Create  Edit  FrontPage  Index  Search  Changes  History  RSS  Login

tut-gtk2-dnd-intro

DnD Introduction

(10.1)

When in the context of GUI we are talking about "dragging and dropping" our operating domain consists of three kinds of objects. In particular in our Gtk environment, we are dragging (moving) Gtk widgets between two different geometric locations. One is the location from which you are dragging a widget. This location is called the 'source' location. Naturally, the location to which you would like to drag the widget will then be called the 'destination' location. So the three object we mentioned earlier are (1) the graphic object you are moving, (2) the source location, and (3) the destination location. Behind the scene the drag-and-drop metaphor a significant inter-process communications is shielded from GUI developers. Namely, when a user initiates the drag process in the source environment the information about the object being dragged must be conveyed to the destination so it can properly react to the 'drop'. Depending on the type of 'drag' action the source and destination may, at the completion of the 'drag', need to perform different but coordinated actions, such as copying, duplicating and/or removing the object being dragged from the source location, and placing, or perhaps even refusing to accept the dragged object at the destination point.

Gtk includes a module and a number of classes to provide required drag-and-drop behaviour and additional helper objects and methods. The most prominent is appropriately called the Gtk::Drag module and some of the helper classes are Gdk::DragContext, Gdk::Atom, Gtk::TargetList, in addition to the Gtk::Selection module. An important part of drag-and-drop features are signals. As it happens, the three drag-and-drop objects rely on Gtk::Widget drag related signals.

Identifying the DnD Objects

(10.1.1)

Unlike to a GUI user, to a GUI developer identification of a drag-and-drop objects is not at all that obvious. While conceptually it should be always easy to determine what is the dragging object and its source and destination locations, in reality the lines between these items may be blurry. This is mostly due to the fact, that Gtk performs many tasks for you behind the scene, and also because the objects that are to be dragged are not at all directly referenced in drag-and-drop API. Instead, the methods in Gtk::Drag module and related classes refer to them as abstracttargetswhich you have to identify not so much in terms of graphic objects but rather as Gtk+ abstraction defined in Gtk::TargetList. Gtk::TargetList object is an array of targets (i.e. draggable items), that are specified with an ordered list or a tuple of three items '[target, flags, info]'. The'target'parameter is a string identifying the object to be dragged from the source and dropped onto a destination. The'flags'parameter is one of the constants defined in Gtk::Drag#TargetFlags (TARGET_SAME_APP, TARGET_SAME_WIDGET, TARGET_OTHER_APP, and TARGET_OTHER_WIDGET). The last'info'element in the targets tuple is by the application or the developer defined identification integer.

As we already mentioned another reason for a blurry view of what a drag-and-drop object is, is the fact that Gtk performs many tasks such as those defined in Gdk::DragContext#GdkDragAction behind the scene. This means that Gtk and not the programer is responsible for copying, moving and/or removing items you are dragging. However, this may may not be entirely true or not at all, depending whether you are dragging items between different widgets or within the widgets that supports dragging and dropping by design. An example of such a widget, which supports dnd by design is the Gtk::TreeView. You may also decide you want to bypass, Gtk default behaviour and implement your own drag-and-drop behaviour. In that case you will need to perform copying and removing dragging objects yourself. In that case you will need to obtain the true dragging object, such as a tree view row, or an icon residing in icon view, or perhaps a different image object all together for instance a large image the icon represents. As you can see, in such situations you may need to have a true reference to the dragged object and Gtk's target abstraction may be rather useless. Fortunately these are special cases, and require a better understanding of these issues. You may find it necessary to consult the'Inter-Client Communications Conventions Manual' (ICCCM). Though, our intention here is to provide the basic coverage of the dnd in Ruby Gtk, we will look at some of the more advanced issues, enough to point you in the right direction, and perhaps to inspire developers to polish the rough edges in this areas of Gtk.

The DnD Source And Destination Objects

(10.1.2)

The source locations and/or widgets are those in which the objects to be dragged originally reside, and the destinations are places, widgets or object onto which you wish to drop the dragged objects. These widgets or objects may be different, for instance you may plan to drag an icon from an icon view onto some other viewing area, perhaps a canvas or a text widget. Or they can be one and the same widget and indeed the same object, as is the case when you are reordering rows in a tree view, or items in the text view. In the previous chapter 9, (section: 9.6.2.2), we have already seen our toolbar 'dnd-toolbox.rb' example in which the source and destination are of the same type namely Gtk::Frame, but they are two different objects.

Why Do the DnD Widgets Need a Gdk::Window Of Their Own

(10.1.2.1)
The Gtk API tells us that all the widgets which have their own Gdk::Window can be set as source or destination widgets. A widget that contains its own Gdk::Window must not have Gtk::Widget::NO_WINDOW flag set. You can check for this flag with the Gtk::Widget#no_window? method. (Note that this method returns true if the widget (the receiver object) does not contain the Gdk::Window.)

The above statement is not completely accurate, because it suggests that Gtk widgets without their own Gdk::Window can not be set as source and destination drag-and-drop widgets. Later we will develop a simple example (button-to-label-dnd.rb) program with button as the dnd source and a label as a the dnd destination widget. Neither Gtk button nor label have their own Gdk::Window, and yet they can be set up as source and destination widgets.

What is true with respect to drag-and-drop restrictions for widgets with no Gdk::Window of their own is, that such widgets do not respond to very important dnd'drag-data-get' and'drag-data-received' signals used by drag-and-drop protocol (PROTO_XDND) on which Gtk's dnd communications is based, which translated to plain English simply means, that such widgets can not benefit from predefined Gtk drag-and-drop actions, and that you will have to provide your own callbacks for either 'drag-begin', 'drag-begin' or some other more appropriate dnd signal.

The basic principles behind setting up drag-and-drop source and destination objects are simple, however, some widgets support dnd by design, while most of the Gtk widgets do not. Naturally, sometimes you need to mix the two kinds of (the dnd and the non-dnd) widgets. This complicates both the teaching and the learning of the Gtk's dnd metaphor a bit. This perhaps is one of the reasons in most of the currently available Gtk drag-and-drop tutorials you find rather incomplete or useless examples, such as dragging a button to a label, which in itself is a nonsense proposition, since a label can never contain a button on the first place. Nevertheless, as we will see shortly, using a button and a label for a source and destination, after all, makes sense, especially when the students understand that in this case the source and destination objects themselves are being also dragged and/or manipulated by the dnd.

There Are Three Different Source And Destination DnD Set-Up Scenarios

(10.1.2.2)
Normally, you have to set up source and destination widgets. There exists a plethora of methods to accomplish this. However, there are three different situations or circumstances in which you need to set up either source or destination widget for drag-and-drop management. Depending on the combination of the source and destination drag-and-drop widgets you are using in your application, there are many ways to set up (or, as you will soon see, perhaps not at all set up) these source and destination widgets.

We can briefly point out the three different scenarios for setting up source and destination widgets as follows. Starting with those widgets that support dnd by design, we encounter those (1) for which you may simply start using the native dnd facilities without ever setting either the source or the destination widgets (for instance Gtk::TextView is such a widget). (2) Next, from the same category we have such as the Gtk::IconView, or the Gtk::TreeView widget, with which you can use the default dnd behaviour with almost no extra programming work. And lastly, (3) the widgets without native drag-and-drop support for which, in order to use drag-and-drop facilities, you really need to register those widgets as source or destination widget or both.

We plan to look at all of these three situations.

To explain what the source and destination drag-and-drop widgets and related issues are, it is best to study how to set these up for widgets without the native dnd support. When setting up such a widget without the native dnd support as source or destination widget, you have to identify the drag-object(s), or as we called them earlier above, the 'target(s)'.

If you plan to use Gtk's dnd features for either the source or destination widget then during this registration of that particular widget you also need to specify theactionthe dnd system is to take for the item being dragged. This action is defined as a constant in Gdk::DragContext#GdkDragAction and conveys to the Gtk system whether the item is to be copied, moved, removed from the source, and how to react at the destination at the time the drop occurs.

Note:
You should be aware, that for the majority of Gtk widgets, Gtk has no clue how to handle them neither on the source nor on the destination side, and that for those widgets any of the predefined actions such as for Gdk::DragContext::ACTION_COPY, {_MOVE, _ASK, etc.}, are meaningless. Indeed, for such widgets you have to implement your own actions in appropriate drag-and-drop signal handlers. The simple 'button-to-label-dnd.rb' example, you will see shortly, falls into this category.

Following are few API examples to show different methods used to register source and destination widgets, and the kind of arguments you may be required to supply when registering these widgets.

You use Gtk::Drag.source_set and Gtk::Drag.dest_set module methods, when source and destination widgets are arbitrary Gtk objects with their own Gdk::Window:

(#1)

Gtk::Drag.source_set(source_widget,    start_button_mask, targets, actions)
Gtk::Drag.dest_set(destination_widget, flags, targets, actions)

When source and destination woidgets natively support dnd, you use their "enable..." instance methods:

(#2)

# Gtk::TreeView#enable_model_drag_source(start_button_mask, targets, actions)
treeview.enable_model_drag_source(Gdk::Window::BUTTON1_MASK, TVDND_TARGETS, TVDND_ACTIONS)

# Gtk::IconView#enable_model_drag_source(start_button_mask, targets, actions)
 enable_model_drag_source(
   	start_button_mask = Gdk::Window::BUTTON1_MASK,
   	targets           = [],
   	actions           = IVDND_ACTION
 )

and a less involved:

(#3)

# Gtk::TreeView#enable_model_drag_dest(targets, actions)
treeview.enable_model_drag_dest(TVDND_TARGETS, Gdk::DragContext::ACTION_ASK)

The targets argument above identifies all the objects that dnd source and destination will accept. It is convenient to assemble all your targets in a constant since your source and destination widgets need to agree they both will operate with the same drag-and-drop objects (data). Namely, when your source and destination widgets are registered, they should be given the same set of targets as a parameter. The targets argument in either case above is an array of target entries, where each entry is, as mentioned above, the tuple in the form [target, flags, info]. Though you may get away by specifying an empty array ([]) for your targets argument, it is better to create constants with, for your application, meaningful data to be used when registering your source and destination widgets. Here is a code snippet from our next example program in which we build our convenience constants and, indeed, the targets array:

TVDND_FLAGS   = Gtk::Drag::TARGET_SAME_WIDGET   # TARGET_{SAME_APP, SAME_WIDGET, OTHER_APP, OTHER_WIDGET}
TVDND_INFO    = 0
TVDND_TARGETS = [["treeview_item", TVDND_FLAGS, TVDND_INFO]]
TVDND_ACTIONS = Gdk::DragContext::ACTION_MOVE	 # _ASK,_COPY,_LINK,_MOVE,_DEFAULT,_PRIVATE

In all the cases above, setting up the source widget, you also define which mouse button should be used to initiate and actually carry out the dragging itself. In our tool-bar example we used yet a different way to register the source widget:

Gtk::Drag.begin(
      widget=src_widget,
      targets=Gtk::TargetList.new(TBDND_TARGETS), # [["toolbar", Gtk::Drag::TARGET_SAME_APP, 100]]
      actions=TBDND_ACTION,                       # Gdk::DragContext::ACTION_ASK
      button=e.button,
      event                                       # Gdk::Event::DRAG_LEAVE
)

When you are registering a destination, you may need to provide additional flags (Gtk::Drag::DestDefaults), controlling the potential resulting action after the drop.

Gtk::Drag.dest_set(
      widget  = treeview, 
      flags   = Gtk::Drag::DestDefaults::DROP, 
      targets = TVDND_TARGETS, 
      actions = Gdk::DragContext::ACTION_ASK
)

Do not be too much concerned with all the different ways we may be using when setting up source and destination widgets. Try to emulate the examples you can find in our tutorial, they should provide enough material, to grasp the basic understanding.

Setting DnD For Widgets Without Their Own Gdk::Window

(10.2)

As already mentioned, some widgets support dnd by design, while others do not. This creates an unusual learning situation, where initially we are better of looking at the widgets that do not natively support drag-and-drop facilities, though by looking at such widgets we only can observe a partial set of the drag-and-drop features. The reason that the widgets without the native dnd support are a better learning starting point is, that their set-up procedures always reflect the 'source/destination dnd duality', namely, unlike for widgets with the dnd support, where a source or destination, can either be implicitly assumed or enabled, for widgets without the native dnd support we always have to set up a source as well as destination side widgets.

Both the source and destination widget set-up also require you to provide the definitions for the objects to be dragged, in the form of tuples called 'targets', which we discussed above in the 'Identifying the DnD Objects' paragraph.

When discussing DnD for widgets without their own Gdk::Window we should also point out that for those widgets the 'drag-data-get' and 'drag-data-received' signals are not emitted.

DnD related signals

(10.2.0.1A)

With the exclusion of the above mentioned exceptions ('drag-data-get' and 'drag-data-received' signals) for widgets that do not natively support dnd, all widgets regardless of whether they natively support the dnd, or not, respond to drag related signals defined in Gtk::Widget#Signals. For your convenience I also list these signals in the following listing:

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

As you can see the widgets without their own Gdk::Window exhibit a limited set of dnd behaviours (actions). For this reason, when you design your programs, that need dnd facilities with such widgets you need to provide your own code to handle drag signals and implement custom drag-and-drop actions. We will look closely at this in the following section revealing the promised 'button-to-label-dnd.rb'

Dragging a Button Onto a Label

(10.2.1B)

button-to-label-dnd-s1.png

This is a very simple drag-and-drop program example. We pack two widgets into a horizontal box. Originally, the button to be dragged on the left, and the label to which the button can be dragged on the right. Both widgets are adorned with the 'titles' indicating the available drag-and-drop action.

drag-document-cursor.png

Hence if you click, and drag the button widget over to the right side the drag-documentshape cursor appears (see the small image here on the right). Should you decide to release the mouse button while the 'drag-document' shape cursor' remains unchanged, the drag operation is cancelled.

ask-to-drop-document-cursor.png

When you cross over to the label side the cursor changes appropriately, namely at this point a little question mark is added to thedrag-documentshape cursor, indicating that cursor has reached the destination widget, on which the 'drop' may be performed.

The image on the left, shows how the button is moved first from left to right and then back again. Observe the cursor indicator and the title (the instructive messages) on either widget, as the button is dragged and dropped and the widgets alter their position in the main window. Obviously, the source and destination sides are switched each time the button is dragged and dropped onto the label.

You almost certainly by now are aware, that for these drag-and-drop actions neither the source button widget nor the destination label widgets offer any particular help with the drag-and-drop operations (actions). We have to provide the appropriate signal handler callbacks, and perform the swapping of the widgets' positions ourselves. However, as you will learn shortly, by registering widgets, that natively do not support dnd behaviour, as dnd source and destination widgets, you actually ask Gtk to chip in, and start providing the basic dnd services such as taking care of cursor shape during dnd operations and some other, to a user not obvious, dnd services.

We will next look at the code of our 'button to label dnd' example program, which I believe, will provide a more concrete revelations of some of the abstract dnd concepts we have been discussing up to now and finally get your feet 'dnd' wet. But first, we should get ready for a switch from procedural programming paradigm, in which all the examples so far were written, to the object oriented one in which all your future Gtk programming should really be done.


Time To Start Using Object-Oriented Programming Paradigm

(10.2.1.1)
So far in our tutorial we have mostly ignore the fact that Gtk is really an object oriented GUI system, intended to be used as such. Ruby itself is an excellent object oriented language and it would be inappropriate if we continued to ignore these facts.

From this point on, we will try to stay in the OO paradigm, and we plan that our example programs reflect this, and will from now on consistently use theBasicWindowclass, which provides all the required Gtk::Window behaviours and properties. In fact it itself is a subclass of Gtk::Window class.

In order to use it in your program examples that follow in this tutorial, you need to make sure that the directory (folder) in which 'HikiGtk'module file called 'hiki-gtk.rb' with BasicWindowclass definition resides, will be included in Ruby's load path.

Hence before you continue, take a few moments and copy the following file into a desired folder or directory on your system:

hiki-gtk.rb

module HikiGtk
  require 'gtk2'
  class BasicWindow < Gtk::Window
    def initialize(title = nil)
      super(Gtk::Window::TOPLEVEL)
      set_title(title) if title
      set_size_request(300, 200)
      border_width = 10
      signal_connect("key_press_event") do |widget, event|
        if event.state.control_mask? and event.keyval == Gdk::Keyval::GDK_q
          destroy
          true
        else
          false
        end
      end
      signal_connect("delete_event") { |widget, event| quit }
      signal_connect("destroy") { Gtk.main_quit }
    end
    def quit
      puts "DEBUG: quiting Hiki example"
      destroy
      false
    end
  end
end

The name and location of the directory into which you copy this file are arbitrary, and all you need to do in your program examples is modify the '$:' line in the listing to point to this directory. For instance if you copy this file into a folder called '/home/mary/mygtk-lib', then in all the example programs you copy from this tutorial, you should change the line$: << '~/work/HikiLib' to$: << '/home/mary/mygtk-lib'.


(Back to our 'Dragging a Button Onto a Label' discussion #1 /10.2.1 - continued-#2/)

The program starts with the widening of the Ruby load path ($:), to ensure our tutorial 'hiki-gtk.rb' class and 'hiki-dnd-dbg.rb' module are found, and with the usual preparation of the driving 'BasicWindow' class.

$: << '~/work/HikiLib'
require 'hiki-gtk.rb'
include HikiGtk
require 'hiki-dnd-dbg.rb'
include HikiDnDdbg
Notes About the DnD Debugging Module:

(10.2.1.1.1)
As you see in the above code snippet in this example here we are using an auxiliary module stored in file called 'hiki-dnd-dbg.rb'. You should copy this file into the folder, where you earlier copied the BasicWindow class file ('hiki-gtk.rb'). Do this now. Here is the file:

hiki-dnd-dbg.rb

module HikiDnDdbg

  def blow_up(targs)
    target_names=""
    line="\n\t\t"
    targs.each do |token|
      if line.length > 65
        target_names += line
        line = "\n\t\t"
      end
      line += token.name + ", "
    end
    target_names + line.chop.chop + ";\n"
  end
# NOTE:
#       Either {{ $DEBUG }} or {{ $MONITOR }} must be set to true for this 
#       method to run! If both are set {{ $MONITOR }} cancels {{ $DEBUG }}.
# ALSO:
#	If you are using Gtk::TextView as source and/or destination, you
#	may alse need to initialize {{ $text_buffer }} global variable to
#	your {{ Gtk::TextView#buffer }}.
=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
  def prn_dnd_sig_parms(sig, *parms)
    return if (!($DEBUG||=false)) && (!($MONITOR||=false))
    w=dc=x=y=d=i=t=nil
    case sig
    when /drag-begin/;		w, dc			= parms
    when /drag-data-delete/;	w, dc			= parms
    when /drag-data-get/;	w, dc, d, i, t		= parms
    when /drag-data-received/;	w, dc, x, y, d, i, t	= parms
    when /drag-drop/;		w, dc, x, y, t		= parms
    when /drag-end/;		w, dc			= parms
    when /drag-leave/;		w, dc, t		= parms
    when /drag-motion/;		w, dc, x, y, t		= parms
    end
    # -- ignore all remaining 'drag-motion' signals, but keep
    #    recording the latest x, y positions!
    if ($prev_sig||=nil) == sig && sig == 'drag-motion'
      if sig == 'drag-motion'
        $prev_x = x; $prev_y = y
      end
      return
    end
    $prev_sig = sig
    print "#{sig}"
    dots = ""
    (40 - sig.length).times {dots += "."}
    if $MONITOR		# ||= false
      msg = sig == 'drag-motion' ?
         " ... potentially many 'drag-motion' signals were caught." : " #{dots} caught."
      puts msg
      return
    else
      print ":\n"
    end
    # -- Note: 
    #       display targets only for 'drag-data-received' and 
    #       'drag-data-get' signals
    if !dc.nil?
      print "\t#{dc.class}.targets: "
      if sig == 'drag-data-received' || sig == 'drag-data-get'
        print blow_up(dc.targets)
      else
        print "\n"
      end
      print "\tdrag_context.selection.name=#{dc.selection.name}," if !dc.selection.nil?
    end
    print "\tx,y=(#{x},#{y}), "	 if !x.nil?
    if !d.nil?
      print "\n\tdata.class=#{d.class}, "
      print "\n\tdata.target.class=#{d.target.class}; "
      print "\n\tdata.target.name /atom.name/ = #{d.target.name}" #=> GTK_TREE_MODEL_ROW | tv_item |...
      print "\n\tdata.text=#{d.text.strip}, " if d.text != "" && ! d.text.nil?
    end
    print "\n\tinfo=#{i}, "	 if !i.nil?
    print "time=#{t} "		 if !t.nil?
    if $prev_x != nil
        print "\n\n ....\t( [final x,y=(#{$prev_x},#{$prev_y})] .... " +
              "remaining 'drag-motion' signals\n     \t   before final one were blocked! )\n"
       $prev_x = $prev_y = nil
    end
    puts
  end

  def show_if_widget_contains_its_own_gdk_window(w)
    answer = "#{w.class} does not contain its own Gdk::Window"
    if w.respond_to? :no_window?
      # widget.no_window? #=> false is o.k., contains Gdk::Window
      answer = "#{w.class} contains its own Gdk::Window" if ! w.no_window?
    end
    answer    
  end
end

Do not pay too much attention to the above 'HikiDnDdbg' auxiliary module. It provides rather useful drag-and-drop debugging methods, which if commented out of the example programs that use them should in no way effect user's experience, unless of course that user is a developer, that would like to observe some of the behind the scene run-time program characteristics. The only thing you should pay attention to is how we are including such auxiliary modules into our programs and developing environment.

Our 'button-to-label-dnd.rb' example program consists of a single 'DnDWindow' class which is a subclass of the 'BasicWindow' base class, i.e. the class defined in the module 'HikiGTK'. The BasicWindow base class is a Gtk::Window class, implementing the most rudimentary Gtk::Window behaviour, that any application should always implement. It sets up the top level 'delete_event' and 'destroy' widget signals, sets up the window title if, it is passed to its window constructor, and takes care of the window size and its border. Indeed, you can override all these properties and behaviours in your main (driving) window subclass you are defining.

A Note About the DnD Widgets And 'HikiDnDdbg' Module In This Example:
(10.2.1.1.2)
Note, that the 'HikiDnDdbg' module is not terribly important in this program, we could easily live without it. We included it only to add a single method, called 'show_if_widget_contains_its_own_gdk_window', to our 'DnDWindow' class, to show that neither the button nor the label widget contains its own Gdk::Window, and yet, contrary to the official Gtk API documentation, we can set these two widgets as dnd source and destination.


(Back to our 'Dragging a Button Onto a Label' discussion /10.2.1 - continued-#2/)

Our 'DnDWindow' class contains three methods. The default Ruby constructor (initialize), and three methods of our own, namely the 'set_dnd_source_widget', 'set_dnd_destination_widget', and on_drag_drop methods.

The UI for this program is initialized in the 'initialize' method, where we create both dnd source button and destination label widgets, register them as the source and the destination widget respectively, and finally pack the two into a horizontal box, which is then added to the DnDWindow, and exposed by calling 'show_all' on self, i.e. on the DnDWindow itself. Initialization is concluded by the mandatory 'Gtk::main?' call. From the dnd prospective, except the indirect source and destination dnd widget registration there is nothing new revealed in the 'initialize' method. Our interest lies within the two registration 'set_dnd_{source|destination}_widgets' methods. Both of these two methods take as arguments, beside the widget object either Gtk constants or the constants we defined at the beginning of our DnDWindow class. Following is code segment from our DnDWindow class where these constants are defined:

B2LDND_FLAGS   = Gtk::Drag::TARGET_SAME_APP
B2LDND_INFO    = 105
B2LDND_TARGETS = [["btt-2-label dnd test", B2LDND_FLAGS, B2LDND_INFO]]
B2LDND_ACTIONS = Gdk::DragContext::ACTION_ASK	           # _ASK,_COPY,_LINK,_MOVE,_DEFAULT,_PRIVATE

We already discussed how these constants are assembled above in the paragraph with the title 'Identifying the DnD Objects'. Basically, these constants are used as a convenience to help a developer synchronize the source and destination widget registration. Namely, source and destination widget must agree on the targets, i.e. objects allowed to be dragged and dropped. These objects are described in tuples called 'targets'. If you recall, a tuple consists of three items '[target, flags, info]'. The'target'parameter is a string identifying the object to be dragged. The'flags'parameter is one of the constants defined in Gtk::Drag#TargetFlags (TARGET_SAME_APP, TARGET_SAME_WIDGET, TARGET_OTHER_APP, and TARGET_OTHER_WIDGET). And the'info'element is by the application or the developer defined identification integer. Both source and destination registration methods accept a list (an array) of tuples, which means that there could be different kinds of objects that are dragged from a source (or perhaps from many different sources), as well that on a destination widget many different kinds of objects can be dropped. Whether the objects can come from different source widgets or even different applications is defined with the flag element in each tuple. Note also the '*_ACTIONS' constant, this constant is not part of the tuple, or 'targets' definition, it is usually different for source widget registration, than for registration of the destination, however interestingly enough, not for or our button and label source and destination widgets! (Note that for both sides we defined the same action, namely, Gdk::DragContext::ACTION_ASK). You can already tell, this constant is defined in Gdk::DragContext#GdkDragAction, and determines how a dragging item is handled on the either source or destination side.

'set_dnd_source_widget' instance method:

(10.2.1.1a)
As already mentioned, our DnDWindow class has three instance methods we added. First we define the 'set_dnd_source_widget' method to register our 'Drag Here' button as source widget. As you can see from the code, we do not define a signal handler and subsequently also no callback for the source widget. You will see in a moment, that in this program all the work to swap the location of source and destination widgets is performed entirely on the receiving (destination) side. All the dnd work that Gtk does for us here is managing the display, namely, taking care a proper dnd cursor icons are shown during the drag operation. When registering source we also have to provide the argument for 'start_button_mask' parameter, so Gtk knows that if that mouse button is pressed while the cursor is hovering over the source widget, it has to change the cursor icon to 'drag-document' icon. Following is the code segment with our 'set_dnd_source_widget' instance method:

def set_dnd_source_widget(widg)
  # -- Gtk::Drag.source_set sets up a widget so that GTK+ will start a drag
  #    operation when the user clicks and drags on the widget. The widget
  #    must have its own gdk window. (Well, Gtk::Button widget does not as
  #    the following DEBUG line reveals, nevertheless, we can set a button
  #    as the source dnd widget):
  puts "DEBUG: #{show_if_widget_contains_its_own_gdk_window(widg)}"
  Gtk::Drag.source_set(
    widget=widg, 
    start_button_mask=Gdk::Window::BUTTON1_MASK,
    targets=B2LDND_TARGETS,
    actions=B2LDND_ACTIONS
  )
end

The only thing this method does is registers the button widget. We really did not have to wrap this registration into a method of its own, however, it provides us with better and cleaner ways to add new testing and debugging features, as well as a pack away registration of any additional callbacks, we may wish to implement for the source side.

(Note also our 'debug-line' the reason we 'required' our 'HikiDnDdbg' module.)

It is important to realize that in this program source widget represents both the location from which we start dragging the object, as well as the object we are dragging. On the other hand our destination here, the label, seems more natural - namely it does represent the location to which we are dragging our button object. However, as you will see next, this destination object is removed to make place for the button, we drop on it. (By now you should know that label widget is not a container, and hence it may not hold any other widget, except its text, and optionally an image.)

'set_dnd_destination_widget' instance method:

(10.2.1.1b)
In the 'set_dnd_destination_widget' method we undoubtedly find the gist of this dnd example. But let's first talk about the basics, namely, about setting up the destination, i.e. (Gtk::Drag.dest_set). The firs thing to notice is that beside the 'flags' item in the tuple, we have an extra 'flags' argument, that is not a part of a 'targets' tuple, namely, Gtk::Drag::DEST_DEFAULT_ALL. These flags are defined in Gtk::Drag#DestDefaults. Other thing you should see is, that 'targets' and 'actions' arguments are the same as those used when we registered the source widget. Particularly interesting is the fact, that actions for both source and destination here are also the same, though usually when mixing source and destination widgets with and without native dnd support they differ.

As before note the 'debug-line' tese two lines in source set-up method and here are the only reasons we 'required' our 'HikiDnDdbg' module. However, they serve their purpose, by making a point that widgets without their own Gdk::Window can, indeed, be set up as dnd source and destination widgets.

But the most important or interesting part on destination side is the signal handler for the 'drag-drop' signal. We have wrapped it into its own instance method, called 'on_drag_drop'. We'll look at it next, but first lets see the code segment with our 'set_dnd_destination_widget' instance method:

def set_dnd_destination_widget(dest_widg)
  puts "DEBUG: #{show_if_widget_contains_its_own_gdk_window(dest_widg)}"
  Gtk::Drag.dest_set(
    widget=dest_widg,
    flags=Gtk::Drag::DEST_DEFAULT_ALL,
    targets=B2LDND_TARGETS,    # [["btt-2-label dnd test", Gtk::Drag::TARGET_SAME_APP, 105]]
    actions=B2LDND_ACTIONS	    # Gdk::DragContext::ACTION_ASK
  )
  dest_widg.signal_connect('drag-drop') { |w, e| on_drag_drop(w, e) }
end

We have already discussed the fact that in this program the only dnd related processing is happening at the destination side. Normally, the widgets with Gdk::Window, i.e. with native dnd support at the time the drop occurs emit the 'drag-data-received' signal. However, since label widgets do not exhibit the behaviour of widgets with native dnd support, and we can not count on any built-in dnd actions to be available, also the signals tied to these intrinsic dnd actions are not emitted. Instead we have to monitor for the'drag-drop'signal. Usually, we would write a signal handler in the code block attached to the 'signal_connect' method, however, wrapping this code in an instance method with a descriptive name identifying the signal, may prove to be more readable, and as we've said before, easier to do localized experiments, testing and debugging, especially when the documentation of the API is rather scarce, as is currently the case with the Gtk drag-and-drop explanations.

'on_drag_drop' instance method:

(10.2.1.1c)
This method is not at all a complicated one and is easy to understand, though, it introduces an interesting concept of removing widgets from a layout container and rearranging the container afterwards, not often seen in this tutorial thus far. We manage the rearrangement with, for this purpose introduced instance variable '@toggled', holding a Boolean value which helps us keeping the record of two distinct source/destination widget location arrangements.

def on_drag_drop(w, e)
  @hbox.remove(@label)
  @hbox.remove(@button)
  if @toggled
    @hbox.pack_start(@button,  true,  true, 0)	# w, expand, fill, padding
    @hbox.pack_start(@label,   true,  true, 0)
  else
    @hbox.pack_start(@label,   true,  true, 0)
    @hbox.pack_start(@button,  true,  true, 0)	# w, expand, fill, padding
  end
  @toggled = !@toggled
  show_all
end

Though, on my system it is not needed, just to be on the safe side, after rearranging GUI container widgets it is wise to call show_all on the rearranged or on the parent container, as is the case here where 'show_all' call is actually identical to 'self.show_all' - self being the DnDWindow object.

Let's look at the complete program now:

Complete 'button-to-label-dnd.rb' Program Listing

(10.2.1.2)


'button-to-label-dnd' Example Download Notes:
(10.2.1.2.1)
Finally you are going to copy the example program itself onto your system. Do not forget, you can copy each example program into a different directory (folder), you only need to have all the module and class files which you 'require' in the example programs (unless specifically instructed otherwise in the example documentation) in the same directory (as discussed in section # (10.2.1.1) 'Time To Start Using Object-Oriented Programming Paradigm').

button-to-label-dnd.rb

#!/usr/bin/env ruby
$: << '~/work/HikiLib'
require 'hiki-gtk.rb'
include HikiGtk
require 'hiki-dnd-dbg.rb'
include HikiDnDdbg
class DnDWindow < BasicWindow
  # B2LDND_FLAGS   = TARGET_{SAME_APP, SAME_WIDGET, OTHER_APP, OTHER_WIDGET}
  B2LDND_FLAGS   = Gtk::Drag::TARGET_SAME_APP          # tested with: _SAME_WIDGET,_OTHER_APP
  B2LDND_INFO    = 105
  B2LDND_TARGETS = [["btt-2-label dnd test", B2LDND_FLAGS, B2LDND_INFO]]
  B2LDND_ACTIONS = Gdk::DragContext::ACTION_ASK	       # _ASK,_COPY,_LINK,_MOVE,_DEFAULT,_PRIVATE

  def initialize(title)
    super
    resizable = true
    set_size_request(275, -1)
    @toggled = false
    @button = Gtk::Button.new("Drag Here")
    @label  = Gtk::Label.new("Drop Here")
    set_dnd_source_widget(@button)
    set_dnd_destination_widget(@label)
    @hbox = Gtk::HBox.new(false, 0)		# homogeneous, spacing
    @hbox.pack_start(@button,  true,  true, 0)	# w, expand, fill, padding
    @hbox.pack_start(@label,   true,  true, 0)
    add(@hbox)
    show_all
    Gtk.main
  end
private
  ## ----------
  ## DnD Source
  ## ----------
  def set_dnd_source_widget(widg)

    # -- Gtk::Drag.source_set sets up a widget so that GTK+ will start a drag
    #    operation when the user clicks and drags on the widget. The widget
    #    must have its own gdk window. (Well, Gtk::Button widget does not as
    #    the following DEBUG line reveals, nevertheless, we can set a button
    #    as the source dnd widget):
    puts "DEBUG: #{show_if_widget_contains_its_own_gdk_window(widg)}"

    Gtk::Drag.source_set(
      widget=widg, 
      start_button_mask=Gdk::Window::BUTTON1_MASK,
      targets=B2LDND_TARGETS,
      actions=B2LDND_ACTIONS
    )
  end
  ## ---------------
  ## DnD Destination
  ## ---------------
  def set_dnd_destination_widget(dest_widg)
    puts "DEBUG: #{show_if_widget_contains_its_own_gdk_window(dest_widg)}"
    Gtk::Drag.dest_set(
      widget=dest_widg,
      flags=Gtk::Drag::DEST_DEFAULT_ALL,    # ..... works (ok)
                                            # DEST_DEFAULT_MOTION, # ..... works (ok)
                                            # DEST_DEFAULT_DROP,   # ..... doesn't work
      targets=B2LDND_TARGETS,	# [["btt-2-label dnd test", Gtk::Drag::TARGET_SAME_APP, 105]]
      actions=B2LDND_ACTIONS	# Gdk::DragContext::ACTION_ASK
    )
    dest_widg.signal_connect('drag-drop') { |w, e| on_drag_drop(w, e) }
  end
  def on_drag_drop(w, e)
    @hbox.remove(@label)
    @hbox.remove(@button)
    if @toggled
      @hbox.pack_start(@button,  true,  true, 0)	# w, expand, fill, padding
      @hbox.pack_start(@label,   true,  true, 0)
    else
      @hbox.pack_start(@label,   true,  true, 0)
      @hbox.pack_start(@button,  true,  true, 0)	# w, expand, fill, padding
    end
    @toggled = !@toggled
    show_all
  end
end

DnDWindow.new("Dragging a Button Onto a Label")

Note, all the instance methods in our DnDWindow class are made private. That is because all of them are invoked internally, and nobody should be allowed to call them explicitly on DnDWindow object. If you are not familiar with OOP, do not worry about the 'private' directive too much, you can omit it in your programs and all will work just fine, the only drawback, will be that your code will not conform to strict OO conventions, and will provide ways to abuse your code, should you deploy it to the world at large.


Another DnD example we have for widgets without Gdk::Window is the one from chapter 9, section 9.6.2.2 (Dragging a Toolbar Within a Window). Let's look at it again here written in object-oriented programming paradigm. This time, we have sufficiently acquainted ourselves with the basics of 'drag-and-drop' system to also look at its design, code and the Gtk dnd features on which it is built.


Dragging a Toolbar Within a Window (oo version)

(10.2.2)

Let's first get out of the way the 'oo stuff'. There is nothing new in the preamble (the first three lines after '$:' line) here. With the exception of main window class name, everything we explained above under the title 'Time To Start Using Object-Oriented Programming Paradigm' still holds here. Besides we are not extending our driving (main) class with the methods from our debugging module. One thing that needs our attention is the choice of the instance variables. As you see, we choose to use only two instance variables (@entry and @toolbar). All other variables in which we hold references to the needed GUI widgets are stored in local variables. The reasoning behind this choice is the ephemeral nature of source and destination widgets on one hand, and on the other the need to access variables holding references to Gtk widgets from other methods, namely, only entry and toolbar widgets are used elsewhere. The ephemeral nature of source and destination widgets can be realized, when observing that after a user drags the toolbar from its original source location (frame), and drops it on current destination (frame), the two frames swap their source and destination designation, so the widget that is registered as source in the initialize method after every drag-and-drop operation alters its source or destination association! In other words, the original source frame, after the first drop becomes the new destination, and the frame that used to be the destination before the drop now becomes the new source widget. Notice the difference between either of the two toolbar programs and the above 'button-to-label-dnd.rb' example. Here, the object we drag is the toolbar and the source and destination frames stay on their locations only they swap their source/destination designations. The point here is that an object being dragged may actually be a completely different widget than are the source and destination widgets, which most likely are container widgets holding the 'draggable' items. On the other hand, as we saw above in 'button-to-label-dnd.rb' example, the object we drag is simultaneously also the source widget. There are many different scenarios we can come up with, for instance multiple source and destinations, as well as multiple objects to be dragged between them. Needless to say that we have to implement the actions that take place after their respective (drags) and particularly 'drops' for all widgets without native dnd support, and that different situations or dnd scenarios require different implementations of these actions. We can see this the two examples presented here in section 10.2.

'dnd-toolbar-oo.rb' program example notes:

(10.2.2.1)

If you have forgotten the details about this menu bar, revisit the paragraphs in section 9.6.1 under the title  ((<Menus And Sub-menus As Toolbar Items|tut-gtk2-mnstbs-tb#Menus And Sub-menus As Toolbar Items>)).

{{image_left("gnu-baby-32x32.jpg")}}{{image_left("gnu-head-42x42.jpg")}} Just like in chapter 9 you will need to copy the two images here on the left into your working directory, from which you plan to run our next toolbar example program. The image files should be named: 'gnu-baby-32x32.jpg' and 'gnu-head-42x42.jpg' respectively.

dnd-toolbar-colage-w-frame-placeholders.png

Let's look at the program before we comment the relevant parts of it in the light of drag-and-drop metaphor:

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


dnd-toolbar-oo.rb

#!/usr/bin/env ruby
                         # change the following line to suite your directory arrangements:
$: << '~/work/HikiLib'   # e.g.  {{ $: << '~/work/HikiLib' }} ==> {{ $: << '/home/mary/mygtk-lib' }}
require 'hiki-gtk.rb'
include HikiGtk

class DndTbWindow < BasicWindow

  TBDND_ACTIONS = actions=Gdk::DragContext::ACTION_ASK  # _ASK,_COPY,_LINK,_MOVE,_DEFAULT,_PRIVATE
  TBDND_TARGETS = [["toolbar", Gtk::Drag::TARGET_SAME_APP, 100]] # [[target1,flags1,info1],[target2,flags2,info2],...]

  def initialize(title)
    super

    resizable = true
    set_size_request(450, 400)

    @entry   = Gtk::Entry.new
    @entry.set_size_request(100, -1)

    @toolbar = Gtk::Toolbar.new
    vtbframe = Gtk::Frame.new.set_size_request(10, -1)
    htbframe = Gtk::Frame.new.set_size_request(200, 10)

    vbox = Gtk::VBox.new(false, 0)  # (homogeneous, spacing)
    hbox = Gtk::HBox.new(false, 0)

    vbox.pack_start(htbframe,  false,  false, 0)  # (child, expand, fill,  padding)
    vbox.pack_start(hbox,      true,   true,  0)
    hbox.pack_start(vtbframe,  false,  false, 0)

    workspace = Gtk::VBox.new(false, 5)
    workspace.pack_start(@entry,                    false, false, 0)
    workspace.pack_start(Gtk::Label.new("D-e-m-o"), true,  true,  0)
    hbox.pack_start(workspace,                      true,  true,  0)

    create_toolbar

    @toolbar.set_size_request(300, -1)
    htbframe.set_size_request(300, -1)
    htbframe.add(@toolbar)

    set_dnd_source_frame_widget(htbframe)
    set_dnd_destination_frame_widget(htbframe, vtbframe)  # (source_fr, dest_fr)

    add(vbox)
    show_all
    Gtk.main
  end

private

  # Create a toolbar with Cut, Copy, Paste and Select All
  # toolbar items.
  def create_toolbar
    cut       = Gtk::ToolButton.new(Gtk::Stock::CUT)
    copy      = Gtk::ToolButton.new(Gtk::Stock::COPY)
    paste     = Gtk::ToolButton.new(Gtk::Stock::PASTE)
    selectall = Gtk::ToolButton.new(Gtk::Stock::SELECT_ALL)

    # -- Depending on the status in the entry field cut, copy, paste and 
    # -- select menu items should be made sensitive or insensitive, however
    # -- that would introduce too much overhead to our example program here.

    #  cut.sensitive = false
    #  copy.sensitive = false
    #  paste.sensitive = false
    #  selectall.sensitive = false

    separator = Gtk::SeparatorToolItem.new
    my_tb_menu = Gtk::MenuToolButton.new(Gtk::Stock::PREFERENCES)

    # To be sure file exists use more elaborate check making the bixbuf
    pixbuf = get_pixbuf_if_file_exists("gnu-baby-32x32.jpg")
    my_stuff  = Gtk::ToolButton.new(
        icon_widget=Gtk::Image.new(pixbuf), label="My Stuff")
    # or if you are sure you you really have the image file:
    my_stuff1 = Gtk::ToolButton.new(
        icon_widget=Gtk::Image.new("gnu-head-42x42.jpg"), label="My Stuff-1")

    # -- Provide additional drag-sensitive spot on the toolbar ----
    #    However the original intent (action) of this button is lost!!!
    #my_stuff.use_drag_window = true

    # use Gtk::Widget#tooltip_text= instead of deprecated Gtk::Toolbar 
    # methods utilizing deprecated Gtk::Tooltips class!
    my_tb_menu.tooltip_text = "Demonstrate that menus with submenus work."
    cut.tooltip_text        = "Cut saves the selected text in the clipboard,\n" +
                              "and removes it from the editable widget," 
    copy.tooltip_text       = "Copy saves the selected text in the clipboard."
    paste.tooltip_text      = "Paste retrieves last text saved in the clipboard\n" +
                              "and places it at the cursor position in the edit field."
    selectall.tooltip_text  = "Select all the text in the edit field."

    @toolbar.show_arrow = true
    @toolbar.toolbar_style = Gtk::Toolbar::Style::BOTH

    @toolbar.insert(0, my_tb_menu)
    @toolbar.insert(1, my_stuff)
    @toolbar.insert(2, cut)
    @toolbar.insert(3, copy)
    @toolbar.insert(4, paste)
    @toolbar.insert(5, separator)
    @toolbar.insert(6, selectall)
    @toolbar.insert(7, my_stuff1)

    cut.signal_connect('clicked')       { @entry.cut_clipboard; p "Cut" }
    copy.signal_connect('clicked')      { @entry.copy_clipboard; p "Copy" }
    paste.signal_connect('clicked')     { @entry.paste_clipboard; p "Paste" }
    # Select all of the text in the editable (Gtk::Editable#select_region)
    selectall.signal_connect('clicked') { @entry.select_region(0, -1); p "Sel. All" }

    my_stuff.signal_connect('clicked')  { p "My Stuff selected." }
    my_stuff1.signal_connect('clicked') { p "My Stuff-1 selected." }

    menutearoff = Gtk::TearoffMenuItem.new
    testi1 = Gtk::MenuItem.new("Test Item #1")
    testi2 = Gtk::MenuItem.new("Test Item #2")
    testi3 = Gtk::MenuItem.new("Test Item #3")
    langi  = Gtk::MenuItem.new("Languages")
    testi1.signal_connect('activate') { |w| puts "w=#{w.class}:Test Item-1 selected" }
    testi2.signal_connect('activate') { |w| puts "w=#{w.class}:Test Item-2 selected" }
    testi3.signal_connect('activate') { |w| puts "w=#{w.class}:Test Item-3 selected" }

    # Create Test Menu
    testmenu = Gtk::Menu.new
    testmenu.append(menutearoff)
    testmenu.append(testi1)
    testmenu.append(testi2)
    testmenu.append(testi3)
    testmenu.append(langi)

    langmenu = Gtk::Menu.new
    langi.submenu = langmenu

    english = Gtk::MenuItem.new("English")
    french  = Gtk::MenuItem.new("French")
    german  = Gtk::MenuItem.new("German")
    russian = Gtk::MenuItem.new("Russian")
    italian = Gtk::MenuItem.new("Italian")
    langmenu.append(english)
    langmenu.append(french)
    langmenu.append(german)
    langmenu.append(russian)
    langmenu.append(italian)

    english.signal_connect('activate') { |w| puts "w=#{w.class}:English selected" }
    french.signal_connect('activate')  { |w| puts "w=#{w.class}:French selected" }
    german.signal_connect('activate')  { |w| puts "w=#{w.class}:German selected" }
    russian.signal_connect('activate') { |w| puts "w=#{w.class}:Russian selected" }
    italian.signal_connect('activate') { |w| puts "w=#{w.class}:Italian selected" }

    testmenu.show_all
    my_tb_menu.menu = testmenu
  end

  # ( Auxiliary method used in {{ create_toolbar }} )
  # Turn image file into pixbuf, and return nil in case of file error.
  def get_pixbuf_if_file_exists(file)
    begin
      pixbuf = Gdk::Pixbuf.new(file)
      rescue GLib::FileError => err
        print "I/O ERROR (%s): %s\n" % [err.class, err]
        pixbuf = nil
    end
    pixbuf
  end

  ## ----------
  ## DnD Source
  ## ----------
  def set_dnd_source_frame_widget(widg)

    ### ---------------------------------------------------------------------(start)--
    ### NOTE: -- the following is not really necessary, if commented out all 
    ###          still works fine!
##  Gtk::Drag.dest_unset(widg)     # cancel earlier dnd destibation setting
    ### ---------------------------------------------------------------------(end)----

    widg.add_events(Gdk::Event::BUTTON_PRESS_MASK)
    widg.signal_connect('button-press-event') do |w, e|

      Gtk::Drag.begin(
  	widget=w,
  	targets=Gtk::TargetList.new(TBDND_TARGETS), # [["toolbar", Gtk::Drag::TARGET_SAME_APP, 100]]
  	actions=TBDND_ACTIONS,                      # Gdk::DragContext::ACTION_ASK
  	button=e.button,
  	event=e                      # Gdk::Event::DRAG_LEAVE
      )
    end
  end

  ## ---------------
  ## DnD Destination
  ## ---------------
  def set_dnd_destination_frame_widget(src_widg, dest_widg)

    Gtk::Drag.dest_set(
  	widget=dest_widg,
  	flags=Gtk::Drag::DEST_DEFAULT_MOTION,
  	targets=TBDND_TARGETS,	# [["toolbar", Gtk::Drag::TARGET_SAME_APP, 100]]
  	actions=TBDND_ACTIONS,	# Gdk::DragContext::ACTION_ASK
    )

    dest_widg.signal_connect('drag-drop') do |w, e|

      src_widg.remove(@toolbar)

      if @toolbar.orientation == Gtk::Orientation::HORIZONTAL
        dest_widg.set_size_request(80, 350)
        new_orientation = Gtk::Orientation::VERTICAL
        src_widg.set_size_request(350, 10) # x,y ... reduce size of horizontal old source
      else
        dest_widg.set_size_request(350, -1)
        new_orientation = Gtk::Orientation::HORIZONTAL
        src_widg.set_size_request(10, 350) # x,y ... reduce size of vertical old source
      end

      # src_widg.shadow_type = Gtk::SHADOW_NONE        # Gtk::SHADOW_IN
##      src_widg.parent.show_all

      @toolbar.orientation = new_orientation

      dest_widg.add(@toolbar)
##      dest_widg.show_all

      Gtk::Drag.source_unset(dest_widg)	# cancel earlier dnd source setting

      # NOTE: by now source and destination are already reversed.
      #       The next reverse will allow next dnd to start all
      #       over again like nothing ever happened.
      reverse_dnd_source_n_destination(src_widg, dest_widg)
    end
  end

  # Auxiliary method used in {{ set_dnd_destination_frame_widget }}.
  def reverse_dnd_source_n_destination(src_widg, dest_widg)
    set_dnd_source_frame_widget(dest_widg)
    set_dnd_destination_frame_widget(dest_widg, src_widg)
  end

end


DndTbWindow.new("Drag-gable Toolbox")

Let me repeat that the bulkiest of our DndTbWindow class instance methods, called 'create_toolbar' is here the list important. Those who believe they should understand it better, must have missed or forgotten our discussion and study of menus, toolbars and menu items in chapter 9, entitled 'Menus and Toolbars'. Toolbar here for us, is just a widget, an object (or a black box, if you must), we wish to drag from one position to another, and back, if so desired. Its internals or the details about how it's built are of no interest to us here!

What we are interested in, is the overall design or architecture of the top window encapsulated in DndTbWindow class. You can learn all about the layout of this window by reading its 'initialize' method, focusing on Gtk widgets built and gathered in the hierarchy of containers, and for the moment ignoring the drag-and-drop related methods, such as 'set_dnd_source_frame_widget' and 'set_dnd_destination_frame_widget'. We'll talk about these only after we understand the structure and design of the top window.

In summary the main window consists of two parts, the edge frames (on the top, and on the left side of the main window), and the working area in the middle between the two frames. The two frames are the placeholders for our toolbar. Originally the top frame contains the toolbar. When the toolbar is dragged from its original position (frame) onto the other currently empty frame, we have to remove the toolbar from its current (source) frame and place it into the other empty (destination) frame. In this process we also have to shrink the vacated frame and indeed, change the toolbar orientation and size parameters accordingly. Of course we could prepare this window layout, including the toolbar in either position, without the knowledge of how the drag-and-drop features are implemented, and let a software engineer next-door implement the dnd features. But what would be the point ?), actually, the point is just to illustrate the two step approach here. After we have our GUI design or window infrastructure established we can start focusing on drag-and-drop issues.


Preparing DnD Environment In Toolbar Drag Program(s)

(10.2.2.2)

First dnd related thing we do in our DndTbWindow class is define the our toolbar as the draggable object in accordance with Gtk::Drag module conventions which we discussed at the beginning of this article and in particular in section 10.1.1 called 'Identifying the DnD Objects' and the subsequent section 10.1.2 'The DnD Source And Destination Objects':

TBDND_ACTIONS = actions=Gdk::DragContext::ACTION_ASK  # _ASK,_COPY,_LINK,_MOVE,_DEFAULT,_PRIVATE
TBDND_TARGETS = [["toolbar", Gtk::Drag::TARGET_SAME_APP, 100]]  # [[target1, flags1, info1], [target2, flags2, info2], ...]

The first element in the tuple (the tuple being the first element in the array of arrays packed here into the into the TBDND_TARGETS constant), 'target', is an arbitrary string, here, "toolbar". Its value could be anything you like. The second tuple element, 'flags' declares that the object to be dragged we are specifying, can be dragged only within its original application. And the last tuple's element, 'info', is an arbitrary identification integer (we chose 100). We are defining a single target (draggable) object, so there is only one (single) tuple present in the array of targets, stored in 'TBDND_TARGETS' constant which, and this is important, should be used for both source and destination widgets registration.

Just like we have observed in our previous (button-to-label-dnd.rb) example, the '*_ACTIONS' constant is not part of the tuple, or 'targets' definition, it is usually different for source widget registration, than for registration of the destination, however not for or our button and label nor toolbar's frame source and destination widgets! Just like before, also here, we define the same action for both source and destination sides namely, the Gdk::DragContext::ACTION_ASK. (This constant is defined in Gdk::DragContext#GdkDragAction).

Setting Source And Destination Widgets In Toolbar Drag Program(s)

(10.2.2.3)

Not surprisingly, from the drag-and-drop processing perspective the most important methods in our DndTbWindow class are the 'set_dnd_source_frame_widget' and 'set_dnd_destination_frame_widget'. With the exception of a sneaky pseudo recursive call from the 'reverse_dnd_source_n_destination' method at the time'drag-drop'signal is emitted and caught by the destination frame, the two 'set_dnd_*' methods are both called only from the initialize method:

set_dnd_source_frame_widget(htbframe)
set_dnd_destination_frame_widget(htbframe, vtbframe)  # (source_fr, dest_fr)
'set_dnd_source_frame_widget(htbframe)' method

(10.2.2.3.1)
The 'set_dnd_source_frame_widget(htbframe)' method is straight forward, nevertheless it contains a few perks that deserve special attention.

In this method we register our source widget (frame) as a listener or the signal handler for 'button-press-event' signals (remember the frames normally do not respond to signals), so we first have to add (register) event/signal for our toolbar frame, and then register this frame's callback proc (or block) to respond to emissions of the 'button-press-event' signals. When and if the 'button-press-event' occurs while cursor is hovering over the frame, we make this signal handler to finally register the widget as the dnd source. The perk is that we can not use the regular Gtk::Drag.source_set method to set up our source widget (frame), as we did in our 'button-to-label-dnd.rb' example.

widg.signal_connect('button-press-event') do |w, e|
  Gtk::Drag.begin(
    widget=w,
    targets=Gtk::TargetList.new(TBDND_TARGETS), # [["toolbar", Gtk::Drag::TARGET_SAME_APP, 100]]
    actions=TBDND_ACTIONS,                      # Gdk::DragContext::ACTION_ASK
    button=e.button,
    event=e                      # Gdk::Event::DRAG_LEAVE
  )
end
Note:

The source dnd widget in our 'dnd-toolbar*.rb' programs does not respond to Gtk::Widget's 'drag-*'signals at all, in fact these signals do not even provide button, and events parameters, we need to register a dnd source widget!

dest_widg.signal_connect('drag-drop') do |w, e|

Lastly, we use source widget (placeholder frame) as an argument (htbframe=/horizontal toolbar frame/) when we invoke the 'set_dnd_source_frame_widget (htbframe)' instance method, or as a parameter in this method definition, rather than an instance variable. The same is true for 'set_dnd_destination_frame_widget (htbframe, vtbframe)' instance method and its arguments and parameters. But the explanation of this should wait until you see, that both of these two 'set_dnd_*' methods are also called in a pseudo recursive call when the'drag-drop'signal is emitted and caught by the destination frame. And this (setting and managing the destination frame) is what we'll look at next.

'set_dnd_destination_frame_widget(htbframe, vtbframe)' method

(10.2.2.3.2)
At first glance the odd thing about this method may be the fact that we receive as parameters both source and destination frames. This will only become clear after we study the destination widget's (frame's) signal-handler. Indeed, for this we first need to set (register) the destination widget, i.e. our destination frame. When called the first time, from initialize method, this frame is the vertical and empty one on the left side of the window. For this registration we use the regular Gtk::Drag.dest_set method.

Gtk::Drag.dest_set(
   widget=dest_widg,
   flags=Gtk::Drag::DEST_DEFAULT_MOTION,
   targets=TBDND_TARGETS,	# [["toolbar", Gtk::Drag::TARGET_SAME_APP, 100]]
   actions=TBDND_ACTIONS,	# Gdk::DragContext::ACTION_ASK
)

There is nothing new about this method. We've seen it already in our 'button-to-label-dnd.rb' program and the similarity is obvious. The difference is minor, namely, in the tuple inside the array in the 'targets' variable the 'target' string and the 'info' integer are different - though these values are more the formality rather than strict settings (we already said you can set them to anything you like, providing you use them consistently for both, your source, and the destination, in each application). Another similarity with the previous "button-to-label-dnd" program is the use of the same actions constant. In fact you should notice also the similarity, how the actions (signal-handlers as well as signals themselves) are handled.

Despite, the differences between the algorithms that handle actual placing and vacating of the source widget from source location to the destination location in the two programs, the underlying concepts including which signals drive the actions are identical. In both cases we do not care about signals on either source side (DnD system takes care of that, namely, managing the cursor appearance), and in both cases on destination side, we are listening for the same ('drag-drop') signal, which triggers albeit different, but logically very similar GUI rearrangement actions, which, by the way, in both cases we have to implement ourselves (this is why we say that widgets without the Gdk::Window are widgets without "native DnD support").

The astute readers may have already noticed, it is not entirely true, that for widgets without "native DnD support" we get no help at all from Gtk system during DnD operations, and after the drop occurs. When we register either source or destination widgets, Gtk assumes certain responsibilities, such as monitoring the cursor and adjusting its appearance, and most of all, monitoring user's mouse movements and actions to which the Gtk reacts by emitting appropriate signals at right time and place, as well as driving the graphical presentation of either completions or premature terminations of the dragging actions. By now, it does not come to us as a surprise anymore that on the destination side the widgets without their own Gdk::Window respond to'drag-drop'signal. Each time a user drops the the dragged item on a destination widget this signal wakes up a callback, prepared by a GUI developer, on the destination widget. In the code, this is ensured, and defined with the 'signal_connect' call.

In this program, when the 'drag-drop' signal is caught by the destination widget we have to remove the toolbar from its current (source) widget or frame, change its orientation and size properties, add (install) it to/in the new designation, i.e. current destination frame, and finally reverse the source and destination associations for newly arranged destination and source frames.

At this point the current destination frame, which just got populated with the dropped toolbar, has to cease acting as the destination widget, and needs to register as the new dnd source. The current source and also just vacated frame, on the other hand, has to register as the new destination widget, and the new listener for the possible returning'drag-drop'signal.

All this is accomplished in the 'drag-drop' signal-handler connected to the current destination widget (frame), where the GUI widgets are rearranged or repositioned and finalized in the the auxiliary 'reverse_dnd_source_n_destination' method, in particular in what we earlier announced as a pseudo recursive call, where the newly positioned widgets are reassigned their source and destination designations, as well as the new destination is re-registered as the new listener for the possible returning 'drag-drop' signal.

(10.2.2.3.2.1)

Why pseudo recursive call?

If you look into the 'reverse_dnd_source_n_destination' method, which, upon catching the 'drag-drop' signal, is called last in the signal-handler attached to the current destination frame inside the 'set_dnd_destination_frame_widget' method, you will discover that in it (in the 'reverse_dnd_source_n_destination' method) the 'set_dnd_destination_frame_widget' method is called again, i.e. with the help of 'reverse_dnd_source_n_destination' method, the 'set_dnd_destination_frame_widget' method calls itself:

# Auxiliary method used in {{ set_dnd_destination_frame_widget }}.
def reverse_dnd_source_n_destination(src_widg, dest_widg)
  set_dnd_source_frame_widget(dest_widg)
  set_dnd_destination_frame_widget(dest_widg, src_widg)
end

The above call from within the 'set_dnd_destination_frame_widget' method would be recursive if the requirement that each time the 'set_dnd_destination_frame_widget' method is called also the 'drag-drop' signal was emitted. This indeed is not the case, since we have just responded to the initial 'drag-drop' event, and are still in the process of turning the two source and destination frame (widget) associations around, and two consecutive drops, one after another, are out of the question. There's no way, any user can be that quick;)

Finally, after you have seen, the temporary nature of source and destination widgets (frames) in either of our 'dnd-toolbar*.rb' programs, you may better understand the reasons we use source and destination widgets as arguments to these instance methods, rather than instance variables. Anyway, this after all, is more a question of style than substance.