Set Nautilus-Actions as being the actual official product name
[nautilus-actions.git] / src / nact / nact-tree-model-dnd.c
blobf2337f85b40951381c98ab0cc511b6e9f3b4a1b0
1 /*
2 * Nautilus-Actions
3 * A Nautilus extension which offers configurable context menu actions.
5 * Copyright (C) 2005 The GNOME Foundation
6 * Copyright (C) 2006, 2007, 2008 Frederic Ruaudel and others (see AUTHORS)
7 * Copyright (C) 2009, 2010, 2011 Pierre Wieser and others (see AUTHORS)
9 * This Program 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.
14 * This Program 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
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public
20 * License along with this Library; see the file COPYING. If not,
21 * write to the Free Software Foundation, Inc., 59 Temple Place,
22 * Suite 330, Boston, MA 02111-1307, USA.
24 * Authors:
25 * Frederic Ruaudel <grumz@grumz.net>
26 * Rodrigo Moya <rodrigo@gnome-db.org>
27 * Pierre Wieser <pwieser@trychlos.org>
28 * ... and many others (see AUTHORS)
31 #ifdef HAVE_CONFIG_H
32 #include <config.h>
33 #endif
35 #include <gconf/gconf-client.h>
36 #include <glib/gi18n.h>
37 #include <string.h>
39 #include <api/na-core-utils.h>
40 #include <api/na-object-api.h>
42 #include <core/na-iprefs.h>
43 #include <core/na-gnome-vfs-uri.h>
44 #include <core/na-importer.h>
46 #include "nact-application.h"
47 #include "nact-clipboard.h"
48 #include "nact-iactions-list.h"
49 #include "nact-iprefs.h"
50 #include "nact-main-menubar-edit.h"
51 #include "nact-main-statusbar.h"
52 #include "nact-main-window.h"
53 #include "nact-tree-model.h"
54 #include "nact-tree-model-dnd.h"
55 #include "nact-tree-model-priv.h"
58 * call once egg_tree_multi_drag_add_drag_support( treeview ) at init time (before gtk_main)
60 * when we start with drag
61 * call once egg_tree_multi_dnd_on_button_press_event( treeview, event, drag_source )
62 * call many egg_tree_multi_dnd_on_motion_event( treeview, event, drag_source )
63 * until mouse quits the selected area
65 * as soon as mouse has quitted the selected area
66 * call once egg_tree_multi_dnd_stop_drag_check( treeview )
67 * call once nact_tree_model_imulti_drag_source_row_draggable: drag_source=0x92a0d70, path_list=0x9373c90
68 * call once nact_clipboard_on_drag_begin( treeview, context, main_window )
70 * when we drop (e.g. in Nautilus)
71 * call once egg_tree_multi_dnd_on_drag_data_get( treeview, context, selection_data, info=0, time )
72 * call once nact_tree_model_imulti_drag_source_drag_data_get( drag_source, context, selection_data, path_list, atom=XdndDirectSave0 )
73 * call once nact_tree_model_idrag_dest_drag_data_received
74 * call once nact_clipboard_on_drag_end( treeview, context, main_window )
76 * when we drop in Nautilus-Actions
77 * call once egg_tree_multi_dnd_on_drag_data_get( treeview, context, selection_data, info=0, time )
78 * call once nact_tree_model_imulti_drag_source_drag_data_get( drag_source, context, selection_data, path_list, atom=XdndNautilusActions )
79 * call once nact_clipboard_get_data_for_intern_use
80 * call once nact_tree_model_idrag_dest_drag_data_received
81 * call once nact_clipboard_on_drag_end( treeview, context, main_window )
84 #define MAX_XDS_ATOM_VAL_LEN 4096
85 #define TEXT_ATOM gdk_atom_intern( "text/plain", FALSE )
86 #define XDS_ATOM gdk_atom_intern( "XdndDirectSave0", FALSE )
87 #define XDS_FILENAME "xds.txt"
89 #define NACT_ATOM gdk_atom_intern( "XdndNautilusActions", FALSE )
91 /* as a dnd source, we provide
92 * - a special XdndNautilusAction format for internal move/copy
93 * - a XdndDirectSave, suitable for exporting to a file manager
94 * (note that Nautilus recognized the "XdndDirectSave0" format as XDS
95 * protocol)
96 * - a text (xml) format, to go to clipboard or a text editor
98 static GtkTargetEntry dnd_source_formats[] = {
99 { "XdndNautilusActions", GTK_TARGET_SAME_WIDGET, NACT_XCHANGE_FORMAT_NACT },
100 { "XdndDirectSave0", GTK_TARGET_OTHER_APP, NACT_XCHANGE_FORMAT_XDS },
101 { "application/xml", GTK_TARGET_OTHER_APP, NACT_XCHANGE_FORMAT_APPLICATION_XML },
102 { "text/plain", GTK_TARGET_OTHER_APP, NACT_XCHANGE_FORMAT_TEXT_PLAIN },
105 /* as a dnd dest, we accept
106 * - of course, the same special XdndNautilusAction format for internal move/copy
107 * - a list of uris, to be imported
108 * - a XML buffer, to be imported
109 * - a plain text, which we are goint to try to import as a XML description
111 GtkTargetEntry tree_model_dnd_dest_formats[] = {
112 { "XdndNautilusActions", 0, NACT_XCHANGE_FORMAT_NACT },
113 { "text/uri-list", 0, NACT_XCHANGE_FORMAT_URI_LIST },
114 { "application/xml", 0, NACT_XCHANGE_FORMAT_APPLICATION_XML },
115 { "text/plain", 0, NACT_XCHANGE_FORMAT_TEXT_PLAIN },
118 guint tree_model_dnd_dest_formats_count = G_N_ELEMENTS( tree_model_dnd_dest_formats );
120 static const gchar *st_refuse_drop_profile = N_( "Unable to drop a profile here" );
121 static const gchar *st_refuse_drop_item = N_( "Unable to drop an action or a menu here" );
122 static const gchar *st_parent_not_writable = N_( "Unable to drop here as parent is not writable" );
123 static const gchar *st_level_zero_not_writable = N_( "Unable to drop here as level zero is not writable" );
125 static gboolean drop_inside( NactTreeModel *model, GtkTreePath *dest, GtkSelectionData *selection_data );
126 static gboolean is_drop_possible( NactTreeModel *model, GtkTreePath *dest, NAObjectItem **parent );
127 static gboolean is_drop_possible_before_iter( NactTreeModel *model, GtkTreeIter *iter, NactMainWindow *window, NAObjectItem **parent );
128 static gboolean is_drop_possible_into_dest( NactTreeModel *model, GtkTreePath *dest, NactMainWindow *window, NAObjectItem **parent );
129 static void drop_inside_move_dest( NactTreeModel *model, GList *rows, GtkTreePath **dest );
130 static gboolean drop_uri_list( NactTreeModel *model, GtkTreePath *dest, GtkSelectionData *selection_data );
131 static NAObjectItem *is_dropped_already_exists( const NAObjectItem *importing, const NactMainWindow *window );
132 static char *get_xds_atom_value( GdkDragContext *context );
133 static gboolean is_parent_accept_new_childs( NactApplication *application, NactMainWindow *window, NAObjectItem *parent );
134 static guint target_atom_to_id( GdkAtom atom );
137 * nact_tree_model_dnd_idrag_dest_drag_data_received:
138 * @drag_dest:
139 * @dest:
140 * @selection_data:
142 * Called when a drop from the outside occurs in the treeview;
143 * this may be an import action, or a move/copy inside of the tree.
145 * Returns: %TRUE if the specified rows were successfully inserted at
146 * the given dest, %FALSE else.
148 * When importing:
149 * - selection=XdndSelection
150 * - target=text/uri-list
151 * - type=text/uri-list
153 * When moving/copy from the treeview to the treeview:
154 * - selection=XdndSelection
155 * - target=XdndNautilusActions
156 * - type=XdndNautilusActions
158 gboolean
159 nact_tree_model_dnd_idrag_dest_drag_data_received( GtkTreeDragDest *drag_dest, GtkTreePath *dest, GtkSelectionData *selection_data )
161 static const gchar *thisfn = "nact_tree_model_dnd_idrag_dest_drag_data_received";
162 gboolean result = FALSE;
163 gchar *atom_name;
164 guint info;
165 gchar *path_str;
166 GdkAtom selection_data_selection;
167 GdkAtom selection_data_target;
168 GdkAtom selection_data_type;
169 gint selection_data_format;
170 gint selection_data_length;
172 g_debug( "%s: drag_dest=%p, dest=%p, selection_data=%p", thisfn, ( void * ) drag_dest, ( void * ) dest, ( void * ) selection_data );
173 g_return_val_if_fail( NACT_IS_TREE_MODEL( drag_dest ), FALSE );
175 #if(( GTK_MAJOR_VERSION >= 2 && GTK_MINOR_VERSION >= 14 ) || GTK_MAJOR_VERSION >= 3 )
176 selection_data_selection = gtk_selection_data_get_selection( selection_data );
177 #else
178 selection_data_selection = selection_data->selection;
179 #endif
180 atom_name = gdk_atom_name( selection_data_selection );
181 g_debug( "%s: selection=%s", thisfn, atom_name );
182 g_free( atom_name );
184 #if(( GTK_MAJOR_VERSION >= 2 && GTK_MINOR_VERSION >= 14 ) || GTK_MAJOR_VERSION >= 3 )
185 selection_data_target = gtk_selection_data_get_target( selection_data );
186 #else
187 selection_data_target = selection_data->target;
188 #endif
189 atom_name = gdk_atom_name( selection_data_target );
190 g_debug( "%s: target=%s", thisfn, atom_name );
191 g_free( atom_name );
193 #if(( GTK_MAJOR_VERSION >= 2 && GTK_MINOR_VERSION >= 14 ) || GTK_MAJOR_VERSION >= 3 )
194 selection_data_type = gtk_selection_data_get_data_type( selection_data );
195 #else
196 selection_data_type = selection_data->type;
197 #endif
198 atom_name = gdk_atom_name( selection_data_type );
199 g_debug( "%s: type=%s", thisfn, atom_name );
200 g_free( atom_name );
202 #if(( GTK_MAJOR_VERSION >= 2 && GTK_MINOR_VERSION >= 14 ) || GTK_MAJOR_VERSION >= 3 )
203 selection_data_format = gtk_selection_data_get_format( selection_data );
204 selection_data_length = gtk_selection_data_get_length( selection_data );
205 #else
206 selection_data_format = selection_data->format;
207 selection_data_length = selection_data->length;
208 #endif
209 g_debug( "%s: format=%d, length=%d", thisfn, selection_data_format, selection_data_length );
211 info = target_atom_to_id( selection_data_type );
212 g_debug( "%s: info=%u", thisfn, info );
214 path_str = gtk_tree_path_to_string( dest );
215 g_debug( "%s: dest_path=%s", thisfn, path_str );
216 g_free( path_str );
218 switch( info ){
219 case NACT_XCHANGE_FORMAT_NACT:
220 result = drop_inside( NACT_TREE_MODEL( drag_dest ), dest, selection_data );
221 break;
223 /* drop some actions from outside
224 * most probably from the file manager as a list of uris
226 case NACT_XCHANGE_FORMAT_URI_LIST:
227 result = drop_uri_list( NACT_TREE_MODEL( drag_dest ), dest, selection_data );
228 break;
230 default:
231 break;
234 return( result );
238 * nact_tree_model_dnd_idrag_dest_row_drop_possible:
239 * @drag_dest:
240 * @dest_path:
241 * @selection_data:
243 * Seems to only be called when the drop in _on_ a row (a square is
244 * displayed), but not when dropped between two rows (a line is displayed),
245 * nor during the motion.
247 gboolean
248 nact_tree_model_dnd_idrag_dest_row_drop_possible( GtkTreeDragDest *drag_dest, GtkTreePath *dest_path, GtkSelectionData *selection_data )
250 static const gchar *thisfn = "nact_tree_model_dnd_idrag_dest_row_drop_possible";
252 g_debug( "%s: drag_dest=%p, dest_path=%p, selection_data=%p", thisfn, ( void * ) drag_dest, ( void * ) dest_path, ( void * ) selection_data );
254 return( TRUE );
258 * nact_tree_model_dnd_imulti_drag_source_drag_data_get:
259 * @context: contains
260 * - the suggested action, as choosen by the drop site,
261 * between those we have provided in imulti_drag_source_get_drag_actions()
262 * - the target folder (XDS protocol)
263 * @selection_data:
264 * @rows: list of row references which are to be dropped
265 * @info: the suggested format, as choosen by the drop site, between those
266 * we have provided in imulti_drag_source_get_target_list()
268 * This function is called when we release the selected items onto the
269 * destination
271 * NACT_XCHANGE_FORMAT_NACT:
272 * internal format for drag and drop inside the treeview:
273 * - copy in the clipboard the list of row references
274 * - selection data is empty
276 * NACT_XCHANGE_FORMAT_XDS:
277 * exchange format to drop to outside:
278 * - copy in the clipboard the list of row references
279 * - set the destination folder
280 * - selection data is 'success' or 'failure'
282 * NACT_XCHANGE_FORMAT_APPLICATION_XML:
283 * NACT_XCHANGE_FORMAT_TEXT_PLAIN:
284 * exchange format to export to outside:
285 * - do not use dnd clipboard
286 * - selection data receives the export in text format
288 * Returns: %TRUE if required data was actually provided by the source,
289 * %FALSE else.
291 gboolean
292 nact_tree_model_dnd_imulti_drag_source_drag_data_get( EggTreeMultiDragSource *drag_source,
293 GdkDragContext *context,
294 GtkSelectionData *selection_data,
295 GList *rows,
296 guint info )
298 static const gchar *thisfn = "nact_tree_model_dnd_imulti_drag_source_drag_data_get";
299 gchar *atom_name;
300 NactTreeModel *model;
301 gchar *data;
302 gboolean ret = FALSE;
303 gchar *dest_folder, *folder;
304 gboolean is_writable;
305 gboolean copy_data;
306 GdkAtom selection_data_target;
307 GdkDragAction context_suggested_action;
308 GdkDragAction context_selected_action;
310 #if(( GTK_MAJOR_VERSION >= 2 && GTK_MINOR_VERSION >= 14 ) || GTK_MAJOR_VERSION >= 3 )
311 selection_data_target = gtk_selection_data_get_target( selection_data );
312 #else
313 selection_data_target = selection_data->target;
314 #endif
316 #if(( GTK_MAJOR_VERSION >= 2 && GTK_MINOR_VERSION >= 22 ) || GTK_MAJOR_VERSION >= 3 )
317 context_suggested_action = gdk_drag_context_get_suggested_action( context );
318 context_selected_action = gdk_drag_context_get_selected_action( context );
319 #else
320 context_suggested_action = context->suggested_action;
321 context_selected_action = context->action;
322 #endif
324 atom_name = gdk_atom_name( selection_data_target );
325 g_debug( "%s: drag_source=%p, context=%p, action=%d, selection_data=%p, rows=%p, atom=%s",
326 thisfn, ( void * ) drag_source, ( void * ) context, ( int ) context_suggested_action,
327 ( void * ) selection_data, ( void * ) rows,
328 atom_name );
329 g_free( atom_name );
331 model = NACT_TREE_MODEL( drag_source );
332 g_return_val_if_fail( model->private->window, FALSE );
334 if( !model->private->dispose_has_run ){
336 if( !rows || !g_list_length( rows )){
337 return( FALSE );
340 switch( info ){
341 case NACT_XCHANGE_FORMAT_NACT:
342 copy_data = ( context_selected_action == GDK_ACTION_COPY );
343 gtk_selection_data_set( selection_data,
344 selection_data_target, 8, ( guchar * ) "", 0 );
345 nact_clipboard_dnd_set( model->private->clipboard, info, rows, NULL, copy_data );
346 ret = TRUE;
347 break;
349 case NACT_XCHANGE_FORMAT_XDS:
350 /* get the dest default filename as an uri
351 * e.g. file:///home/pierre/data/eclipse/nautilus-actions/trash/xds.txt
353 folder = get_xds_atom_value( context );
354 dest_folder = g_path_get_dirname( folder );
356 /* check that target folder is writable
358 is_writable = na_core_utils_dir_is_writable_uri( dest_folder );
359 g_debug( "%s: dest_folder=%s, is_writable=%s", thisfn, dest_folder, is_writable ? "True":"False" );
360 gtk_selection_data_set( selection_data,
361 selection_data_target, 8, ( guchar * )( is_writable ? "S" : "F" ), 1 );
363 if( is_writable ){
364 nact_clipboard_dnd_set( model->private->clipboard, info, rows, dest_folder, TRUE );
367 g_free( dest_folder );
368 g_free( folder );
369 ret = is_writable;
370 break;
372 case NACT_XCHANGE_FORMAT_APPLICATION_XML:
373 case NACT_XCHANGE_FORMAT_TEXT_PLAIN:
374 data = nact_clipboard_dnd_get_text( model->private->clipboard, rows );
375 gtk_selection_data_set( selection_data,
376 selection_data_target, 8, ( guchar * ) data, strlen( data ));
377 g_free( data );
378 ret = TRUE;
379 break;
381 default:
382 break;
386 return( ret );
390 * nact_tree_model_dnd_imulti_drag_source_drag_data_delete:
391 * @drag_source:
392 * @rows:
394 gboolean
395 nact_tree_model_dnd_imulti_drag_source_drag_data_delete( EggTreeMultiDragSource *drag_source, GList *rows )
397 static const gchar *thisfn = "nact_tree_model_dnd_imulti_drag_source_drag_data_delete";
399 g_debug( "%s: drag_source=%p, path_list=%p", thisfn, ( void * ) drag_source, ( void * ) rows );
401 return( TRUE );
405 * nact_tree_model_dnd_imulti_drag_source_get_drag_actions:
406 * @drag_source:
408 GdkDragAction
409 nact_tree_model_dnd_imulti_drag_source_get_drag_actions( EggTreeMultiDragSource *drag_source )
411 return( GDK_ACTION_COPY | GDK_ACTION_MOVE );
414 GtkTargetList *
415 nact_tree_model_dnd_imulti_drag_source_get_format_list( EggTreeMultiDragSource *drag_source )
417 GtkTargetList *target_list;
419 target_list = gtk_target_list_new( dnd_source_formats, G_N_ELEMENTS( dnd_source_formats ));
421 return( target_list );
425 * nact_tree_model_dnd_imulti_drag_source_row_draggable:
426 * @drag_source:
427 * @rows:
429 * All selectable rows are draggable.
430 * Nonetheless, it's a good place to store the dragged row references.
431 * We only make use of them in on_drag_motion handler.
433 gboolean
434 nact_tree_model_dnd_imulti_drag_source_row_draggable( EggTreeMultiDragSource *drag_source, GList *rows )
436 static const gchar *thisfn = "nact_tree_model_dnd_imulti_drag_source_row_draggable";
437 NactTreeModel *model;
438 GtkTreeModel *store;
439 GtkTreePath *path;
440 GtkTreeIter iter;
441 NAObject *object;
442 GList *it;
444 g_debug( "%s: drag_source=%p, rows=%p (%d items)",
445 thisfn, ( void * ) drag_source, ( void * ) rows, g_list_length( rows ));
447 g_return_val_if_fail( NACT_IS_TREE_MODEL( drag_source ), FALSE );
448 model = NACT_TREE_MODEL( drag_source );
450 if( !model->private->dispose_has_run ){
452 model->private->drag_has_profiles = FALSE;
453 store = gtk_tree_model_filter_get_model( GTK_TREE_MODEL_FILTER( model ));
455 for( it = rows ; it && !model->private->drag_has_profiles ; it = it->next ){
457 path = gtk_tree_row_reference_get_path(( GtkTreeRowReference * ) it->data );
458 gtk_tree_model_get_iter( store, &iter, path );
459 gtk_tree_model_get( store, &iter, IACTIONS_LIST_NAOBJECT_COLUMN, &object, -1 );
461 if( NA_IS_OBJECT_PROFILE( object )){
462 model->private->drag_has_profiles = TRUE;
465 g_object_unref( object );
466 gtk_tree_path_free( path );
470 return( TRUE );
474 * nact_tree_model_dnd_on_drag_begin:
475 * @widget: the GtkTreeView from which item is to be dragged.
476 * @context:
477 * @window: the parent #NactMainWindow instance.
479 * This function is called once, at the beginning of the drag operation,
480 * when we are dragging from the IActionsList treeview.
482 void
483 nact_tree_model_dnd_on_drag_begin( GtkWidget *widget, GdkDragContext *context, BaseWindow *window )
485 static const gchar *thisfn = "nact_tree_model_dnd_on_drag_begin";
486 NactTreeModel *model;
487 GdkWindow *context_source_window;
489 g_debug( "%s: widget=%p, context=%p, window=%p",
490 thisfn, ( void * ) widget, ( void * ) context, ( void * ) window );
492 model = NACT_TREE_MODEL( gtk_tree_view_get_model( GTK_TREE_VIEW( widget )));
493 g_return_if_fail( NACT_IS_TREE_MODEL( model ));
495 if( !model->private->dispose_has_run ){
497 model->private->drag_highlight = FALSE;
498 model->private->drag_drop = FALSE;
500 nact_clipboard_dnd_clear( model->private->clipboard );
502 #if(( GTK_MAJOR_VERSION >= 2 && GTK_MINOR_VERSION >= 22 ) || GTK_MAJOR_VERSION >= 3 )
503 context_source_window = gdk_drag_context_get_source_window( context );
504 #else
505 context_source_window = context->source_window;
506 #endif
508 gdk_property_change(
509 context_source_window,
510 XDS_ATOM, TEXT_ATOM, 8, GDK_PROP_MODE_REPLACE, ( guchar * ) XDS_FILENAME, strlen( XDS_FILENAME ));
515 * nact_tree_model_dnd_on_drag_end:
516 * @widget:
517 * @context:
518 * @window:
520 void
521 nact_tree_model_dnd_on_drag_end( GtkWidget *widget, GdkDragContext *context, BaseWindow *window )
523 static const gchar *thisfn = "nact_tree_model_dnd_on_drag_end";
524 NactTreeModel *model;
525 GdkWindow *context_source_window;
527 g_debug( "%s: widget=%p, context=%p, window=%p",
528 thisfn, ( void * ) widget, ( void * ) context, ( void * ) window );
530 model = NACT_TREE_MODEL( gtk_tree_view_get_model( GTK_TREE_VIEW( widget )));
531 g_return_if_fail( NACT_IS_TREE_MODEL( model ));
533 if( !model->private->dispose_has_run ){
535 nact_clipboard_dnd_drag_end( model->private->clipboard );
536 nact_clipboard_dnd_clear( model->private->clipboard );
538 #if(( GTK_MAJOR_VERSION >= 2 && GTK_MINOR_VERSION >= 22 ) || GTK_MAJOR_VERSION >= 3 )
539 context_source_window = gdk_drag_context_get_source_window( context );
540 #else
541 context_source_window = context->source_window;
542 #endif
544 gdk_property_delete( context_source_window, XDS_ATOM );
549 * called when a drop occurs in the treeview for a move/copy inside of
550 * the tree
552 * Returns: %TRUE if the specified rows were successfully inserted at
553 * the given dest, %FALSE else.
555 * The dest path is computed based on the current appearance of the list
556 * Drop should so occurs inside an inchanged list to keep a valid path
557 * in the case of a move, this leads to :
558 * 1) marks dragged items as 'to be deleted'
559 * 2) insert new dropped items
560 * 3) remove 'to be deleted' items
561 * -> not an easy idea as we want modify the id of all the dragged
562 * hierarchy
564 * adjusting the path: quid if the target dest is not at the same level
566 static gboolean
567 drop_inside( NactTreeModel *model, GtkTreePath *dest, GtkSelectionData *selection_data )
569 static const gchar *thisfn = "nact_tree_model_dnd_inside_drag_and_drop";
570 NactApplication *application;
571 NAUpdater *updater;
572 NactMainWindow *main_window;
573 NAObjectItem *parent;
574 gboolean copy_data;
575 GList *rows;
576 GtkTreePath *new_dest;
577 GtkTreePath *path;
578 NAObject *current;
579 NAObject *inserted;
580 GList *object_list, *it;
581 GtkTreeIter iter;
582 GList *deletable;
583 gboolean relabel;
585 application = NACT_APPLICATION( base_window_get_application( model->private->window ));
586 updater = nact_application_get_updater( application );
587 main_window = NACT_MAIN_WINDOW( base_application_get_main_window( BASE_APPLICATION( application )));
590 * NACT format (may embed profiles, or not)
591 * with profiles: only valid dest is inside an action
592 * without profile: only valid dest is outside (besides of) an action
594 parent = NULL;
595 rows = nact_clipboard_dnd_get_data( model->private->clipboard, &copy_data );
597 if( !is_drop_possible( model, dest, &parent )){
598 return( FALSE );
601 new_dest = gtk_tree_path_copy( dest );
602 if( !copy_data ){
603 drop_inside_move_dest( model, rows, &new_dest );
606 g_debug( "%s: rows has %d items, copy_data=%s", thisfn, g_list_length( rows ), copy_data ? "True":"False" );
607 object_list = NULL;
608 for( it = rows ; it ; it = it->next ){
609 path = gtk_tree_row_reference_get_path(( GtkTreeRowReference * ) it->data );
610 if( path ){
611 if( gtk_tree_model_get_iter( GTK_TREE_MODEL( model ), &iter, path )){
612 gtk_tree_model_get( GTK_TREE_MODEL( model ), &iter, IACTIONS_LIST_NAOBJECT_COLUMN, &current, -1 );
613 g_object_unref( current );
615 if( copy_data ){
616 inserted = ( NAObject * ) na_object_duplicate( current );
617 na_object_set_origin( inserted, NULL );
618 na_object_check_status( inserted );
620 } else {
621 inserted = na_object_ref( current );
622 deletable = g_list_prepend( NULL, inserted );
623 nact_iactions_list_bis_delete( NACT_IACTIONS_LIST( main_window ), deletable, FALSE );
624 g_list_free( deletable );
627 relabel = nact_main_menubar_edit_is_pasted_object_relabeled( inserted, NA_PIVOT( updater ));
628 na_object_prepare_for_paste( inserted, relabel, copy_data, parent );
629 object_list = g_list_prepend( object_list, inserted );
630 g_debug( "%s: dropped=%s", thisfn, na_object_get_label( inserted ));
632 gtk_tree_path_free( path );
635 object_list = g_list_reverse( object_list );
637 nact_iactions_list_bis_insert_at_path( NACT_IACTIONS_LIST( main_window ), object_list, new_dest );
639 if( gtk_tree_path_get_depth( new_dest ) == 1 ){
640 g_signal_emit_by_name( main_window, MAIN_WINDOW_SIGNAL_LEVEL_ZERO_ORDER_CHANGED, GINT_TO_POINTER( TRUE ));
643 g_list_foreach( object_list, ( GFunc ) na_object_object_unref, NULL );
644 g_list_free( object_list );
645 gtk_tree_path_free( new_dest );
647 g_list_foreach( rows, ( GFunc ) gtk_tree_row_reference_free, NULL );
648 g_list_free( rows );
650 return( TRUE );
654 * is a drop possible at given dest ?
656 * the only case where we would be led to have to modify the dest if
657 * we'd want be able to drop a profile into another profile, accepting
658 * it, actually dropping the profile just before the target
660 * -> it appears both clearer for the user interface and easyer from a
661 * code point of view to just refuse to drop a profile into a profile
663 * so this function is just to check if a drop is possible at the given
664 * dest
666 static gboolean
667 is_drop_possible( NactTreeModel *model, GtkTreePath *dest, NAObjectItem **parent )
669 gboolean drop_ok;
670 NactApplication *application;
671 NactMainWindow *main_window;
672 GtkTreeIter iter;
673 NAObjectItem *parent_dest;
675 drop_ok = FALSE;
676 parent_dest = NULL;
677 application = NACT_APPLICATION( base_window_get_application( model->private->window ));
678 main_window = NACT_MAIN_WINDOW( base_application_get_main_window( BASE_APPLICATION( application )));
680 /* if we can have an iter on given dest, then the dest already exists
681 * so dropped items should be of the same type that already existing
683 if( gtk_tree_model_get_iter( GTK_TREE_MODEL( model ), &iter, dest )){
684 drop_ok = is_drop_possible_before_iter( model, &iter, main_window, &parent_dest );
686 /* inserting at the end of the list
687 * parent_dest is NULL
689 } else if( gtk_tree_path_get_depth( dest ) == 1 ){
691 if( model->private->drag_has_profiles ){
692 nact_main_statusbar_display_with_timeout(
693 main_window, TREE_MODEL_STATUSBAR_CONTEXT, st_refuse_drop_profile );
695 } else {
696 drop_ok = TRUE;
699 /* we cannot have an iter on the dest: this means that we try to
700 * insert items into not-opened dest (an empty menu or an action with
701 * zero or one profile) : check what is the parent
703 } else {
704 drop_ok = is_drop_possible_into_dest( model, dest, main_window, &parent_dest );
707 if( drop_ok ){
708 drop_ok = is_parent_accept_new_childs( application, main_window, parent_dest );
711 if( drop_ok && parent ){
712 *parent = parent_dest;
715 return( drop_ok );
718 static gboolean
719 is_drop_possible_before_iter( NactTreeModel *model, GtkTreeIter *iter, NactMainWindow *window, NAObjectItem **parent )
721 static const gchar *thisfn = "nact_tree_model_dnd_is_drop_possible_before_iter";
722 gboolean drop_ok;
723 NAObject *object;
725 drop_ok = FALSE;
726 *parent = NULL;
728 gtk_tree_model_get( GTK_TREE_MODEL( model ), iter, IACTIONS_LIST_NAOBJECT_COLUMN, &object, -1 );
729 g_object_unref( object );
730 g_debug( "%s: current object at dest is %s", thisfn, G_OBJECT_TYPE_NAME( object ));
732 if( model->private->drag_has_profiles ){
734 if( NA_IS_OBJECT_PROFILE( object )){
735 drop_ok = TRUE;
736 *parent = na_object_get_parent( object );
738 } else {
739 /* unable to drop a profile here */
740 nact_main_statusbar_display_with_timeout(
741 window, TREE_MODEL_STATUSBAR_CONTEXT, st_refuse_drop_profile );
744 } else if( NA_IS_OBJECT_ITEM( object )){
745 drop_ok = TRUE;
746 *parent = na_object_get_parent( object );
748 } else {
749 /* unable to drop an action or a menu here */
750 nact_main_statusbar_display_with_timeout(
751 window, TREE_MODEL_STATUSBAR_CONTEXT, st_refuse_drop_item );
754 return( drop_ok );
757 static gboolean
758 is_drop_possible_into_dest( NactTreeModel *model, GtkTreePath *dest, NactMainWindow *window, NAObjectItem **parent )
760 static const gchar *thisfn = "nact_tree_model_dnd_is_drop_possible_into_dest";
761 gboolean drop_ok;
762 GtkTreePath *path;
763 GtkTreeIter iter;
764 NAObject *object;
766 drop_ok = FALSE;
767 *parent = NULL;
769 path = gtk_tree_path_copy( dest );
771 if( gtk_tree_path_up( path )){
772 if( gtk_tree_model_get_iter( GTK_TREE_MODEL( model ), &iter, path )){
773 gtk_tree_model_get( GTK_TREE_MODEL( model ), &iter, IACTIONS_LIST_NAOBJECT_COLUMN, &object, -1 );
774 g_object_unref( object );
775 g_debug( "%s: current object at parent dest is %s", thisfn, G_OBJECT_TYPE_NAME( object ));
777 if( model->private->drag_has_profiles ){
779 if( NA_IS_OBJECT_ACTION( object )){
780 drop_ok = TRUE;
781 *parent = NA_OBJECT_ITEM( object );
783 } else {
784 nact_main_statusbar_display_with_timeout(
785 window, TREE_MODEL_STATUSBAR_CONTEXT, st_refuse_drop_profile );
788 } else if( NA_IS_OBJECT_MENU( object )){
789 drop_ok = TRUE;
790 *parent = na_object_get_parent( object );
792 } else {
793 nact_main_statusbar_display_with_timeout(
794 window, TREE_MODEL_STATUSBAR_CONTEXT, st_refuse_drop_item );
799 gtk_tree_path_free( path );
801 return( drop_ok );
804 static void
805 drop_inside_move_dest( NactTreeModel *model, GList *rows, GtkTreePath **dest )
807 GList *it;
808 GtkTreePath *path;
809 gint before;
810 gint i;
811 gint *indices;
813 g_return_if_fail( dest );
815 before = 0;
816 for( it = rows ; it ; it = it->next ){
817 path = gtk_tree_row_reference_get_path(( GtkTreeRowReference * ) it->data );
818 if( path ){
819 if( gtk_tree_path_get_depth( path ) == 1 && gtk_tree_path_compare( path, *dest ) == -1 ){
820 before += 1;
822 gtk_tree_path_free( path );
826 g_debug( "drop_inside_move_dest: before=%d", before );
827 g_debug( "drop_inside_move_dest: dest=%s", gtk_tree_path_to_string( *dest ));
829 if( before ){
830 indices = gtk_tree_path_get_indices( *dest );
831 indices[0] -= before;
832 path = gtk_tree_path_new_from_indices( indices[0], -1 );
833 for( i = 1 ; i < gtk_tree_path_get_depth( *dest ); ++i ){
834 gtk_tree_path_append_index( path, indices[i] );
836 gtk_tree_path_free( *dest );
837 *dest = gtk_tree_path_copy( path );
838 gtk_tree_path_free( path );
841 g_debug( "drop_inside_move_dest: dest=%s", gtk_tree_path_to_string( *dest ));
845 * called when a drop from the outside occurs in the treeview
847 * Returns: %TRUE if the specified rows were successfully inserted at
848 * the given dest, %FALSE else.
850 * URI format only involves actions or menus
851 * so ony valid dest in outside (besides of) an action
853 static gboolean
854 drop_uri_list( NactTreeModel *model, GtkTreePath *dest, GtkSelectionData *selection_data )
856 static const gchar *thisfn = "nact_tree_model_dnd_drop_uri_list";
857 gboolean drop_done;
858 NactApplication *application;
859 NAUpdater *updater;
860 NactMainWindow *main_window;
861 NAImporterParms parms;
862 GConfClient *gconf;
863 GList *it;
864 guint count;
865 GString *str;
866 GSList *im;
867 GList *imported;
868 const gchar *selection_data_data;
870 gchar *dest_str = gtk_tree_path_to_string( dest );
871 g_debug( "%s: model=%p, dest=%p (%s), selection_data=%p",
872 thisfn, ( void * ) model, ( void * ) dest, dest_str, ( void * ) selection_data );
873 g_free( dest_str );
875 drop_done = FALSE;
876 model->private->drag_has_profiles = FALSE;
878 if( !is_drop_possible( model, dest, NULL )){
879 return( FALSE );
882 application = NACT_APPLICATION( base_window_get_application( model->private->window ));
883 updater = nact_application_get_updater( application );
884 main_window = NACT_MAIN_WINDOW( base_application_get_main_window( BASE_APPLICATION( application )));
886 #if(( GTK_MAJOR_VERSION >= 2 && GTK_MINOR_VERSION >= 14 ) || GTK_MAJOR_VERSION >= 3 )
887 selection_data_data = ( const gchar * ) gtk_selection_data_get_data( selection_data );
888 #else
889 selection_data_data = ( const gchar * ) selection_data->data;
890 #endif
891 g_debug( "%s", selection_data_data );
893 parms.parent = base_window_get_toplevel( BASE_WINDOW( main_window ));
894 parms.uris = g_slist_reverse( na_core_utils_slist_from_split( selection_data_data, "\r\n" ));
896 gconf = gconf_client_get_default();
897 parms.mode = na_iprefs_get_import_mode( gconf, IPREFS_IMPORT_ITEMS_IMPORT_MODE );
898 g_object_unref( gconf );
900 parms.check_fn = ( NAIImporterCheckFn ) is_dropped_already_exists;
901 parms.check_fn_data = main_window;
902 parms.results = NULL;
904 na_importer_import_from_list( NA_PIVOT( updater ), &parms );
906 /* analysing output results
907 * - first line of first message is displayed in status bar
908 * - simultaneously build the concatenation of all lines of messages
909 * - simultaneously build the list of imported items
911 count = 0;
912 str = g_string_new( "" );
913 imported = NULL;
915 for( it = parms.results ; it ; it = it->next ){
916 NAImporterResult *result = ( NAImporterResult * ) it->data;
918 if( result->messages ){
919 if( count == 0 ){
920 nact_main_statusbar_display_with_timeout(
921 main_window,
922 TREE_MODEL_STATUSBAR_CONTEXT,
923 result->messages->data );
925 count += 1;
926 for( im = result->messages ; im ; im = im->next ){
927 g_string_append_printf( str, "%s\n", ( const gchar * ) im->data );
931 if( result->imported ){
932 imported = g_list_prepend( imported, result->imported );
936 /* if there is more than one message, display them in a dialog box
938 if( count > 1 ){
939 GtkMessageDialog *dialog = GTK_MESSAGE_DIALOG( gtk_message_dialog_new(
940 parms.parent,
941 GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE,
942 "%s", _( "Some messages have occurred during drop operation." )));
943 gtk_message_dialog_format_secondary_markup( dialog, "%s", str->str );
946 g_string_free( str, TRUE );
948 /* check status of newly imported items, and insert them in the list view
950 for( it = imported ; it ; it = it->next ){
951 na_object_check_status( it->data );
952 na_object_dump( it->data );
953 drop_done = TRUE;
956 nact_iactions_list_bis_insert_at_path( NACT_IACTIONS_LIST( main_window ), imported, dest );
957 nact_tree_model_dump( model );
959 na_object_unref_items( imported );
960 na_core_utils_slist_free( parms.uris );
962 for( it = parms.results ; it ; it = it->next ){
963 na_importer_free_result( it->data );
965 g_list_free( parms.results );
967 return( drop_done );
970 static NAObjectItem *
971 is_dropped_already_exists( const NAObjectItem *importing, const NactMainWindow *window )
973 gchar *id = na_object_get_id( importing );
974 NAObjectItem *exists = nact_main_window_get_item( window, id );
975 g_free( id );
977 return( exists );
981 * this function works well, but only called from on_drag_motion handler...
983 /*static gboolean
984 is_row_drop_possible( NactTreeModel *model, GtkTreePath *path, GtkTreeViewDropPosition pos )
986 gboolean ok = FALSE;
987 GtkTreeModel *store;
988 GtkTreeIter iter;
989 NAObject *object;
991 store = gtk_tree_model_filter_get_model( GTK_TREE_MODEL_FILTER( model ));
992 gtk_tree_model_get_iter( store, &iter, path );
993 gtk_tree_model_get( store, &iter, IACTIONS_LIST_NAOBJECT_COLUMN, &object, -1 );
995 if( model->private->drag_has_profiles ){
996 if( NA_IS_OBJECT_PROFILE( object )){
997 ok = TRUE;
998 } else if( NA_IS_OBJECT_ACTION( object )){
999 ok = ( pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE || pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER );
1001 } else {
1002 if( NA_IS_OBJECT_ITEM( object )){
1003 ok = TRUE;
1007 g_object_unref( object );
1008 return( ok );
1012 * called when the user moves into the target widget
1013 * returns TRUE if a drop zone
1015 #if 0
1016 static gboolean
1017 on_drag_motion( GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, BaseWindow *window )
1019 NactTreeModel *model;
1020 gboolean ok = FALSE;
1021 GtkTreePath *path;
1022 GtkTreeViewDropPosition pos;
1024 model = NACT_TREE_MODEL( gtk_tree_view_get_model( GTK_TREE_VIEW( widget )));
1025 g_return_val_if_fail( NACT_IS_TREE_MODEL( model ), FALSE );
1027 if( !model->private->dispose_has_run ){
1029 if( !model->private->drag_highlight ){
1030 model->private->drag_highlight = TRUE;
1031 gtk_drag_highlight( widget );
1034 /*target = gtk_drag_dest_find_target( widget, context, NULL );
1035 if( target == GDK_NONE ){
1036 gdk_drag_status( context, 0, time );
1037 } else {
1038 gtk_drag_get_data( widget, context, target, time );
1041 if( gtk_tree_view_get_dest_row_at_pos( GTK_TREE_VIEW( widget ), x, y, &path, &pos )){
1042 ok = is_row_drop_possible( model, path, pos );
1043 if( ok ){
1044 gdk_drag_status( context, 0, time );
1048 gtk_tree_path_free( path );
1049 g_debug( "nact_tree_model_on_drag_motion: ok=%s, pos=%d", ok ? "True":"False", pos );
1052 return( ok );
1054 #endif
1057 * called when the user drops the data
1058 * returns TRUE if a drop zone
1060 /*static gboolean
1061 on_drag_drop( GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, BaseWindow *window )
1063 NactTreeModel *model;
1065 model = NACT_TREE_MODEL( gtk_tree_view_get_model( GTK_TREE_VIEW( widget )));
1066 g_return_val_if_fail( NACT_IS_TREE_MODEL( model ), FALSE );
1068 if( !model->private->dispose_has_run ){
1070 model->private->drag_drop = TRUE;
1073 return( TRUE );
1076 /* The following function taken from bugzilla
1077 * (http://bugzilla.gnome.org/attachment.cgi?id=49362&action=view)
1078 * Author: Christian Neumair
1079 * Copyright: 2005 Free Software Foundation, Inc
1080 * License: GPL
1082 * On a 32-bits system:
1083 * get_xds_atom_value: actual_length=63, actual_length=15
1084 * get_xds_atom_value: ret=file:///home/pierre/data/eclipse/nautilus-actions/trash/xds.txt0x8299
1085 * get_xds_atom_value: dup=file:///home/pierre/data/eclipse/nautilus-actions/trash/xds.txt
1086 * get_xds_atom_value: ret=file:///home/pi
1088 * idem on a 64bits system.
1090 static char *
1091 get_xds_atom_value( GdkDragContext *context )
1093 gchar *ret;
1094 gint actual_length;
1095 GdkWindow *context_source_window;
1097 #if(( GTK_MAJOR_VERSION >= 2 && GTK_MINOR_VERSION >= 22 ) || GTK_MAJOR_VERSION >= 3 )
1098 context_source_window = gdk_drag_context_get_source_window( context );
1099 #else
1100 context_source_window = context->source_window;
1101 #endif
1103 g_return_val_if_fail( context != NULL, NULL );
1104 g_return_val_if_fail( context_source_window != NULL, NULL );
1106 gdk_property_get( context_source_window, /* a GdkWindow */
1107 XDS_ATOM, /* the property to retrieve */
1108 TEXT_ATOM, /* the desired property type */
1109 0, /* offset (in 4 bytes chunks) */
1110 MAX_XDS_ATOM_VAL_LEN, /* max length (in 4 bytes chunks) */
1111 FALSE, /* whether to delete the property */
1112 NULL, /* actual property type */
1113 NULL, /* actual format */
1114 &actual_length, /* actual length (in 4 bytes chunks) */
1115 ( guchar ** ) &ret ); /* data pointer */
1117 ret[actual_length] = '\0';
1119 return( ret );
1123 * when dropping something somewhere, we must ensure that we will be able
1124 * to register the new child
1126 static gboolean
1127 is_parent_accept_new_childs( NactApplication *application, NactMainWindow *window, NAObjectItem *parent )
1129 gboolean accept_ok;
1130 NAUpdater *updater;
1132 accept_ok = FALSE;
1133 updater = nact_application_get_updater( application );
1135 /* inserting as a level zero item
1136 * ensure that level zero is writable
1138 if( parent == NULL ){
1140 if( na_pivot_is_level_zero_writable( NA_PIVOT( updater ))){
1141 accept_ok = TRUE;
1143 } else {
1144 nact_main_statusbar_display_with_timeout(
1145 window, TREE_MODEL_STATUSBAR_CONTEXT, st_level_zero_not_writable );
1148 /* see if the parent is writable
1150 } else if( na_updater_is_item_writable( updater, parent, NULL )){
1151 accept_ok = TRUE;
1153 } else {
1154 nact_main_statusbar_display_with_timeout(
1155 window, TREE_MODEL_STATUSBAR_CONTEXT, st_parent_not_writable );
1158 return( accept_ok );
1161 static guint
1162 target_atom_to_id( GdkAtom atom )
1164 gint i;
1165 guint info = 0;
1166 gchar *atom_name;
1168 atom_name = gdk_atom_name( atom );
1169 for( i = 0 ; i < tree_model_dnd_dest_formats_count ; ++i ){
1170 if( !g_ascii_strcasecmp( tree_model_dnd_dest_formats[i].target, atom_name )){
1171 info = tree_model_dnd_dest_formats[i].info;
1172 break;
1175 g_free( atom_name );
1176 return( info );