Drag and drop are typical actions users have been spoiled with. In my use of Gtk+, this was one of the last things I've learned to utilize in programs.
This lesson will demonstrate how to enable drag and drop in a program.
There are basic steps which should always be present for drag and drop to work correct. Before we start, lets get the lingo right to remove any confusion.
The item which we drag is called the drag source. This item is dragged and also drag able.
The item onto which we drop is called the drag destination. This item receives dragged items and also drop able.
The drag icon is the icon displayed as soon as you start to drag a drag source.
Each item which will be dragged needs to declare itself as a drag source.
This is accomplished with the 'drag_source_set'
method of the Gtk2::Widget
class.
Each item which will receive dragged items needs to declare itself as a drag destination.
This is accomplished with the 'drag_dest_set'
method of the Gtk2::Widget
class.
An item can be a drag source and destination at the same time.
If you want to change the drag icon, connect a callback to the 'drag-begin'
signal of the drag source. This callback is then used to set up the drag icon. You use the 'drag_source_set_icon_pixbuf'
method of the Gtk2::Widget
class for this.
When a drag source is dropped onto a valid drag destination, data must flow between them. Two signals and their callbacks are used to accomplish this. First the 'drag-data-get'
signal will trigger on the drag source.
After this signal, the 'drag-data-received'
signal will trigger on the drag destination.
Callbacks need to be connected to both these signals in order for data to flow between the source and destination widgets. The one callback will set up the data, and the other will extract the data.
The above four (or five if you like cosmetics) checks is all you ever need to remember for the typical drag and drop operations.
There are two data types used in drag and drop operations which can be strange and obscure on first encounter. We discuss them here so you may recognize them in the sample program later on.
Gtk2::TargetEntry
The 'drag_source_set'
and 'drag_dest_set'
methods are used to enable a widget to be a drag source and a drag destination respectively.
$widget->drag_source_set ($start_button_mask, $actions, ...) * $start_button_mask (Gtk2::Gdk::ModifierType) * $actions (Gtk2::Gdk::DragAction) * ... (list) of Gtk2::TargetEntry's
Gtk2::Gdk::ModifierType
and Gtk2::Gdk::DragAction
are explained on the POD of Gtk2::Widget
and we do not want to duplicate documentation here.
The Gtk2::TargetEntry
data structure represents a single type of data which can be supplied or received during drag-and-drop.
# as a HASH $target_entry = { target => 'text/plain', # some string representing the drag type flags => [], # Gtk2::TargetFlags info => 42, # some app-defined integer identifier };
It is used as an argument to the 'drag_source_set'
and 'drag_dest_set'
methods.
The 'drag_source_set'
usually contains only one Gtk2::TargetEntry
, but the 'drag_dest_set'
can contain an array of them, each representing a source it is willing to accept drag data from.
#Create a target table to receive drops my @target_table = ( {'target' => 'STRING', 'flags' => [], 'info' => ID_ICONVIEW }, {'target' => "text/uri-list",'flags' => [], 'info' => ID_URI }, {'target' => "text/plain", 'flags' => [], 'info' => ID_LABEL }, ); #make this the drag destination (drop) for various drag sources $slist->drag_dest_set('all', ['copy'], @target_table);
If you want to receive dropped data from the file browser (eg Nautilus), you need to specify a target as "text/uri-list". When you drop data from Nautilus onto a drag destination, it receives a list of newline separated URIs each representing a dragged item.
Not going into to much detail, the usual values will be either 'text/plain' or 'STRING' to transfer text between a drag source and destination. The end results of both are the same. The one is a MIME type, the other is a If you want to transfer data other than plain text during drag and drop, you can specify another type of To find out more about the pre-defined |
The flags value is empty most of the time.
The 'info' is an integer value that will represent the source of the data which is send or received. This value will be passed on to the 'drag-data-get'
and 'drag-data-received'
callbacks. This will enable you to differentiate between the sources of the drag and drop action.
It makes your program more readable if you assign constants to the integer values. |
Gtk2::SelectionData
When a drag source is dropped onto a drag destination you need to first set up the data of the drag source, and then extract that data on the drag destination.
A typical signal handler for the drag source's 'drag-data-get'
signal will look like this:
sub source_drag_data_get { #--------------------------------------------------- #This sets up the data of the drag source. It is --- #required before the 'drag-data-received' event ---- #fires which can be used to extract this data ------ #--------------------------------------------------- my ($widget, $context, $data, $info, $time,$string) = @_; $data->set_text($string,-1); }You may want to pass an extra string to define $data's value. $info is used to determine which source called this callback. It is the 'info' value of the
Gtk2::TargetEntry
data type. This allows you to utilize the same callback by various drag sources. $data is what this section is discussing, and will follow after a snippet of the typical 'drag-data-received'
signal callback.
sub target_drag_data_received { #--------------------------------------------------- #Extract the data which was set up during the ----- #'drag-data-get' event which fired just before ----- #the 'drag-data-received' event. Also checks which-- #source supplied the data, and handle accordingly---- #--------------------------------------------------- my ($widget, $context, $x, $y, $data, $info, $time) = @_; if ($info eq ID_URI){ print "Mmmm feels like Nautilus...\n"; foreach ($data->get_uris){ print "Got file ".$_."\n"; } } }$info and $data are used most often. The rest can be used during more advanced implementations.
The Gtk2::SelectionData
object is used to store a chunk of data along with the data type and other associated information. It has various convenient methods to insert and extract data to and from it. You can check out its POD for more info.
In the sample program we will use its 'get_uris'
, 'data'
and 'set_text'
methods. They are sufficient to transfer text between the drag source and destination.
Although |
This is the fun part. Enough theory, lets look at a sample. In the sample we make use of the two Gtk2::Paned
classes, Gtk2::VPaned
and Gtk2::HPaned
. We have a Gtk2::IconView
widget that contains a list of all the Gtk2::Stock
icons. These can be dragged onto a Gtk2::SimpleList
. This Gtk2::SimpleList
can also accept dragged items from the Nautilus file browser and the forbidden 'Now Stanley, DO NOT drag this item' event box. (come on, try it out :)). As a last bonus, we introduce Gtk2::ToolTips
to the reader.
Table 21-1. Gtk2
object classes used
Gtk2 Class Name |
---|
Gtk2::Window |
Gtk2::ToolTips |
Gtk2::VBox |
Gtk2::Paned |
Gtk2::VPaned |
Gtk2::HPaned |
Gtk2::EventBox |
Gtk2::Label |
Gtk2::IconView |
Gtk2::SimpleList |
Gtk2::ScrolledWindow |
Gtk2::ListStore |
Gtk2::IconFactory |
Gtk2::IconSet |
Gtk2::Stock |
Gtk2::Style |
Gtk2::TargetEntry |
Gtk2::SelectionData |
The program can be found here: 'gtk2_iconview.pl'
1 #! /usr/bin/perl -w 2 3 use strict; 4 use Gtk2 '-init'; 5 use Glib qw/TRUE FALSE/; 6 use Gtk2::SimpleList; 7 8 #Declare our columns 9 use constant C_MARKUP => 0; 10 use constant C_PIXBUF => 1; 11 12 #Declare our IDENDTIFIER ID's 13 use constant ID_ICONVIEW => 48; 14 use constant ID_LABEL => 49; 15 use constant ID_URI => 50; 16 17 #Add a tooltip object 18 my $tooltips = Gtk2::Tooltips->new(); 19 20 #standard window creation, placement, and signal connecting 21 my $window = Gtk2::Window->new('toplevel'); 22 $window->signal_connect('delete_event' => sub { Gtk2->main_quit; }); 23 $window->set_border_width(5); 24 $window->set_position('center_always'); 25 26 #add and show the vbox 27 $window->add(&ret_vbox); 28 $window->show(); 29 30 #our main event-loop 31 Gtk2->main(); 32 33 sub ret_vbox { 34 35 my $vbox = Gtk2::VBox->new(FALSE,5); 36 my $h_paned = Gtk2::HPaned->new(); 37 38 my $v_paned = Gtk2::VPaned->new(); 39 #get the iconview 40 $v_paned->pack1(create_iconview(),TRUE,FALSE); 41 #get the simple list where everything will be dropped into 42 $v_paned->pack2(create_simple_list(),TRUE,FALSE); 43 44 $h_paned->pack1($v_paned,TRUE,FALSE); 45 46 #create an eventbox to accept drag actions 47 my $event_box = Gtk2::EventBox->new(); 48 my $label_drag_me = Gtk2::Label->new("Now Stanley,\nDO NOT drag this item"); 49 $event_box->add($label_drag_me); 50 51 #Try and convince the user otherwise 52 $tooltips->set_tip ($event_box, 53 'No way! You have to drag it at least once!'. 54 'Drag it, and drop it into the bottom list' 55 ); 56 57 #Setting up the Gtk2::Event Box instead of the Gtk2::Label, enabling it as 58 #a drag source 59 $event_box->drag_source_set ( 60 ['button1_mask', 'button3_mask'], 61 ['copy'], 62 { 63 'target' => 'text/plain', 64 'flags' => [], 65 'info' => ID_LABEL, 66 }, 67 ); 68 69 my $text_to_drag = '<span foreground="red" size="x-large">"Well there\'s another nice mess you\'ve gotten me into."</span>'; 70 71 #set up the data which needs to be fed to the drag destination (drop) 72 $event_box->signal_connect ('drag-data-get' => \&source_drag_data_get,$text_to_drag ); 73 74 $h_paned->pack2($event_box,TRUE,FALSE); 75 76 $vbox->add($h_paned); 77 $vbox->show_all(); 78 return $vbox; 79 } 80 81 sub create_iconview { 82 #--------------------------------------------------- 83 #Creates an Iconview in a ScrolledWindow. This ----- 84 #Iconview has the ability to drag items off it ----- 85 #--------------------------------------------------- 86 87 my $icon_string= undef; 88 my $tree_model = create_iconview_model(); 89 90 my $icon_view = Gtk2::IconView->new_with_model($tree_model); 91 $icon_view->set_markup_column(C_MARKUP); 92 $icon_view->set_pixbuf_column(C_PIXBUF); 93 94 #Enable the Gtk2::IconView as a drag source 95 $icon_view->drag_source_set ( 96 ['button1_mask', 'button3_mask'], 97 ['copy'], 98 { 99 'target' => 'STRING', 100 'flags' => [], 101 'info' => ID_ICONVIEW, 102 }, 103 ); 104 105 #This is a nice to have. It changes the drag icon to that of the 106 #icon which are now selected and dragged (single selection mode) 107 $icon_view->signal_connect('drag-begin' => sub { 108 $icon_view->selected_foreach ( sub{ 109 110 my $iter =$tree_model->get_iter($_[1]); 111 #set the text and pixbuf 112 my $icon_pixbuf = $tree_model->get_value($iter,C_PIXBUF); 113 $icon_string = $tree_model->get_value($iter,C_MARKUP); 114 $icon_view->drag_source_set_icon_pixbuf ($icon_pixbuf); 115 } ); 116 }); 117 118 #set up the data which needs to be fed to the drag destination (drop) 119 $icon_view->signal_connect ('drag-data-get' => sub { source_drag_data_get(@_,$icon_string) } ); 120 121 #Standard scrolledwindow to allow growth 122 my $sw = Gtk2::ScrolledWindow->new(undef,undef); 123 $sw->set_policy('never','automatic'); 124 $sw->add($icon_view); 125 $sw->set_border_width(6); 126 $sw->set_size_request(600,150); 127 return $sw; 128 } 129 130 sub create_iconview_model { 131 #---------------------------------------------------- 132 #The Iconview needs a Gtk2::Treemodel implementation- 133 #containing at least a Glib::String and ------------- 134 #Gtk2::Gdk::Pixbuf type. The first is used for the -- 135 #text of the icon, and the last for the icon self---- 136 #Gtk2::ListStore is ideal for this ------------------ 137 #---------------------------------------------------- 138 139 my $list_store = Gtk2::ListStore->new(qw/Glib::String Gtk2::Gdk::Pixbuf/); 140 141 #****************************************************** 142 #we populate the Gtk2::ListStore with Gtk2::Stock icons 143 #****************************************************** 144 145 my $icon_factory = Gtk2::IconFactory->new(); 146 147 foreach my $val(sort Gtk2::Stock->list_ids){ 148 #get the iconset from the icon_factory 149 my $iconset = $icon_factory->lookup_default($val); 150 #try and extract the icon from it 151 my $pixbuf = $iconset->render_icon(Gtk2::Style->new(),'none','normal','dnd',undef); 152 153 #if there was a valid icon in the iconset, add it 154 if( defined $pixbuf ){ 155 156 my $iter = $list_store->append; 157 $list_store->set ( 158 $iter, 159 C_MARKUP, "<b>$val</b>", 160 C_PIXBUF, $pixbuf, 161 ); 162 163 } 164 } 165 166 return $list_store; 167 } 168 169 sub create_simple_list { 170 #--------------------------------------------------- 171 #Creates a simple list with pixbuf and markup ----- 172 #columns make this list the drop target for various- 173 #drag sources -------------------------------------- 174 #--------------------------------------------------- 175 176 my $slist = Gtk2::SimpleList->new ('' => 'pixbuf', '' => 'markup'); 177 $slist->set_rules_hint(TRUE); 178 $slist->set_headers_visible(FALSE); 179 180 #Also suggest to try Nautilus 181 $tooltips->set_tip ($slist, 182 'Drag a few files from Nautilus '. 183 'and drop it here' 184 ); 185 186 #Create a target table to receive drops 187 my @target_table = ( 188 189 {'target' => 'STRING', 'flags' => [], 'info' => ID_ICONVIEW }, 190 {'target' => "text/uri-list",'flags' => [], 'info' => ID_URI }, 191 {'target' => "text/plain", 'flags' => [], 'info' => ID_LABEL }, 192 ); 193 194 #make this the drag destination (drop) for various drag sources 195 $slist->drag_dest_set('all', ['copy'], @target_table); 196 197 #do a callback as soon as drag data is received 198 $slist->signal_connect ('drag-data-received' => \&target_drag_data_received,$slist ); 199 200 #Standard scrolledwindow to allow growth 201 my $sw = Gtk2::ScrolledWindow->new(undef,undef); 202 $sw->set_policy('never','automatic'); 203 $sw->add($slist); 204 $sw->set_border_width(6); 205 $sw->set_size_request(600,150); 206 return $sw; 207 } 208 209 sub target_drag_data_received { 210 #--------------------------------------------------- 211 #Extract the data which was set up during the ----- 212 #'drag-data-get' event which fired just before ----- 213 #the 'drag-data-received' event. Also checks which-- 214 #source supplied the data, and handle accordingly---- 215 #--------------------------------------------------- 216 217 my ($widget, $context, $x, $y, $data, $info, $time,$slist) = @_; 218 219 my $icon_factory = Gtk2::IconFactory->new(); 220 221 if ($info eq ID_LABEL){ 222 my $iconset = $icon_factory->lookup_default('gtk-no'); 223 my $pixbuf = $iconset->render_icon(Gtk2::Style->new(),'none','normal','menu',undef); 224 push @{$slist->{data}}, [ $pixbuf, $data->data ]; 225 } 226 227 if ($info eq ID_URI){ 228 my $iconset = $icon_factory->lookup_default('gtk-yes'); 229 my $pixbuf = $iconset->render_icon(Gtk2::Style->new(),'none','normal','menu',undef); 230 231 foreach ($data->get_uris){ 232 push @{$slist->{data}}, 233 [ $pixbuf, '<span foreground="forest green" size="x-small">'.$_.'</span>' ]; 234 } 235 } 236 237 if ($info eq ID_ICONVIEW){ 238 239 my $no_markup = $data->data; 240 $no_markup =~ s/<[^>]*>//g; 241 my $iconset = $icon_factory->lookup_default($no_markup); 242 my $pixbuf = $iconset->render_icon(Gtk2::Style->new(),'none','normal','menu',undef); 243 push @{$slist->{data}}, [ $pixbuf, $data->data ]; 244 } 245 246 $context->finish (0, 0, $time); 247 } 248 249 sub source_drag_data_get { 250 #--------------------------------------------------- 251 #This sets up the data of the drag source. It is --- 252 #required before the 'drag-data-received' event ---- 253 #fires which can be used to extract this data ------ 254 #--------------------------------------------------- 255 256 my ($widget, $context, $data, $info, $time,$string) = @_; 257 258 $data->set_text($string,-1); 259 }
In this sample we make use of the Gtk2::IconFactory
class. This class is used to store Gtk2::IconSet
s. It also contains all the Gtk2::Stock
items which contains icons (as Gtk2::IconSet
s). To get the iconset for a certain Gtk2::Stock
item, we call the 'lookup_default'
method on an instance of Gtk2::IconFactory
.
#get the iconset from the icon_factory my $iconset = $icon_factory->lookup_default('gtk-ok');A
Gtk2::IconSet
bring forth various sizes of icons. This makes it easy to use when you want the same icon in various sizes. A list of possible values for the size of the icon from the Gtk2::IconSet
can be extracted with the 'get_sizes'
method.
To extract the Gtk2::Gdk::Pixbuf
from the Gtk2::IconSet
we use its render_icon
method. We then specify a required value for Gtk2::IconSize as an argument to the method in order to manipulate the size.
#try and extract the icon from it my $pixbuf = $iconset->render_icon(Gtk2::Style->new(),'none','normal','dnd',undef);
Gtk2::IconView
. This is a viewer for the typical MVC (Model-View-Controller) design pattern. It makes use of the Gtk2::ListStore
as the implementation of the Gtk2::TreeModel
interface. This model must contain at least a Gtk2::Gdk::Pixbuf
column containing pixbufs of the icons to display.
We connect the Gtk2::Gdk::Pixbuf
column in the Gtk2::ListStore
to the Gtk2::IconView
using its set_pixbuf_column
method.
If you need to display text below the icons, you may specify a Glib::String type on the Gtk2::ListStore
model. This gets connected to the Gtk2::IconView
using its set_text_column
. This text can be Gtk2::Pango
markup, then you simply connect to the model using the Gtk2::IconView
's set_markup_column
method.
Again you can make life easier for yourself by using constants instead of integers to keep track of the columns. |
Gtk2::Paned
A paned widget draws a separator between the two child widgets and a small handle that the user can drag to adjust the division. It does not draw any relief around the children or around the separator.
You can read more about Gtk2::Paned
widgets on its POD.
To force a minimum size on a widget, you can use the |
The following section takes the points mentioned at the start of this lesson, and show how each one is implemented in the program.
We declare the Gtk2::IconView
as a drag source
#Enable the Gtk2::IconView as a drag source $icon_view->drag_source_set ( ['button1_mask', 'button3_mask'], ['copy'], { 'target' => 'STRING', 'flags' => [], 'info' => ID_ICONVIEW, }, );
We declare the Gtk2::SimpleList
as a drag destination. Here we specify a few sources it will accept data from.
#Create a target table to receive drops my @target_table = ( {'target' => 'STRING', 'flags' => [], 'info' => ID_ICONVIEW }, {'target' => "text/uri-list",'flags' => [], 'info' => ID_URI }, {'target' => "text/plain", 'flags' => [], 'info' => ID_LABEL }, ); #make this the drag destination (drop) for various drag sources $slist->drag_dest_set('all', ['copy'], @target_table);
When a user starts to drag an icon, we want to show which one was chosen.
#This is a nice to have. It changes the drag icon to that of the #icon which are now selected and dragged (single selection mode) $icon_view->signal_connect('drag-begin' => sub { $icon_view->selected_foreach ( sub{ my $iter =$tree_model->get_iter($_[1]); #set the text and pixbuf my $icon_pixbuf = $tree_model->get_value($iter,C_PIXBUF); $icon_string = $tree_model->get_value($iter,C_MARKUP); $icon_view->drag_source_set_icon_pixbuf ($icon_pixbuf); } ); });The second argument passed the the sub called by
selected_foreach
is a Gtk2::TreePath
. We use this to get a Gtk2::TreeIter
pointing to the selected icon. Since we are in single selection mode, the foreach will only run once.
To set up the data on the drag source source:
#set up the data which needs to be fed to the drag destination (drop) $icon_view->signal_connect ('drag-data-get' => sub { source_drag_data_get(@_,$icon_string) });
To extract the data on the drag destination:
#do a callback as soon as drag data is received $slist->signal_connect ('drag-data-received' => \&target_drag_data_received,$slist );
If you want to change the appearance of the drag destination as soon as a drag source enters it, connect a callback to the |
Gtk2::ToolTips
.Tooltips are a wonderful way to guide a user into what is expected of him/her. It is also nice to give a bit more detail on certain widgets and their functions.
To create tooltips, first declare a Gtk2::ToolTips
object. Then add the widgets with the the tooltip text to it. To do this, make use of the set_tip
method.
#Try and convince the user otherwise $tooltips->set_tip ( $event_box, 'No way! You have to drag it at least once!'. 'Drag it, and drop it into the bottom list' );
This brings us to the end of the drag and drop section. I trust this may get you out of a nice mess some day.