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