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