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