1 /* eggtreemultidnd.c
2  * Copyright (C) 2001  Red Hat, Inc.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 
20 #include <string.h>
21 #include <gtk/gtk.h>
22 #include "eggtreemultidnd.h"
23 
24 #define EGG_TREE_MULTI_DND_STRING "EggTreeMultiDndString"
25 
26 typedef struct
27 {
28   guint pressed_button;
29   gint x;
30   gint y;
31   gulong motion_notify_handler;
32   gulong button_release_handler;
33   gulong drag_data_get_handler;
34   GSList *event_list;
35 } EggTreeMultiDndData;
36 
37 /* CUT-N-PASTE from gtktreeview.c */
38 typedef struct _TreeViewDragInfo TreeViewDragInfo;
39 struct _TreeViewDragInfo
40 {
41   GdkModifierType start_button_mask;
42 
43   /* This has been unused since 2.14.x */
44   GtkTargetList *unused_source_target_list;
45   GdkDragAction source_actions;
46 
47   GtkTargetList *dest_target_list;
48 
49   guint source_set : 1;
50   guint dest_set : 1;
51 };
52 
53 
54 GType
egg_tree_multi_drag_source_get_type(void)55 egg_tree_multi_drag_source_get_type (void)
56 {
57   static GType our_type = 0;
58 
59   if (!our_type)
60     {
61       static const GTypeInfo our_info =
62       {
63         sizeof (EggTreeMultiDragSourceIface), /* class_size */
64 	NULL,		/* base_init */
65 	NULL,		/* base_finalize */
66 	NULL,
67 	NULL,		/* class_finalize */
68 	NULL,		/* class_data */
69 	0,
70 	0,              /* n_preallocs */
71 	NULL
72       };
73 
74       our_type = g_type_register_static (G_TYPE_INTERFACE, "EggTreeMultiDragSource", &our_info, 0);
75     }
76 
77   return our_type;
78 }
79 
80 
81 /**
82  * egg_tree_multi_drag_source_row_draggable:
83  * @drag_source: a #EggTreeMultiDragSource
84  * @path: row on which user is initiating a drag
85  *
86  * Asks the #EggTreeMultiDragSource whether a particular row can be used as
87  * the source of a DND operation. If the source doesn't implement
88  * this interface, the row is assumed draggable.
89  *
90  * Return value: %TRUE if the row can be dragged
91  **/
92 gboolean
egg_tree_multi_drag_source_row_draggable(EggTreeMultiDragSource * drag_source,GList * path_list)93 egg_tree_multi_drag_source_row_draggable (EggTreeMultiDragSource *drag_source,
94 					  GList                  *path_list)
95 {
96   EggTreeMultiDragSourceIface *iface = EGG_TREE_MULTI_DRAG_SOURCE_GET_IFACE (drag_source);
97 
98   g_return_val_if_fail (EGG_IS_TREE_MULTI_DRAG_SOURCE (drag_source), FALSE);
99   g_return_val_if_fail (iface->row_draggable != NULL, FALSE);
100   g_return_val_if_fail (path_list != NULL, FALSE);
101 
102   if (iface->row_draggable)
103     return (* iface->row_draggable) (drag_source, path_list);
104   else
105     return TRUE;
106 }
107 
108 
109 /**
110  * egg_tree_multi_drag_source_drag_data_delete:
111  * @drag_source: a #EggTreeMultiDragSource
112  * @path: row that was being dragged
113  *
114  * Asks the #EggTreeMultiDragSource to delete the row at @path, because
115  * it was moved somewhere else via drag-and-drop. Returns %FALSE
116  * if the deletion fails because @path no longer exists, or for
117  * some model-specific reason. Should robustly handle a @path no
118  * longer found in the model!
119  *
120  * Return value: %TRUE if the row was successfully deleted
121  **/
122 gboolean
egg_tree_multi_drag_source_drag_data_delete(EggTreeMultiDragSource * drag_source,GList * path_list)123 egg_tree_multi_drag_source_drag_data_delete (EggTreeMultiDragSource *drag_source,
124 					     GList                  *path_list)
125 {
126   EggTreeMultiDragSourceIface *iface = EGG_TREE_MULTI_DRAG_SOURCE_GET_IFACE (drag_source);
127 
128   g_return_val_if_fail (EGG_IS_TREE_MULTI_DRAG_SOURCE (drag_source), FALSE);
129   g_return_val_if_fail (iface->drag_data_delete != NULL, FALSE);
130   g_return_val_if_fail (path_list != NULL, FALSE);
131 
132   return (* iface->drag_data_delete) (drag_source, path_list);
133 }
134 
135 /**
136  * egg_tree_multi_drag_source_drag_data_get:
137  * @drag_source: a #EggTreeMultiDragSource
138  * @path: row that was dragged
139  * @selection_data: a #EggSelectionData to fill with data from the dragged row
140  *
141  * Asks the #EggTreeMultiDragSource to fill in @selection_data with a
142  * representation of the row at @path. @selection_data->target gives
143  * the required type of the data.  Should robustly handle a @path no
144  * longer found in the model!
145  *
146  * Return value: %TRUE if data of the required type was provided
147  **/
148 gboolean
egg_tree_multi_drag_source_drag_data_get(EggTreeMultiDragSource * drag_source,GList * path_list,GtkSelectionData * selection_data)149 egg_tree_multi_drag_source_drag_data_get    (EggTreeMultiDragSource *drag_source,
150 					     GList                  *path_list,
151 					     GtkSelectionData  *selection_data)
152 {
153   EggTreeMultiDragSourceIface *iface = EGG_TREE_MULTI_DRAG_SOURCE_GET_IFACE (drag_source);
154 
155   g_return_val_if_fail (EGG_IS_TREE_MULTI_DRAG_SOURCE (drag_source), FALSE);
156   g_return_val_if_fail (iface->drag_data_get != NULL, FALSE);
157   g_return_val_if_fail (path_list != NULL, FALSE);
158   g_return_val_if_fail (selection_data != NULL, FALSE);
159 
160   return (* iface->drag_data_get) (drag_source, path_list, selection_data);
161 }
162 
163 static void
stop_drag_check(GtkWidget * widget)164 stop_drag_check (GtkWidget *widget)
165 {
166   EggTreeMultiDndData *priv_data;
167   GSList *l;
168 
169   priv_data = g_object_get_data (G_OBJECT (widget), EGG_TREE_MULTI_DND_STRING);
170 
171   for (l = priv_data->event_list; l != NULL; l = l->next)
172     gdk_event_free (l->data);
173 
174   g_slist_free (priv_data->event_list);
175   priv_data->event_list = NULL;
176 }
177 
178 static gboolean
egg_tree_multi_drag_button_release_event(GtkWidget * widget,GdkEventButton * event,gpointer data)179 egg_tree_multi_drag_button_release_event (GtkWidget      *widget,
180 					  GdkEventButton *event,
181 					  gpointer        data)
182 {
183   EggTreeMultiDndData *priv_data;
184   GSList *l;
185 
186   priv_data = g_object_get_data (G_OBJECT (widget), EGG_TREE_MULTI_DND_STRING);
187 
188   /* disconnect before sending all queued events to avoid a warning with
189    * GtkFileChooserDialog and double click. */
190   if (priv_data->motion_notify_handler) {
191     g_signal_handler_disconnect (widget, priv_data->motion_notify_handler);
192     priv_data->motion_notify_handler = 0;
193   }
194   if (priv_data->button_release_handler) {
195     g_signal_handler_disconnect (widget, priv_data->button_release_handler);
196     priv_data->button_release_handler = 0;
197   }
198 
199   for (l = priv_data->event_list; l != NULL && gtk_widget_get_realized (widget); l = l->next)
200     gtk_propagate_event (widget, l->data);
201 
202   stop_drag_check (widget);
203 
204   return FALSE;
205 }
206 
207 static void
selection_foreach(GtkTreeModel * model,GtkTreePath * path,GtkTreeIter * iter,gpointer data)208 selection_foreach (GtkTreeModel *model,
209 		   GtkTreePath  *path,
210 		   GtkTreeIter  *iter,
211 		   gpointer      data)
212 {
213   GList **list_ptr;
214 
215   list_ptr = (GList **) data;
216 
217   *list_ptr = g_list_prepend (*list_ptr, gtk_tree_row_reference_new (model, path));
218 }
219 
220 static void
path_list_free(GList * path_list)221 path_list_free (GList *path_list)
222 {
223   g_list_foreach (path_list, (GFunc) gtk_tree_row_reference_free, NULL);
224   g_list_free (path_list);
225 }
226 
227 static void
set_context_data(GdkDragContext * context,GList * path_list)228 set_context_data (GdkDragContext *context,
229 		  GList          *path_list)
230 {
231   g_object_set_data_full (G_OBJECT (context),
232                           "egg-tree-view-multi-source-row",
233                           path_list,
234                           (GDestroyNotify) path_list_free);
235 }
236 
237 static void
egg_tree_multi_drag_begin(GtkWidget * tree,GdkDragContext * context,gpointer user_data)238 egg_tree_multi_drag_begin (GtkWidget *tree,
239                            GdkDragContext *context,
240                            gpointer user_data)
241 {
242   gtk_drag_set_icon_default (context);
243   g_signal_stop_emission_by_name (tree, "drag-begin");
244 }
245 
246 static GList *
get_context_data(GdkDragContext * context)247 get_context_data (GdkDragContext *context)
248 {
249   return g_object_get_data (G_OBJECT (context),
250 			    "egg-tree-view-multi-source-row");
251 }
252 
253 /* CUT-N-PASTE from gtktreeview.c */
254 static TreeViewDragInfo*
get_info(GtkTreeView * tree_view)255 get_info (GtkTreeView *tree_view)
256 {
257   return g_object_get_data (G_OBJECT (tree_view), "gtk-tree-view-drag-info");
258 }
259 
260 
261 static void
egg_tree_multi_drag_drag_data_get(GtkWidget * widget,GdkDragContext * context,GtkSelectionData * selection_data,guint info,guint time)262 egg_tree_multi_drag_drag_data_get (GtkWidget        *widget,
263 				   GdkDragContext   *context,
264 				   GtkSelectionData *selection_data,
265 				   guint             info,
266 				   guint             time)
267 {
268   GtkTreeView *tree_view;
269   GtkTreeModel *model;
270   TreeViewDragInfo *di;
271   GList *path_list;
272 
273   tree_view = GTK_TREE_VIEW (widget);
274 
275   model = gtk_tree_view_get_model (tree_view);
276 
277   if (model == NULL)
278     return;
279 
280   di = get_info (GTK_TREE_VIEW (widget));
281 
282   if (di == NULL)
283     return;
284 
285   path_list = get_context_data (context);
286 
287   if (path_list == NULL)
288     return;
289 
290   /* We can implement the GTK_TREE_MODEL_ROW target generically for
291    * any model; for DragSource models there are some other targets
292    * we also support.
293    */
294 
295     if (EGG_IS_TREE_MULTI_DRAG_SOURCE (model))
296     {
297       egg_tree_multi_drag_source_drag_data_get (EGG_TREE_MULTI_DRAG_SOURCE (model),
298 						path_list,
299 						selection_data);
300     }
301 }
302 
303 static gboolean
egg_tree_multi_drag_motion_event(GtkWidget * widget,GdkEventMotion * event,gpointer data)304 egg_tree_multi_drag_motion_event (GtkWidget      *widget,
305 				  GdkEventMotion *event,
306 				  gpointer        data)
307 {
308   EggTreeMultiDndData *priv_data;
309 
310   priv_data = g_object_get_data (G_OBJECT (widget), EGG_TREE_MULTI_DND_STRING);
311 
312   if (gtk_drag_check_threshold (widget,
313 				priv_data->x,
314 				priv_data->y,
315 				event->x,
316 				event->y))
317     {
318       GList *path_list = NULL;
319       GtkTreeSelection *selection;
320       GtkTreeModel *model;
321       GdkDragContext *context;
322       TreeViewDragInfo *di;
323 
324       di = get_info (GTK_TREE_VIEW (widget));
325 
326       if (di == NULL)
327 	return FALSE;
328 
329       selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
330       if (priv_data->motion_notify_handler) {
331         g_signal_handler_disconnect (widget, priv_data->motion_notify_handler);
332         priv_data->motion_notify_handler = 0;
333       }
334       if (priv_data->button_release_handler) {
335         g_signal_handler_disconnect (widget, priv_data->button_release_handler);
336         priv_data->button_release_handler = 0;
337       }
338       stop_drag_check (widget);
339       gtk_tree_selection_selected_foreach (selection, selection_foreach, &path_list);
340       path_list = g_list_reverse (path_list);
341       model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
342       if (egg_tree_multi_drag_source_row_draggable (EGG_TREE_MULTI_DRAG_SOURCE (model), path_list))
343 	{
344 	  gulong sig;
345 
346 	  /* This is to disconnect the default signal handler for treeviews as
347   	   * it sometimes gives a warning. The default handler just sets the
348 	   * icon which we do as well in our callback so it is fine. */
349 	  sig = g_signal_connect (widget,
350 	                          "drag-begin",
351 	                          G_CALLBACK (egg_tree_multi_drag_begin),
352 	                          NULL);
353 	  context = gtk_drag_begin (widget,
354 				    gtk_drag_source_get_target_list (widget),
355 				    di->source_actions,
356 	                            priv_data->pressed_button,
357 				    (GdkEvent*)event);
358 	  g_signal_handler_disconnect (widget, sig);
359 	  set_context_data (context, path_list);
360 	}
361       else
362 	{
363 	  path_list_free (path_list);
364 	}
365     }
366   return TRUE;
367 }
368 
369 static gboolean
egg_tree_multi_drag_button_press_event(GtkWidget * widget,GdkEventButton * event,gpointer data)370 egg_tree_multi_drag_button_press_event (GtkWidget      *widget,
371 					GdkEventButton *event,
372 					gpointer        data)
373 {
374   GtkTreeView *tree_view;
375   GtkTreePath *path = NULL;
376   GtkTreeViewColumn *column = NULL;
377   gint cell_x, cell_y;
378   GtkTreeSelection *selection;
379   EggTreeMultiDndData *priv_data;
380 
381   tree_view = GTK_TREE_VIEW (widget);
382   priv_data = g_object_get_data (G_OBJECT (tree_view), EGG_TREE_MULTI_DND_STRING);
383   if (priv_data == NULL)
384     {
385       priv_data = g_new0 (EggTreeMultiDndData, 1);
386       g_object_set_data (G_OBJECT (tree_view), EGG_TREE_MULTI_DND_STRING, priv_data);
387     }
388 
389   if (g_slist_find (priv_data->event_list, event))
390     return FALSE;
391 
392   if (priv_data->event_list)
393     {
394       /* save the event to be propagated in order */
395       priv_data->event_list = g_slist_append (priv_data->event_list, gdk_event_copy ((GdkEvent*)event));
396       return TRUE;
397     }
398 
399   if (event->type == GDK_2BUTTON_PRESS)
400     return FALSE;
401 
402   if (event->button == 3)
403     return FALSE;
404 
405   gtk_tree_view_get_path_at_pos (tree_view,
406 				 event->x, event->y,
407 				 &path, &column,
408 				 &cell_x, &cell_y);
409 
410   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view));
411   if (path && gtk_tree_selection_path_is_selected (selection, path))
412     {
413       priv_data->pressed_button = event->button;
414       priv_data->x = event->x;
415       priv_data->y = event->y;
416       priv_data->event_list = g_slist_append (priv_data->event_list, gdk_event_copy ((GdkEvent*)event));
417 
418       priv_data->motion_notify_handler =
419 	g_signal_connect (G_OBJECT (tree_view), "motion_notify_event", G_CALLBACK (egg_tree_multi_drag_motion_event), NULL);
420      priv_data->button_release_handler =
421 	g_signal_connect (G_OBJECT (tree_view), "button_release_event", G_CALLBACK (egg_tree_multi_drag_button_release_event), NULL);
422 
423       if (priv_data->drag_data_get_handler == 0)
424 	{
425 	  priv_data->drag_data_get_handler =
426 	    g_signal_connect (G_OBJECT (tree_view), "drag_data_get", G_CALLBACK (egg_tree_multi_drag_drag_data_get), NULL);
427 	}
428 
429       gtk_tree_path_free (path);
430 
431       return TRUE;
432     }
433 
434   if (path)
435     {
436       gtk_tree_path_free (path);
437     }
438 
439   return FALSE;
440 }
441 
442 void
egg_tree_multi_drag_add_drag_support(GtkTreeView * tree_view)443 egg_tree_multi_drag_add_drag_support (GtkTreeView *tree_view)
444 {
445   g_return_if_fail (GTK_IS_TREE_VIEW (tree_view));
446   g_signal_connect (G_OBJECT (tree_view), "button_press_event", G_CALLBACK (egg_tree_multi_drag_button_press_event), NULL);
447 }
448 
449