14.9. TreeView Drag and Drop
Prev | Chapter 14. Tree View Widget | Next

## 14.9. TreeView Drag and Drop

### 14.9.1. Drag and Drop Reordering

Reordering of the `TreeView` rows (and the underlying tree model rows is enabled by using the `set_reorderable`() method mentioned above. The `set_reorderable`() method sets the "reorderable" property to the specified value and enables or disables the internal drag and drop of `TreeView` rows. When the "reorderable" property is `TRUE` a user can drag `TreeView` rows and drop them at a new location. This action causes the underlying `TreeModel` rows to be rearranged to match. Drag and drop reordering of rows only works with unsorted stores.

### 14.9.2. External Drag and Drop

If you want to control the drag and drop or deal with drag and drop from external sources, you'll have to enable and control the drag and drop using the following methods:

      treeview.enable_model_drag_source(start_button_mask, targets, actions)
      treeview.enable_model_drag_dest(targets, actions)

These methods enable using rows as a drag source and a drop site respectively. `start_button_mask` is a modifier mask (see the gtk.gtk Constants reference in the PyGTK Reference Manual) that specifies the buttons or keys that must be pressed to start the drag operation. `targets` is a list of 3-tuples that describe the target information that can be given or received. For a drag and drop to succeed at least one of the targets must match in the drag source and drag destination (e.g. the "STRING" target). Each target 3-tuple contains the target name, flags (a combination of `gtk.TARGET_SAME_APP` and `gtk.TARGET_SAME_WIDGET` or neither) and a unique int identifier. `actions` describes what the result of the operation should be:

`gtk.gdk.ACTION_DEFAULT`, `gtk.gdk.ACTION_COPY`, | Copy the data.
`gtk.gdk.ACTION_MOVE` | Move the data, i.e. first copy it, then delete it from the source using the `DELETE` target of the X selection protocol.
`gtk.gdk.ACTION_LINK` | Add a link to the data. Note that this is only useful if source and destination agree on what it means.
`gtk.gdk.ACTION_PRIVATE` | Special action which tells the source that the destination will do something that the source doesn't understand.
`gtk.gdk.ACTION_ASK` | Ask the user what to do with the data.

For example to set up a drag drop destination:

      treeview.enable_model_drag_dest([('text/plain', 0, 0)],
                      gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_MOVE)

Then you'll have to handle the `Widget` "drag-data-received" signal to receive that dropped data - perhaps replacing the data in the row it was dropped on. The signature for the callback for the "drag-data-received" signal is:

      def callback(widget, drag_context, x, y, selection_data, info, timestamp)

where `widget` is the `TreeView`, `drag_context` is a `DragContext` containing the context of the selection, `x` and `y` are the position where the drop occurred, `selection_data` is the `SelectionData` containing the data, `info` is the ID integer of the type, `timestamp` is the time when the drop occurred. The row can be identified by calling the method:

      drop_info = treeview.get_dest_row_at_pos(x, y)

where (`x`, `y`) is the position passed to the callback function and `drop_info` is a 2-tuple containing the path of a row and a position constant indicating where the drop is with respect to the row: `gtk.TREE_VIEW_DROP_BEFORE`, `gtk.TREE_VIEW_DROP_AFTER`, `gtk.TREE_VIEW_DROP_INTO_OR_BEFORE` or `gtk.TREE_VIEW_DROP_INTO_OR_AFTER`. The callback function could be something like:

      treeview.enable_model_drag_dest([('text/plain', 0, 0)],
                      gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_MOVE)
      treeview.connect("drag-data-received", drag_data_received_cb)
      ...
      ...
      def drag_data_received_cb(treeview, context, x, y, selection, info, timestamp):
          drop_info = treeview.get_dest_row_at_pos(x, y)
          if drop_info:
              model = treeview.get_model()
              path, position = drop_info
              data = selection.data
              # do something with the data and the model
              ...
          return
      ...

If a row is being used as a drag source it must handle the `Widget` "drag-data-get" signal that populates a selection with the data to be passed back to the drag drop destination with a callback function with the signature:

      def callback(widget, drag_context, selection_data, info, timestamp)

The parameters to `callback` are similar to those of the "drag-data-received" callback function. Since the callback is not passed a tree path or any easy way of retrieving information about the row being dragged, we assume that the row being dragged is selected and the selection mode is `gtk.SELECTION_SINGLE` or `gtk.SELECTION_BROWSE` so we can retrieve the row by getting the `TreeSelection` and retrieving the tree model and `TreeIter` pointing at the row. For example, text from a row could be passed in the drag drop by:

      ...
      treestore = gtk.TreeStore(str, str)
      ...
      treeview.enable_model_drag_source(gtk.gdk.BUTTON1_MASK,
                      [('text/plain', 0, 0)],
                      gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_MOVE)
      treeview.connect("drag-data-get", drag_data_get_cb)
      ...
      ...
      def drag_data_get_cb(treeview, context, selection, info, timestamp):
          treeselection = treeview.get_selection()
          model, iter = treeselection.get_selected()
          text = model.get_value(iter, 1)
          selection.set('text/plain', 8, text)
          return
      ...

The `TreeView` can be disabled as a drag source and drop destination by using the methods:

      treeview.unset_rows_drag_source()
      treeview.unset_rows_drag_dest()

### 14.9.3. TreeView Drag and Drop Example

A simple example program is needed to pull together the pieces of code described above. This example (treeviewdnd.py) is a list that URLs can be dragged from and dropped on. Also the URLs in the list can be reordered by dragging and dropping within the `TreeView`. A couple of buttons are provided to clear the list and to clear a selected item.

        1   #!/usr/bin/env python
        2
        3   # example treeviewdnd.py
        4
        5   import pygtk
        6   pygtk.require('2.0')
        7   import gtk
        8
        9   class TreeViewDnDExample:
       10
       11       TARGETS = [
       12           ('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_WIDGET, 0),
       13           ('text/plain', 0, 1),
       14           ('TEXT', 0, 2),
       15           ('STRING', 0, 3),
       16           ]
       17       # close the window and quit
       18       def delete_event(self, widget, event, data=None):
       19           gtk.main_quit()
       20           return False
       21
       22       def clear_selected(self, button):
       23           selection = self.treeview.get_selection()
       24           model, iter = selection.get_selected()
       25           if iter:
       26               model.remove(iter)
       27           return
       28
       29       def __init__(self):
       30           # Create a new window
       31           self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
       32
       33           self.window.set_title("URL Cache")
       34
       35           self.window.set_size_request(200, 200)
       36
       37           self.window.connect("delete_event", self.delete_event)
       38
       39           self.scrolledwindow = gtk.ScrolledWindow()
       40           self.vbox = gtk.VBox()
       41           self.hbox = gtk.HButtonBox()
       42           self.vbox.pack_start(self.scrolledwindow, True)
       43           self.vbox.pack_start(self.hbox, False)
       44           self.b0 = gtk.Button('Clear All')
       45           self.b1 = gtk.Button('Clear Selected')
       46           self.hbox.pack_start(self.b0)
       47           self.hbox.pack_start(self.b1)
       48
       49           # create a liststore with one string column to use as the model
       50           self.liststore = gtk.ListStore(str)
       51
       52           # create the TreeView using liststore
       53           self.treeview = gtk.TreeView(self.liststore)
       54
       55          # create a CellRenderer to render the data
       56           self.cell = gtk.CellRendererText()
       57
       58           # create the TreeViewColumns to display the data
       59           self.tvcolumn = gtk.TreeViewColumn('URL', self.cell, text=0)
       60
       61           # add columns to treeview
       62           self.treeview.append_column(self.tvcolumn)
       63           self.b0.connect_object('clicked', gtk.ListStore.clear, self.liststore)
       64           self.b1.connect('clicked', self.clear_selected)
       65           # make treeview searchable
       66           self.treeview.set_search_column(0)
       67
       68           # Allow sorting on the column
       69           self.tvcolumn.set_sort_column_id(0)
       70
       71           # Allow enable drag and drop of rows including row move
       72           self.treeview.enable_model_drag_source( gtk.gdk.BUTTON1_MASK,
       73                                                   self.TARGETS,
       74                                                   gtk.gdk.ACTION_DEFAULT|
       75                                                   gtk.gdk.ACTION_MOVE)
       76           self.treeview.enable_model_drag_dest(self.TARGETS,
       77                                                gtk.gdk.ACTION_DEFAULT)
       78
       79           self.treeview.connect("drag_data_get", self.drag_data_get_data)
       80           self.treeview.connect("drag_data_received",
       81                                 self.drag_data_received_data)
       82
       83           self.scrolledwindow.add(self.treeview)
       84           self.window.add(self.vbox)
       85           self.window.show_all()
       86
       87       def drag_data_get_data(self, treeview, context, selection, target_id,
       88                              etime):
       89           treeselection = treeview.get_selection()
       90           model, iter = treeselection.get_selected()
       91           data = model.get_value(iter, 0)
       92           selection.set(selection.target, 8, data)
       93
       94       def drag_data_received_data(self, treeview, context, x, y, selection,
       95                                   info, etime):
       96           model = treeview.get_model()
       97           data = selection.data
       98           drop_info = treeview.get_dest_row_at_pos(x, y)
       99           if drop_info:
      100               path, position = drop_info
      101               iter = model.get_iter(path)
      102               if (position == gtk.TREE_VIEW_DROP_BEFORE
      103                   or position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE):
      104                   model.insert_before(iter, [data])
      105               else:
      106                   model.insert_after(iter, [data])
      107           else:
      108               model.append([data])
      109           if context.action == gtk.gdk.ACTION_MOVE:
      110               context.finish(True, True, etime)
      111           return
      112
      113   def main():
      114       gtk.main()
      115
      116   if __name__ == "__main__":
      117       treeviewdndex = TreeViewDnDExample()
      118       main()

The result of running the example program treeviewdnd.py is illustrated in Figure 14.8, “TreeView Drag and Drop Example”:

Figure 14.8. TreeView Drag and Drop Example

The key to allowing both external drag and drop and internal row reordering is the organization of the targets (the `TARGETS` attribute - line 11). An application specific target (`MY_TREE_MODEL_ROW`) is created and used to indicate a drag and drop within the `TreeView` by setting the `gtk.TARGET_SAME_WIDGET` flag. By setting this as the first target the drag destination will attempt to match it first with the drag source targets. Next the source drag actions must include `gtk.gdk.ACTION_MOVE` and `gtk.gdk.ACTION_DEFAULT` (see lines 72-75). When the destination is receiving the data from the source, if the `DragContext` action is `gtk.gdk.ACTION_MOVE` the source is told to delete the data (in this case the row) by calling the `DragContext` method `finish`() (see lines 109-110). The `TreeView` provides a number of internal functions that we are leveraging to drag, drop and delete the data.

Prev | Up | Next
14.8. TreeSelections | Home | 14.10. TreeModelSort and TreeModelFilter