22.3. DND Methods

22.3.1. Setting Up the Source Widget

The method drag_source_set() specifies a set of target types for a drag operation on a widget.

  widget.drag_source_set(start_button_mask, targets, actions)

The parameters signify the following:

  • widget specifies the drag source widget

  • start_button_mask specifies a bitmask of buttons that can start the drag (e.g. BUTTON1_MASK)

  • targets specifies a list of target data types the drag will support

  • actions specifies a bitmask of possible actions for a drag from this window

The targets parameter is a list of tuples each similar to:

  (target, flags, info)

target specifies a string representing the drag type.

flags restrict the drag scope. flags can be set to 0 (no limitation of scope) or the following flags:

  gtk.TARGET_SAME_APP    # Target will only be selected for drags within a single application. 

  gtk.TARGET_SAME_WIDGET # Target will only be selected for drags within a single widget. 

info is an application assigned integer identifier.

If a widget is no longer required to act as a source for drag-and-drop operations, the method drag_source_unset() can be used to remove a set of drag-and-drop target types.

  widget.drag_source_unset()

22.3.2. Signals On the Source Widget

The source widget is sent the following signals during a drag-and-drop operation.

Table 22.1. Source Widget Signals

drag_begindef drag_begin_cb(widget, drag_context, data):
drag_data_getdef drag_data_get_cb(widget, drag_context, selection_data, info, time, data):
drag_data_deletedef drag_data_delete_cb(widget, drag_context, data):
drag_enddef drag_end_cb(widget, drag_context, data):

The "drag-begin" signal handler can be used to set up some inital conditions such as a drag icon using one of the Widget methods: drag_source_set_icon(), drag_source_set_icon_pixbuf(), drag_source_set_icon_stock(). The "drag-end' signal handler can be used to undo the actions of the "drag-begin" signal ahndler.

The "drag-data-get" signal handler should return the drag data matching the target specified by info. It fills in the gtk.gdk.SelectionData with the drag data.

The "drag-delete" signal handler is used to delete the drag data for a gtk.gdk.ACTION_MOVE action after the data has been copied.

22.3.3. Setting Up a Destination Widget

The drag_dest_set() method specifies that this widget can receive drops and specifies what types of drops it can receive.

drag_dest_unset() specifies that the widget can no longer receive drops.

  widget.drag_dest_set(flags, targets, actions)

  widget.drag_dest_unset()

flags specifies what actions GTK+ should take on behalf of widget for drops on it. The possible values of flags are:

gtk.DEST_DEFAULT_MOTION

If set for a widget, GTK+, during a drag over this widget will check if the drag matches this widget's list of possible targets and actions. GTK+ will then call drag_status() as appropriate.

gtk.DEST_DEFAULT_HIGHLIGHT

If set for a widget, GTK+ will draw a highlight on this widget as long as a drag is over this widget and the widget drag format and action is acceptable.

gtk.DEST_DEFAULT_DROP

If set for a widget, when a drop occurs, GTK+ will check if the drag matches this widget's list of possible targets and actions. If so, GTK+ will call drag_get_data() on behalf of the widget. Whether or not the drop is succesful, GTK+ will call drag_finish(). If the action was a move and the drag was succesful, then TRUE will be passed for the delete parameter to drag_finish().

gtk.DEST_DEFAULT_ALL

If set, specifies that all default actions should be taken.

targets is a list of target information tuples as described above.

actions is a bitmask of possible actions for a drag onto this widget. The possible values that can be or'd for actions are:

  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 and actions are ignored if flags does not contain gtk.DEST_DEFAULT_MOTION or gtk.DEST_DEFAULT_DROP. In that case the application must handle the "drag-motion" and "drag-drop" signals.

The "drag-motion" handler must determine if the drag data is appropriate by matching the destination targets with the gtk.gdk.DragContext targets and optionally by examining the drag data by calling the drag_get_data() method. The gtk.gdk.DragContext. drag_status() method must be called to update the drag_context status.

The "drag-drop" handler must determine the matching target using the Widget drag_dest_find_target() method and then ask for the drag data using the Widget drag_get_data() method. The data will be available in the "drag-data-received" handler.

The dragtargets.py program prints out the targets of a drag operation in a label:

    1   #!/usr/local/env python
    2
    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()

The program creates a window and then sets it as a drag destination for no targets and actions by setting the flags to zero. The motion_cb() and drop_cb() handlers are connected to the "drag-motion" and "drag-drop" signals respectively. The motion_cb() handler just sets the drag status for the drag context so that a drop will be enabled. The drop_cb() sets the label text to a string containing the drag targets and finishes the drop leaving the source data intact.

22.3.4. Signals On the Destination Widget

The destination widget is sent the following signals during a drag-and-drop operation.

Table 22.2. Destination Widget Signals

drag_motiondef drag_motion_cb(widget, drag_context, x, y, time, data):
drag_dropdef drag_drop_cb(widget, drag_context, x, y, time, data):
drag_data_receiveddef drag_data_received_cb(widget, drag_context, x, y, selection_data, info, time, data):

The dragndrop.py example program demonstrates the use of drag and drop in one application. A button with a xpm pixmap (in gtkxpm.py) is the source for the drag; it provides both text and xpm data. A layout widget is the destination for the xpm drop while a button is the destination for the text drop. Figure 22.1, “Drag and Drop Example” illustrates the program display after an xpm drop has been made on the layout and a text drop has been made on the button:

Figure 22.1. Drag and Drop Example

Drag and Drop Example

The dragndrop.py source code is:

    1	#!/usr/bin/env python
    2	
    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	        # create the scrollbars and pack into the table
   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	        # tell the scrollbars to use the layout widget's adjustments
   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()