No issues found
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 }