Create  Edit  FrontPage  Index  Search  Changes  History  RSS  Login

tut-gtk2-dnd-native-treev

DnD Widgets With Their Own Gdk::Window

(10.3)

Now that we have learned the basics of drag-and-drop mechanism exploring the widgets without their own Gdk::Window, which we also dubbed "widgets without native DnD support", it is time to look at those GTk widgets, that do support DnD by design. Since these Gtk widgets have built in drag-and-drop mechanism, they in general require very little or no additional coding. However, sometimes you are not satisfied with the default dnd behaviour, and you may need to try to implement some customized dnd functionality. Gtk widgets provide a number of methods, attributes and signals that comprise "dnd infrastructure" to help you mould the DnD behaviour to better fit your needs. In the continuation of this chapter we will investigate both of these options and mechanisms. The most prominent widgets from this category are the Gtk::TextView, Gtk::IconView, and Gtk::TreeView.

DnD In Text View Widget

(10.3.1)

Of the three TextView, IconView, and TreeView widgets, the Gtk::TreeView widget is the list demanding to implement drag-and-drop features in your own Gtk GUI programs. Its DnD functionality is fully enabled for any Grk::TextView object you create, and users can start dragging text-view objects by selecting and dragging them to desired locations within the text-view. It is even possible to drag items external to the application with the text-view, whose contents can be converted to plain text, or replaced with the URI text. This text-view's default behaviour is quite satisfying when used on selected text within the text-view itself, but is rather limited for external items, nevertheless, it is fully functional, without any additional programming (coding) effort.

drag-in-txtv-colage.png

To demonstrate these to Gtk::TextView native drag-and-drop properties and behaviour, I included a simple example program called 'textview-native-dnd.rb', in which we use our DnD debugging module 'HikiDnDdbg' to show that all eight Gtk::Widget's drag related signals are caught, though, we have not declared neither source nor destination widget in the code. The only thing we did in this program is set signal handlers for all the Gtk::Widget's drag related signals.

Note:

As you will see in the listing for this example we can use two global variables ($MONITOR, and $DEBUG) to control the behaviour of the 'prn_dnd_sig_parms' method. If you do not care for the debugging you can simply ignore these variables, (you should not set them). The difference between the two variables is in the debugging message verbosity, namely, $DEBUG prints extensive analysis of DnD signal parameters, attributes and setups, whereas $MONITOR reports only if a specified signal was caught.

Either '$DEBUG' or '$MONITOR' variable must be set totruefor the 'prn_dnd_sig_parms' debug method to work! If both '$DEBUG' or '$MONITOR' variables are set '$MONITOR' cancels a more verbose '$DEBUG' setting.

Let's look at the program listing:

Complete 'textview-native-dnd.rb' Program Listing

(10.3.1.1)

Programmer's Note:
(10.3.1.1.1)
(HikiGtk and HikiDnDdbg modules can be viewed and copied from section: 10.2.1.1 [Time To Start Using Object-Oriented Programming Paradigm])

textview-native-dnd.rb

#!/usr/bin/env ruby
$: << '~/work/HikiLib'		# replace this with your path to the tutorial modules
require 'hiki-gtk.rb'
include HikiGtk
require 'hiki-dnd-dbg.rb'
include HikiDnDdbg

$MONITOR=true
# $DEBUG=true	# $MONITOR cancels $DEBUG

class TextViewDND < BasicWindow
  def initialize(title)
    super
    set_size_request(350, 250)

    textview = Gtk::TextView.new
    textview.buffer.text = "Exploring DnD in TextView widget. Try to select\n" +
                           "some text abd drag to a new location.\n" +
                           "line-3 abc d\nline-4 ...\nline-5 -- +++ [ ] {{}}\n" +
                           "line-6 _____________\n" +
                           "Last line."
=begin
      Gtk::Widget drag related signals and mnemonics of their parameters:
      -------------------------------------------------------------------
      drag-begin ............... self, drag_context
      drag-data-delete ......... self, drag_context
      drag-data-get ............ self, drag_context, data, info, time
      drag-data-received ....... self, drag_context, x, y, data, info, time
      drag-drop ................ self, drag_context, x, y, time
      drag-end ................. self, drag_context
      drag-leave ............... self, drag_context, time
      drag-motion .............. self, drag_context, x, y, time
=end
    textview.signal_connect('drag-begin')                   do |w,dc|
      prn_dnd_sig_parms('drag-begin',         w,dc)
    end
    textview.signal_connect('drag-data-delete')             do |w,dc|
      prn_dnd_sig_parms('drag-data-delete',   w,dc)
    end
    textview.signal_connect('drag-data-get')                do |w,dc,d,i,t|
      prn_dnd_sig_parms('drag-data-get',      w,dc,d,i,t)
    end
    textview.signal_connect('drag-data-received')           do |w,dc,x,y,d,i,t|
      prn_dnd_sig_parms('drag-data-received' ,w,dc,x,y,d,i,t)
    end
    textview.signal_connect('drag-drop')                    do |w,dc,x,y,t|
      prn_dnd_sig_parms('drag-drop',         w,dc,x,y,t)
    end
    textview.signal_connect('drag-end')                     do |w,dc|	
      prn_dnd_sig_parms('drag-end',          w,dc)
    end
    textview.signal_connect('drag-leave')                   do |w,dc,t|
      prn_dnd_sig_parms('drag-leave',        w,dc,t)
    end
    textview.signal_connect('drag-motion')                  do |w,dc,x,y,t|
      prn_dnd_sig_parms('drag-motion',       w,dc,x,y,t)
    end

    scrolledw = Gtk::ScrolledWindow.new
    scrolledw.border_width = 5
    scrolledw.add(textview)
    scrolledw.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_ALWAYS)

    add(scrolledw)
    show_all
    Gtk.main

  end
end

TextViewDND.new("Exploring Native Text-View DnD")


The main point of this program example is to see that drag-and-drop actions, when used with Gtk::TextView widget, trigger all of the Gtk::Widget's drag related signals, and how to use relevant signal parameters to try to better understand the behind the scene DnD processes. You may find 'prn_dnd_sig_parms' method in HikiDnDdbg module a useful tool when writing your own programs employing DnD mechanisms.

When reading the above program following is the output on the controlling terminal or console, after you drag and drop an item:

Console Output:

(10.3.1.1.2)

drag-begin .............................. caught.
drag-motion ... potentially many 'drag-motion' signals were caught.
drag-leave .............................. caught.
drag-drop ............................... caught.
drag-data-get ........................... caught.
drag-data-received ...................... caught.
drag-data-delete ........................ caught.
drag-end ................................ caught.

If you wish to see a more verbose console messages, find the '$MONITOR=true' statement near the top of the listing, and comment out the $MONITOR line and uncomment the $DEBUG line like the following:

# $MONITOR=true
$DEBUG=true	# $MONITOR cancels $DEBUG


This example may seem too trivial to even consider it to include in the tutorial, however it took me a while before I figured out, that setting up a Gtk::TextView as destination widget manually causes Gtk only to misbehave. However, there may be circumstances when you may need to provide your own custom DnD actions, particularly when you wanted to drop into Gtk::TextView external objects and render them either as images and pixbufs or rich-text for that matter, which theoretically should all be possible, however, the documentation is far too vague about these situations, and projects that would need these features in their programs end up often as rather expensive RND operations. However, as we will see later, in the segment 10.3.3.1 (Custom DnD In Text View) it is possible to insert images into text-view by dragging and dropping.



DnD Tree View Items

(10.3.2)

Another widget, that requires almost no programming effort to activate native drag-and-drop features is Gtk::TreeView. Besides, this widget is one of those, where dragged and dropped objects are strictly the items belonging to some particular data structures and/or hierarchies and must be of a rather particular format, namely, the rows of predetermined sets of columns stored in related data models or data stores. This particularity about data structure and format is best met if dragging and dropping occurs within the same widget, i.e. you should always be able to drag one row within a list or a tree view to a new location, as long as the drop occurs in the same widget in which the drag also started. Currently this is even more true for a generic Gtk library, used by languages like Ruby, Python and alike, than is for the original GTK+ C and C++ (a.k.a. gtkmm) implementations. Namely, the original C GTK+ implementation allows you to drag rows of data from one list/tree view to another, i.e. in GTK+ you are not limited to the single widget, as you are in Ruby or Python when using Gtk+.

When using DnD mechanism to move around rows within single tree or list view, you are actually employing the native DnD facilities. We will look at these next.

Built-in DnD Tree View Mechanism

(10.3.2.1)

treeview-builtin-dnd-s1.png

Next 'treeview-built-in-dnd.rb' example program will show that only with a line of code you can get the complete drag-and-drop behaviour for free. All you need is to set up the reorderable attribute to true, by using the 'Gtk::TreeView#reorderable=' method. The drag objects here are the tree view rows, regardless whether they are leaf or node items with children. When you left-click, hold the mouse button down and drag the mouse simultaneously the cursor changes to the 'move-icon' and the row you are dragging appears underneath it. If you look carefully, when dragging, you will notice that a potential drop position is shown, namely, the items over which you are dragging the row get highlighted, and when you are in-between i.e., above or below rows, a line appears. If you are dropping the dragged row onto a highlighted row it will become it's child, but if you drop it when the line indicator is shown, the dragged item will be placed at that position.

Office Supplies Array Initialization Module

(10.3.2.1.1)
In the 'treeview-built-in-dnd.rb' example program we use auxiliary initialization application dependent module. If this module were useful also in many other tutorial example programs it would be wise to place it in a common directory or folder, along with our 'HikiGtk' module file (hiki-gtk.rb). However, we decided to place it in the same directory where also the example 'treeview-built-in-dnd.rb' program resides. This requires in the example program we tell Ruby interpreter to also include our current directory, from which we are going to run our example code, in the load path (look for the declarative statement "$: << '.'" at the beginning of the example program). Indeed, you could have as well specified the full path in the 'require' declarative instead, however, I wanted to repeat the idiom expanding the load path, so you get used to it, because it is very convenient when you use module files in other directories.

Following is the module file, which you should copy into your working directory along with the program example:

init-office-supp-arr.rb

# This file is planed to be loaded from the same directory in which
# the example program resides. 
#
# Its name should be: 'init-office-supp-arr.rb'
module InitializeOfficeSuppliesFromASCItable
  # Tree View  Iinitialization  Array Of Arrays:
  # name,   qty,  Children
  # ---------------------------------------------
  INIT_ARRAY = [
    ['Stationery',  nil,  [
        ['Letter paper',       "500 sheets",  nil],
        ['Envelopes',          "200",         nil],
        ['Computer paper',  nil,    [
            ['Legal',  "1 box",      nil],
            ['Letter', "2 boxes",    nil],
          ]
        ]
      ]
    ],
    ['Notebooks & Writing Pads',  nil,  [
        ['Memo Book',   "10",  nil],
        ['Journals',    "5",  nil],
      ]
    ],

    ['Computer Accessories',  nil,  [
        ['Printer Tonner',  "2x C501AB",  nil],
        ['RWCDs&DVD',       "100x 4GB",  nil],
      ]
    ],
  ]
  class OfficeSupplies
    attr_accessor :name, :qty, :children
    def initialize(name, qty, children)
      @name, @qty, @children = name, qty, children
      arr = []
      if @children
        @children.each do |row|
          arr << OfficeSupplies.new(*row)
        end
        @children = arr
      end
    end
  end
end

Let's look now at the (10.3.2.1) example program:

'treeview-built-in-dnd.rb' Program Listing

(10.3.2.1 - /continued/)

treeview-built-in-dnd.rb

#!/usr/bin/env ruby
$: << '~/work/HikiLib'
require 'hiki-gtk.rb'
include HikiGtk
$: << '.'
require 'init-office-supp-arr.rb'
include InitializeOfficeSuppliesFromASCItable

class DnDWindow < BasicWindow
  NAME_COLUMN, QTY_COLUMN = 0, 1
  def initialize(title)
    super
    self.set_size_request(400, 350)
    office_supply_list = []
    INIT_ARRAY.each_with_index do |row, i|
      office_supply_list[i] = OfficeSupplies.new(*row)
    end
    @treeview = Gtk::TreeView.new(store = Gtk::TreeStore.new(String, String))
    load_office_supplies_into_tree_view(store, office_supply_list, nil)
    @treeview.expand_all        # only works after treview is loaded
    setup_tree_view
    ## -- Enables automatic Drag-And-Drop in the tree view -------
    @treeview.reorderable = true
    scrolled_win = Gtk::ScrolledWindow.new
    scrolled_win.add(@treeview)
    scrolled_win.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC)
    self.add(scrolled_win)
    self.show_all
    Gtk.main
  end

  private
  def setup_tree_view
    renderer = Gtk::CellRendererText.new
    column   = Gtk::TreeViewColumn.new("Name", renderer, :text => NAME_COLUMN)
    @treeview.append_column(column)
    renderer = Gtk::CellRendererText.new
    column   = Gtk::TreeViewColumn.new("Quantity", renderer, :text => QTY_COLUMN)
    @treeview.append_column(column)
  end
  def load_office_supplies_into_tree_view(store, office_supply_list, parent)
    # Add all of the names to the GtkTreeStore.
    office_supply_list.each do |supplies_obj|
      # If the supplies object has children it's a parent
      if (supplies_obj.children)
        # Add the parent as a new root row (element).
        child_parent = store.append(parent)
        child_parent[NAME_COLUMN] = supplies_obj.name
        child_parent[QTY_COLUMN]  = supplies_obj.qty
        load_office_supplies_into_tree_view(store, supplies_obj.children, child_parent)
      # Otherwise, add the leaf item as a child row.
      else
        child = store.append(parent)
        child[NAME_COLUMN] = supplies_obj.name
        child[QTY_COLUMN]  = supplies_obj.qty
      end
    end
  end
end
DnDWindow.new("Built-in Drag-n-Drop Within a Tree View")


We start the above program with a helper module used to initialize our tree view from the asci array. We have discussed this technique in chapter 8 in section 8.2.3.1 (Tedious Job Of Loading Multidimensional Tree Store). The tree view itself is rather simple, and you should be fairly familiar with its code. The only piece of code that deserves our attention is the one that enables the dnd functionality:

## -- Enables automatic Drag-And-Drop in the tree view -------
@treeview.reorderable = true

Also note that we do not need to set up any signal handlers for the dnd to function.

Custom DnD In Tree View

(10.3.2.2)

To explore how to use custom DnD for tree-view widgets, we are going to use almost the same code as before in the 'treeview-built-in-dnd.rb' example. Our program here is called 'treeview-custom-dnd.rb'. Just like the parts with new custom Dnd, the preamble in program example here changed a bit, because we added the module to help us with debugging messages. If you need to refresh your memory about the 'requiring' and 'including' modules and classes or perhaps about the BasicWindow class, please refer back to section 10.2.1.1 (Time To Start Using Object-Oriented Programming Paradigm).

Warning:

Let me warn you that if the default (native) DnD tree-view behaviour does not satisfy your needs, writing custom DnD for Gtk::TreeView at this point most likely will not either, because all you can do is more a learning, testing, and hopefully debugging, exercise rather than a useful programming effort, since the current Gtk library implementation in this area is lacking some of the essential internal helper tools, that would allow using tree view and the pertinent model data indirectly, though even direct access to tree-view items seems to be lacking functionality in the current implementation of Gtk::SelectionData class, which currently does not provide any access to tree-view/model row data, row data paths or their references. As you will see in our next example, we have to resort to some rather dubious techniques to get anything working at all. Nevertheless, I hope that this example will kick-start a more thorough investigation of these issues, and perhaps cause someone to fix either the API or the documentation spelling out what works, and what we can hope will work, or will never work in the future! 

Whenever we wish to implement custom DnD, we need to define our source and destination widgets. For most widgets we use Gtk::Drag.source_set and Gtk::Drag.dest_set methods, however, for Gtk::TreeView objects we have to use by the Gtk::TreeView class provided instance methods Gtk::TreeView#enable_model_drag_source and Gtk::TreeView#enable_model_drag_dest. To all source and destination (*_set or enable_*) methods you have to supply the 'targets', and 'actions' arguments (to refresh your memory about 'targets' see sections 10.1, 10.1.1 and 10.1.2, in particular section 10.1.2 The DnD Source And Destination Objects at the beginning of this chapter). Not surprisingly, our treatment of these arguments (targets, and actions), here is following the footsteps of our discussions in section 10.1. We include the following constants at the beginning of our DnDWindow class definition:

TVDND_FLAGS   = Gtk::Drag::TARGET_SAME_WIDGET	# TARGET_{SAME_{APP,WIDGET}, OTHER_{APP,_WIDGET}}
TVDND_INFO    = 0
TVDND_TARGETS = [["tree view item", TVDND_FLAGS, TVDND_INFO]]
TVDND_ACTIONS = Gdk::DragContext::ACTION_MOVE	# MOVE 	# _ASK,_COPY,_LINK,_MOVE,_DEFAULT,_PRIVATE

Beside the above code, there's nothing new in this version of the 'treeview-custom-dnd.rb' example program until after the following lines:

## -- Enable/disable Gtk's native Drag-And-Drop in the tree view
@treeview.reorderable = false	# false = 'custom dnd'
Programmer's Note:
(10.3.2.2.1)
(HikiGtk and HikiDnDdbg modules can be viewed and copied from section: 10.2.1.1 [Time To Start Using Object-Oriented Programming Paradigm])

treeview-custom-dnd.rb

Last modified:2013/03/13 06:23:04
Keyword(s):
References:[tut-gtk] [tut-gtk2-dnd]