]> www.fi.muni.cz Git - evince.git/blob - cut-n-paste/toolbar-editor/eggtreemultidnd.c
a76a00f234ef61eaa93f392bb5076d5d0cb4cb05
[evince.git] / cut-n-paste / toolbar-editor / eggtreemultidnd.c
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., 59 Temple Place - Suite 330,
17  * Boston, MA 02111-1307, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #include <config.h>
22 #endif
23
24 #include <string.h>
25 #include <gtk/gtk.h>
26 #include "eggtreemultidnd.h"
27
28 #define EGG_TREE_MULTI_DND_STRING "EggTreeMultiDndString"
29
30 typedef struct
31 {
32   guint pressed_button;
33   gint x;
34   gint y;
35   guint motion_notify_handler;
36   guint button_release_handler;
37   guint drag_data_get_handler;
38   GSList *event_list;
39 } EggTreeMultiDndData;
40
41 /* CUT-N-PASTE from gtktreeview.c */
42 typedef struct _TreeViewDragInfo TreeViewDragInfo;
43 struct _TreeViewDragInfo
44 {
45   GdkModifierType start_button_mask;
46   GtkTargetList *source_target_list;
47   GdkDragAction source_actions;
48
49   GtkTargetList *dest_target_list;
50
51   guint source_set : 1;
52   guint dest_set : 1;
53 };
54
55
56 GType
57 egg_tree_multi_drag_source_get_type (void)
58 {
59   static GType our_type = 0;
60
61   if (!our_type)
62     {
63       const GTypeInfo our_info =
64       {
65         sizeof (EggTreeMultiDragSourceIface), /* class_size */
66         NULL,           /* base_init */
67         NULL,           /* base_finalize */
68         NULL,
69         NULL,           /* class_finalize */
70         NULL,           /* class_data */
71         0,
72         0,              /* n_preallocs */
73         NULL
74       };
75
76       our_type = g_type_register_static (G_TYPE_INTERFACE, "EggTreeMultiDragSource", &our_info, 0);
77     }
78   
79   return our_type;
80 }
81
82
83 /**
84  * egg_tree_multi_drag_source_row_draggable:
85  * @drag_source: a #EggTreeMultiDragSource
86  * @path: row on which user is initiating a drag
87  * 
88  * Asks the #EggTreeMultiDragSource whether a particular row can be used as
89  * the source of a DND operation. If the source doesn't implement
90  * this interface, the row is assumed draggable.
91  *
92  * Return value: %TRUE if the row can be dragged
93  **/
94 gboolean
95 egg_tree_multi_drag_source_row_draggable (EggTreeMultiDragSource *drag_source,
96                                           GList                  *path_list)
97 {
98   EggTreeMultiDragSourceIface *iface = EGG_TREE_MULTI_DRAG_SOURCE_GET_IFACE (drag_source);
99
100   g_return_val_if_fail (EGG_IS_TREE_MULTI_DRAG_SOURCE (drag_source), FALSE);
101   g_return_val_if_fail (iface->row_draggable != NULL, FALSE);
102   g_return_val_if_fail (path_list != NULL, FALSE);
103
104   if (iface->row_draggable)
105     return (* iface->row_draggable) (drag_source, path_list);
106   else
107     return TRUE;
108 }
109
110
111 /**
112  * egg_tree_multi_drag_source_drag_data_delete:
113  * @drag_source: a #EggTreeMultiDragSource
114  * @path: row that was being dragged
115  * 
116  * Asks the #EggTreeMultiDragSource to delete the row at @path, because
117  * it was moved somewhere else via drag-and-drop. Returns %FALSE
118  * if the deletion fails because @path no longer exists, or for
119  * some model-specific reason. Should robustly handle a @path no
120  * longer found in the model!
121  * 
122  * Return value: %TRUE if the row was successfully deleted
123  **/
124 gboolean
125 egg_tree_multi_drag_source_drag_data_delete (EggTreeMultiDragSource *drag_source,
126                                              GList                  *path_list)
127 {
128   EggTreeMultiDragSourceIface *iface = EGG_TREE_MULTI_DRAG_SOURCE_GET_IFACE (drag_source);
129
130   g_return_val_if_fail (EGG_IS_TREE_MULTI_DRAG_SOURCE (drag_source), FALSE);
131   g_return_val_if_fail (iface->drag_data_delete != NULL, FALSE);
132   g_return_val_if_fail (path_list != NULL, FALSE);
133
134   return (* iface->drag_data_delete) (drag_source, path_list);
135 }
136
137 /**
138  * egg_tree_multi_drag_source_drag_data_get:
139  * @drag_source: a #EggTreeMultiDragSource
140  * @path: row that was dragged
141  * @selection_data: a #EggSelectionData to fill with data from the dragged row
142  * 
143  * Asks the #EggTreeMultiDragSource to fill in @selection_data with a
144  * representation of the row at @path. @selection_data->target gives
145  * the required type of the data.  Should robustly handle a @path no
146  * longer found in the model!
147  * 
148  * Return value: %TRUE if data of the required type was provided 
149  **/
150 gboolean
151 egg_tree_multi_drag_source_drag_data_get    (EggTreeMultiDragSource *drag_source,
152                                              GList                  *path_list,
153                                              GtkSelectionData  *selection_data)
154 {
155   EggTreeMultiDragSourceIface *iface = EGG_TREE_MULTI_DRAG_SOURCE_GET_IFACE (drag_source);
156
157   g_return_val_if_fail (EGG_IS_TREE_MULTI_DRAG_SOURCE (drag_source), FALSE);
158   g_return_val_if_fail (iface->drag_data_get != NULL, FALSE);
159   g_return_val_if_fail (path_list != NULL, FALSE);
160   g_return_val_if_fail (selection_data != NULL, FALSE);
161
162   return (* iface->drag_data_get) (drag_source, path_list, selection_data);
163 }
164
165 static void
166 stop_drag_check (GtkWidget *widget)
167 {
168   EggTreeMultiDndData *priv_data;
169   GSList *l;
170
171   priv_data = g_object_get_data (G_OBJECT (widget), EGG_TREE_MULTI_DND_STRING);
172   
173   for (l = priv_data->event_list; l != NULL; l = l->next)
174     gdk_event_free (l->data);
175   
176   g_slist_free (priv_data->event_list);
177   priv_data->event_list = NULL;
178   g_signal_handler_disconnect (widget, priv_data->motion_notify_handler);
179   g_signal_handler_disconnect (widget, priv_data->button_release_handler);
180 }
181
182 static gboolean
183 egg_tree_multi_drag_button_release_event (GtkWidget      *widget,
184                                           GdkEventButton *event,
185                                           gpointer        data)
186 {
187   EggTreeMultiDndData *priv_data;
188   GSList *l;
189
190   priv_data = g_object_get_data (G_OBJECT (widget), EGG_TREE_MULTI_DND_STRING);
191
192   for (l = priv_data->event_list; l != NULL; l = l->next) 
193     gtk_propagate_event (widget, l->data);
194   
195   stop_drag_check (widget);
196
197   return FALSE;
198 }
199
200 static void
201 selection_foreach (GtkTreeModel *model,
202                    GtkTreePath  *path,
203                    GtkTreeIter  *iter,
204                    gpointer      data)
205 {
206   GList **list_ptr;
207
208   list_ptr = (GList **) data;
209
210   *list_ptr = g_list_prepend (*list_ptr, gtk_tree_row_reference_new (model, path));
211 }
212
213 static void
214 path_list_free (GList *path_list)
215 {
216   g_list_foreach (path_list, (GFunc) gtk_tree_row_reference_free, NULL);
217   g_list_free (path_list);
218 }
219
220 static void
221 set_context_data (GdkDragContext *context,
222                   GList          *path_list)
223 {
224   g_object_set_data_full (G_OBJECT (context),
225                           "egg-tree-view-multi-source-row",
226                           path_list,
227                           (GDestroyNotify) path_list_free);
228 }
229
230 static GList *
231 get_context_data (GdkDragContext *context)
232 {
233   return g_object_get_data (G_OBJECT (context),
234                             "egg-tree-view-multi-source-row");
235 }
236
237 /* CUT-N-PASTE from gtktreeview.c */
238 static TreeViewDragInfo*
239 get_info (GtkTreeView *tree_view)
240 {
241   return g_object_get_data (G_OBJECT (tree_view), "gtk-tree-view-drag-info");
242 }
243
244
245 static void
246 egg_tree_multi_drag_drag_data_get (GtkWidget        *widget,
247                                    GdkDragContext   *context,
248                                    GtkSelectionData *selection_data,
249                                    guint             info,
250                                    guint             time)
251 {
252   GtkTreeView *tree_view;
253   GtkTreeModel *model;
254   TreeViewDragInfo *di;
255   GList *path_list;
256
257   tree_view = GTK_TREE_VIEW (widget);
258
259   model = gtk_tree_view_get_model (tree_view);
260
261   if (model == NULL)
262     return;
263
264   di = get_info (GTK_TREE_VIEW (widget));
265
266   if (di == NULL)
267     return;
268
269   path_list = get_context_data (context);
270
271   if (path_list == NULL)
272     return;
273
274   /* We can implement the GTK_TREE_MODEL_ROW target generically for
275    * any model; for DragSource models there are some other targets
276    * we also support.
277    */
278
279   if (EGG_IS_TREE_MULTI_DRAG_SOURCE (model))
280     {
281       egg_tree_multi_drag_source_drag_data_get (EGG_TREE_MULTI_DRAG_SOURCE (model),
282                                                 path_list,
283                                                 selection_data);
284     }
285 }
286
287 static gboolean
288 egg_tree_multi_drag_motion_event (GtkWidget      *widget,
289                                   GdkEventMotion *event,
290                                   gpointer        data)
291 {
292   EggTreeMultiDndData *priv_data;
293
294   priv_data = g_object_get_data (G_OBJECT (widget), EGG_TREE_MULTI_DND_STRING);
295
296   if (gtk_drag_check_threshold (widget,
297                                 priv_data->x,
298                                 priv_data->y,
299                                 event->x,
300                                 event->y))
301     {
302       GList *path_list = NULL;
303       GtkTreeSelection *selection;
304       GtkTreeModel *model;
305       GdkDragContext *context;
306       TreeViewDragInfo *di;
307
308       di = get_info (GTK_TREE_VIEW (widget));
309
310       if (di == NULL)
311         return FALSE;
312       
313       selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
314       stop_drag_check (widget);
315       gtk_tree_selection_selected_foreach (selection, selection_foreach, &path_list);
316       path_list = g_list_reverse (path_list);
317       model = gtk_tree_view_get_model (GTK_TREE_VIEW (widget));
318       if (egg_tree_multi_drag_source_row_draggable (EGG_TREE_MULTI_DRAG_SOURCE (model), path_list))
319         {
320
321           context = gtk_drag_begin (widget,
322                                     gtk_drag_source_get_target_list (widget),
323                                     di->source_actions,
324                                     priv_data->pressed_button,
325                                     (GdkEvent*)event);
326           set_context_data (context, path_list);
327           gtk_drag_set_icon_default (context);
328
329         }
330       else
331         {
332           path_list_free (path_list);
333         }
334     }
335
336   return TRUE;
337 }
338
339 static gboolean
340 egg_tree_multi_drag_button_press_event (GtkWidget      *widget,
341                                         GdkEventButton *event,
342                                         gpointer        data)
343 {
344   GtkTreeView *tree_view;
345   GtkTreePath *path = NULL;
346   GtkTreeViewColumn *column = NULL;
347   gint cell_x, cell_y;
348   GtkTreeSelection *selection;
349   EggTreeMultiDndData *priv_data;
350
351   tree_view = GTK_TREE_VIEW (widget);
352   priv_data = g_object_get_data (G_OBJECT (tree_view), EGG_TREE_MULTI_DND_STRING);
353   if (priv_data == NULL)
354     {
355       priv_data = g_new0 (EggTreeMultiDndData, 1);
356       g_object_set_data (G_OBJECT (tree_view), EGG_TREE_MULTI_DND_STRING, priv_data);
357     }
358
359   if (g_slist_find (priv_data->event_list, event)) 
360     return FALSE;
361
362   if (priv_data->event_list) 
363     {
364       /* save the event to be propagated in order */
365       priv_data->event_list = g_slist_append (priv_data->event_list, gdk_event_copy ((GdkEvent*)event));
366       return TRUE;
367     }
368   
369   if (event->type == GDK_2BUTTON_PRESS)
370     return FALSE;
371
372   gtk_tree_view_get_path_at_pos (tree_view,
373                                  event->x, event->y,
374                                  &path, &column,
375                                  &cell_x, &cell_y);
376
377   selection = gtk_tree_view_get_selection (tree_view);
378
379   if (path && gtk_tree_selection_path_is_selected (selection, path))
380     {
381       priv_data->pressed_button = event->button;
382       priv_data->x = event->x;
383       priv_data->y = event->y;
384       priv_data->event_list = g_slist_append (priv_data->event_list, gdk_event_copy ((GdkEvent*)event));
385       priv_data->motion_notify_handler =
386         g_signal_connect (G_OBJECT (tree_view), "motion_notify_event", G_CALLBACK (egg_tree_multi_drag_motion_event), NULL);
387       priv_data->button_release_handler =
388         g_signal_connect (G_OBJECT (tree_view), "button_release_event", G_CALLBACK (egg_tree_multi_drag_button_release_event), NULL);
389
390       if (priv_data->drag_data_get_handler == 0) 
391         {
392           priv_data->drag_data_get_handler =
393             g_signal_connect (G_OBJECT (tree_view), "drag_data_get", G_CALLBACK (egg_tree_multi_drag_drag_data_get), NULL);
394         }
395
396       gtk_tree_path_free (path);
397       
398       return TRUE;
399     }
400
401   if (path) 
402     {
403       gtk_tree_path_free (path);
404     }
405
406   return FALSE;
407 }
408
409 void
410 egg_tree_multi_drag_add_drag_support (GtkTreeView *tree_view)
411 {
412   g_return_if_fail (GTK_IS_TREE_VIEW (tree_view));
413   g_signal_connect (G_OBJECT (tree_view), "button_press_event", G_CALLBACK (egg_tree_multi_drag_button_press_event), NULL);
414 }
415