22.3. Методы D&D

22.3.1. Настройка исходного виджета

Метод drag_source_set() определяет набор типов целей для перетаскивания.

  widget.drag_source_set(start_button_mask, targets, actions)

Параметры означают следующее:

  • widget определяет исходный виджет

  • start_button_mask определяет битовую маску кнопок, которые могут начинать перетаскивание (например BUTTON1_MASK)

  • targets определяет список целей которые будут поддерживаться

  • actions определяет битовую маску возможных действий перетаскивания из этого окна.

Параметр targets это список кортежей, имеющих вид:

  (target, flags, info)

target определяет строку, отображающую тип перетаскивания.

flags ограничивает область перетаскивания. flags может быть установлено в 0 (без ограничений) или содержать следующие значения:

  gtk.TARGET_SAME_APP    # Цель будет работать только в пределах этого приложения. 

gtk.TARGET_SAME_WIDGET # Цель будет работать только в пределах одного виджета.

info это целочисленный идентификатор приложения.

Если виджет более не нуждается в источнике D&D операций, то может быть использован метод drag_source_unset() для удаления множества целей drag and drop.

  widget.drag_source_unset()

22.3.2. Сигналы на исходном виджете

Исходный виджет отправляет следующие сигналы в течение операции drag and drop:

Таблица 22.1. Сигналы исходного виджета

drag_begin def drag_begin_cb(widget, drag_context, data):
drag_data_get def drag_data_get_cb(widget, drag_context, selection_data, info, time, data):
drag_data_delete def drag_data_delete_cb(widget, drag_context, data):
drag_end def drag_end_cb(widget, drag_context, data):

Обработчик "drag-begin" может быть использован для настройки дополнительных условий, таких как иконка перетаскивания, с использованием одного из методов Widget: drag_source_set_icon(), drag_source_set_icon_pixbuf(), drag_source_set_icon_stock(). Обработчик сигнала "drag-end' может быть использован для отмены действий обработчика сигнала "drag-begin".

Обработчик сигнала "drag-data-get" должен возвращать перетаскиваемые данные, совпадающие с целью, указанной в info. Он заполняет gtk.gdk.SelectionData данными для перетаскивания.

Обработчик сигнала "drag-delete" используется для удаления перетасктиваемых данных из источника, в случае действия gtk.gdk.ACTION_MOVE.

22.3.3. Настройка конечного виджета

Метод drag_dest_set() определяет, может ли виджет принимать сбрасываемые на него данные, и определяет какой тип данных он может принимать.

drag_dest_unset() определяет виджет, который больше не может принимать сбрасываемые данные.

  widget.drag_dest_set(flags, targets, actions)

widget.drag_dest_unset()

flags определяет, какие действия GTK+ должен предпринимать от лица виджета при скидывании чего-либо на него. Возможные значения flags:

gtk.DEST_DEFAULT_MOTION

Если выбран, то GTK+ во время перетаскивания над виджетом будет проверять, совпадают ли перетаскиваемые данные со списком доступных целей и действий для виджета. GTK+ будет вызывать drag_status().

gtk.DEST_DEFAULT_HIGHLIGHT

Если выбран, то GTK+ будет подсвечивать виджет в то время, когда над ним происходит перетаскивание, и формат перетаскиваемых данных подходит.

gtk.DEST_DEFAULT_DROP

Если выбран, то во время скидывания GTK+ будет проверять совпадает ли цель и действие со списком доступных целей и действий для виджета. В этом случае GTK+ будет вызывать drag_get_data() от имени виджета. В любом случае GTK+ вызовет drag_finish(). Если действием было Move, и перетаскивание было успешным, то в параметр delete метода drag_finish() будет передано TRUE.

gtk.DEST_DEFAULT_ALL

Если выбран, то будут приниматься все действия выше.

targets это список кортежей с информацией о целях, таких, как показано выше.

actions это битовая маска возможных действий для перетаскивания на виджет. Возможные значения:

  gtk.gdk.ACTION_DEFAULT
gtk.gdk.ACTION_COPY
gtk.gdk.ACTION_MOVE
gtk.gdk.ACTION_LINK
gtk.gdk.ACTION_PRIVATE
gtk.gdk.ACTION_ASK

targets и actions игнорируются, если flags не содержит gtk.DEST_DEFAULT_MOTION или gtk.DEST_DEFAULT_DROP. В этом случае приложение должно обрабатывать сигналы "drag-motion" и "drag-drop".

Обработчик "drag-motion" должен определять, насколько подходят перетаскиваемые данные, сравнивая цели приёмника с gtk.gdk.DragContext, и дополнительно, проверкой перетаскиваемых данных, вызывая метод drag_get_data(). Метод gtk.gdk.DragContext. drag_status() должен вызываться для обновления статуса drag_context.

Обработчик "drag-drop" должен определять совпадение целей, используя метод виджета drag_dest_find_target() и запрос перетаскиваемых данных, с помощью метода виджета drag_get_data(). Данные будут доступны в обработчике "drag-data-received".

Программа dragtargets.py выводит цели операции перетаскивания в метке:

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. import pygtk
  4. pygtk.require('2.0')
  5. import gtk
  6.  
  7. def motion_cb(wid, context, x, y, time):
  8. context.drag_status(gtk.gdk.ACTION_COPY, time)
  9. return True
  10.  
  11. def drop_cb(wid, context, x, y, time):
  12. l.set_text('n'.join([str(t) for t in context.targets]))
  13. context.finish(True, False, time)
  14. return True
  15.  
  16. w = gtk.Window()
  17. w.set_size_request(200, 150)
  18. w.drag_dest_set(0, [], 0)
  19. w.connect('drag_motion', motion_cb)
  20. w.connect('drag_drop', drop_cb)
  21. w.connect('destroy', lambda w: gtk.main_quit())
  22. l = gtk.Label('Метка')
  23. w.add(l)
  24. w.show_all()
  25.  
  26. gtk.main()
  27.  

Программа создаёт окно и настраивает приёмник перетаскивания без целей и действий, с пустыми флагами. Обработчики motion_cb() и drop_cb() подключены к сигналам "drag-motion" и "drag-drop" соответственно. Обработчик motion_cb() просто устанавливает состояние перетаскивания, поэтому скидывание включено. drop_cb() устанавливает текст метки содержащий цели перетаскивания, и заканчивает D&D, оставляя исходные данные нетронутыми.

22.3.4. Сигналы на конечном виджете

Конечный виджет отправляет следующие сигналы в течении операции D&D:

Таблица 22.2. сигналы конечного виджета

drag_motion def drag_motion_cb(widget, drag_context, x, y, time, data):
drag_drop def drag_drop_cb(widget, drag_context, x, y, time, data):
drag_data_received def drag_data_received_cb(widget, drag_context, x, y, selection_data, info, time, data):

Пример dragndrop.py показывает использование drag and drop в одном приложении. Кнопка с картинкой (в gtkxpm.py) является источником, предоставляющим текст и xpm картинку. Виджет Layout является конечным для xpm-картинки, а кнопка для текста. Рисунок 22.1, “Пример Drag and Drop” показывает окно программы после броска картинки на layout и текста на кнопку:

Рисунок 22.1. Пример Drag and Drop

Drag and Drop Example

Далее показан листинг dragndrop.py:

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # example dragndrop.py
  4.  
  5. import pygtk
  6. pygtk.require('2.0')
  7. import gtk
  8. import string, time
  9.  
  10. import gtkxpm
  11.  
  12. class DragNDropExample:
  13. HEIGHT = 600
  14. WIDTH = 600
  15. TARGET_TYPE_TEXT = 80
  16. TARGET_TYPE_PIXMAP = 81
  17. fromImage = [ ( "text/plain", 0, TARGET_TYPE_TEXT ),
  18. ( "image/x-xpixmap", 0, TARGET_TYPE_PIXMAP ) ]
  19. toButton = [ ( "text/plain", 0, TARGET_TYPE_TEXT ) ]
  20. toCanvas = [ ( "image/x-xpixmap", 0, TARGET_TYPE_PIXMAP ) ]
  21.  
  22. def layout_resize(self, widget, event):
  23. x, y, width, height = widget.get_allocation()
  24. if width > self.lwidth or height > self.lheight:
  25. self.lwidth = max(width, self.lwidth)
  26. self.lheight = max(height, self.lheight)
  27. widget.set_size(self.lwidth, self.lheight)
  28.  
  29. def makeLayout(self):
  30. self.lwidth = self.WIDTH
  31. self.lheight = self.HEIGHT
  32. box = gtk.VBox(False,0)
  33. box.show()
  34. table = gtk.Table(2, 2, False)
  35. table.show()
  36. box.pack_start(table, True, True, 0)
  37. layout = gtk.Layout()
  38. self.layout = layout
  39. layout.set_size(self.lwidth, self.lheight)
  40. layout.connect("size-allocate", self.layout_resize)
  41. layout.show()
  42. table.attach(layout, 0, 1, 0, 1, gtk.FILL|gtk.EXPAND,
  43. gtk.FILL|gtk.EXPAND, 0, 0)
  44. # Создаём полосы прокрутки и размещаем их в таблице
  45. vScrollbar = gtk.VScrollbar(None)
  46. vScrollbar.show()
  47. table.attach(vScrollbar, 1, 2, 0, 1, gtk.FILL|gtk.SHRINK,
  48. gtk.FILL|gtk.SHRINK, 0, 0)
  49. hScrollbar = gtk.HScrollbar(None)
  50. hScrollbar.show()
  51. table.attach(hScrollbar, 0, 1, 1, 2, gtk.FILL|gtk.SHRINK,
  52. gtk.FILL|gtk.SHRINK,
  53. 0, 0)
  54. # Используем полосы прокрутки как регуляторы для виджета layout
  55. vAdjust = layout.get_vadjustment()
  56. vScrollbar.set_adjustment(vAdjust)
  57. hAdjust = layout.get_hadjustment()
  58. hScrollbar.set_adjustment(hAdjust)
  59. layout.connect("drag_data_received", self.receiveCallback)
  60. layout.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
  61. gtk.DEST_DEFAULT_HIGHLIGHT |
  62. gtk.DEST_DEFAULT_DROP,
  63. self.toCanvas, gtk.gdk.ACTION_COPY)
  64. self.addImage(gtkxpm.gtk_xpm, 0, 0)
  65. button = gtk.Button("Text Target")
  66. button.show()
  67. button.connect("drag_data_received", self.receiveCallback)
  68. button.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
  69. gtk.DEST_DEFAULT_HIGHLIGHT |
  70. gtk.DEST_DEFAULT_DROP,
  71. self.toButton, gtk.gdk.ACTION_COPY)
  72. box.pack_start(button, False, False, 0)
  73. return box
  74.  
  75. def addImage(self, xpm, xd, yd):
  76. hadj = self.layout.get_hadjustment()
  77. vadj = self.layout.get_vadjustment()
  78. style = self.window.get_style()
  79. pixmap, mask = gtk.gdk.pixmap_create_from_xpm_d(
  80. self.window.window, style.bg[gtk.STATE_NORMAL], xpm)
  81. image = gtk.Image()
  82. image.set_from_pixmap(pixmap, mask)
  83. button = gtk.Button()
  84. button.add(image)
  85. button.connect("drag_data_get", self.sendCallback)
  86. button.drag_source_set(gtk.gdk.BUTTON1_MASK, self.fromImage,
  87. gtk.gdk.ACTION_COPY)
  88. button.show_all()
  89. # have to adjust for the scrolling of the layout - event location
  90. # is relative to the viewable not the layout size
  91. self.layout.put(button, int(xd+hadj.value), int(yd+vadj.value))
  92. return
  93.  
  94. def sendCallback(self, widget, context, selection, targetType, eventTime):
  95. if targetType == self.TARGET_TYPE_TEXT:
  96. now = time.time()
  97. str = time.ctime(now)
  98. selection.set(selection.target, 8, str)
  99. elif targetType == self.TARGET_TYPE_PIXMAP:
  100. selection.set(selection.target, 8,
  101. string.join(gtkxpm.gtk_xpm, 'n'))
  102.  
  103. def receiveCallback(self, widget, context, x, y, selection, targetType,
  104. time):
  105. if targetType == self.TARGET_TYPE_TEXT:
  106. label = widget.get_children()[0]
  107. label.set_text(selection.data)
  108. elif targetType == self.TARGET_TYPE_PIXMAP:
  109. self.addImage(string.split(selection.data, 'n'), x, y)
  110.  
  111. def __init__(self):
  112. self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
  113. self.window.set_default_size(300, 300)
  114. self.window.connect("destroy", lambda w: gtk.main_quit())
  115. self.window.show()
  116. layout = self.makeLayout()
  117. self.window.add(layout)
  118.  
  119. def main():
  120. gtk.main()
  121.  
  122. if __name__ == "__main__":
  123. DragNDropExample()
  124. main()
  125.