na_updater_is_item_writable is renamed as na_updater_check_item_writability_status
[nautilus-actions.git] / src / nact / nact-tree-model-dnd.c
blob0943e360471f3f83f0e03ad955bb0ee41dcc188f
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 <glib/gi18n.h>
36 #include <libintl.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-main-statusbar.h"
49 #include "nact-main-window.h"
50 #include "nact-tree-model.h"
51 #include "nact-tree-model-priv.h"
52 #include "nact-tree-ieditable.h"
55 * call once egg_tree_multi_drag_add_drag_support( treeview ) at init time (before gtk_main)
57 * when we start with drag
58 * call once egg_tree_multi_dnd_on_button_press_event( treeview, event, drag_source )
59 * call many egg_tree_multi_dnd_on_motion_event( treeview, event, drag_source )
60 * until mouse quits the selected area
62 * as soon as mouse has quitted the selected area
63 * call once egg_tree_multi_dnd_stop_drag_check( treeview )
64 * call once nact_tree_model_imulti_drag_source_row_draggable: drag_source=0x92a0d70, path_list=0x9373c90
65 * call once nact_clipboard_on_drag_begin( treeview, context, main_window )
67 * when we drop (e.g. in Nautilus)
68 * call once egg_tree_multi_dnd_on_drag_data_get( treeview, context, selection_data, info=0, time )
69 * call once nact_tree_model_imulti_drag_source_drag_data_get( drag_source, context, selection_data, path_list, atom=XdndDirectSave0 )
70 * call once nact_tree_model_idrag_dest_drag_data_received
71 * call once nact_clipboard_on_drag_end( treeview, context, main_window )
73 * when we drop in Nautilus-Actions
74 * call once egg_tree_multi_dnd_on_drag_data_get( treeview, context, selection_data, info=0, time )
75 * call once nact_tree_model_imulti_drag_source_drag_data_get( drag_source, context, selection_data, path_list, atom=XdndNautilusActions )
76 * call once nact_clipboard_get_data_for_intern_use
77 * call once nact_tree_model_idrag_dest_drag_data_received
78 * call once nact_clipboard_on_drag_end( treeview, context, main_window )
81 #define MAX_XDS_ATOM_VAL_LEN 4096
82 #define TEXT_ATOM gdk_atom_intern( "text/plain", FALSE )
83 #define XDS_ATOM gdk_atom_intern( "XdndDirectSave0", FALSE )
84 #define XDS_FILENAME "xds.txt"
86 #define NACT_ATOM gdk_atom_intern( "XdndNautilusActions", FALSE )
88 /* as a dnd source, we provide
89 * - a special XdndNautilusAction format for internal move/copy
90 * - a XdndDirectSave, suitable for exporting to a file manager
91 * (note that Nautilus recognized the "XdndDirectSave0" format as XDS
92 * protocol)
93 * - a text (xml) format, to go to clipboard or a text editor
95 static GtkTargetEntry dnd_source_formats[] = {
96 { "XdndNautilusActions", GTK_TARGET_SAME_WIDGET, NACT_XCHANGE_FORMAT_NACT },
97 { "XdndDirectSave0", GTK_TARGET_OTHER_APP, NACT_XCHANGE_FORMAT_XDS },
98 { "application/xml", GTK_TARGET_OTHER_APP, NACT_XCHANGE_FORMAT_APPLICATION_XML },
99 { "text/plain", GTK_TARGET_OTHER_APP, NACT_XCHANGE_FORMAT_TEXT_PLAIN },
102 /* as a dnd dest, we accept
103 * - of course, the same special XdndNautilusAction format for internal move/copy
104 * - a list of uris, to be imported
105 * - a XML buffer, to be imported
106 * - a plain text, which we are goint to try to import as a XML description
108 GtkTargetEntry tree_model_dnd_dest_formats[] = {
109 { "XdndNautilusActions", 0, NACT_XCHANGE_FORMAT_NACT },
110 { "text/uri-list", 0, NACT_XCHANGE_FORMAT_URI_LIST },
111 { "application/xml", 0, NACT_XCHANGE_FORMAT_APPLICATION_XML },
112 { "text/plain", 0, NACT_XCHANGE_FORMAT_TEXT_PLAIN },
115 guint tree_model_dnd_dest_formats_count = G_N_ELEMENTS( tree_model_dnd_dest_formats );
117 static const gchar *st_refuse_drop_profile = N_( "Unable to drop a profile here" );
118 static const gchar *st_refuse_drop_item = N_( "Unable to drop an action or a menu here" );
119 static const gchar *st_parent_not_writable = N_( "Unable to drop here as parent is not writable" );
120 static const gchar *st_level_zero_not_writable = N_( "Unable to drop here as level zero is not writable" );
122 static gboolean drop_inside( NactTreeModel *model, GtkTreePath *dest, GtkSelectionData *selection_data );
123 static gboolean is_drop_possible( NactTreeModel *model, GtkTreePath *dest, NAObjectItem **parent );
124 static gboolean is_drop_possible_before_iter( NactTreeModel *model, GtkTreeIter *iter, NactMainWindow *window, NAObjectItem **parent );
125 static gboolean is_drop_possible_into_dest( NactTreeModel *model, GtkTreePath *dest, NactMainWindow *window, NAObjectItem **parent );
126 static void drop_inside_move_dest( NactTreeModel *model, GList *rows, GtkTreePath **dest );
127 static gboolean drop_uri_list( NactTreeModel *model, GtkTreePath *dest, GtkSelectionData *selection_data );
128 static NAObjectItem *is_dropped_already_exists( const NAObjectItem *importing, const NactMainWindow *window );
129 static char *get_xds_atom_value( GdkDragContext *context );
130 static gboolean is_parent_accept_new_children( NactApplication *application, NactMainWindow *window, NAObjectItem *parent );
131 static guint target_atom_to_id( GdkAtom atom );
134 * nact_tree_model_dnd_idrag_dest_drag_data_received:
135 * @drag_dest:
136 * @dest:
137 * @selection_data:
139 * Called when a drop from the outside occurs in the treeview;
140 * this may be an import action, or a move/copy inside of the tree.
142 * Returns: %TRUE if the specified rows were successfully inserted at
143 * the given dest, %FALSE else.
145 * When importing:
146 * - selection=XdndSelection
147 * - target=text/uri-list
148 * - type=text/uri-list
150 * When moving/copy from the treeview to the treeview:
151 * - selection=XdndSelection
152 * - target=XdndNautilusActions
153 * - type=XdndNautilusActions
155 gboolean
156 nact_tree_model_dnd_idrag_dest_drag_data_received( GtkTreeDragDest *drag_dest, GtkTreePath *dest, GtkSelectionData *selection_data )
158 static const gchar *thisfn = "nact_tree_model_dnd_idrag_dest_drag_data_received";
159 gboolean result = FALSE;
160 gchar *atom_name;
161 guint info;
162 gchar *path_str;
163 GdkAtom selection_data_selection;
164 GdkAtom selection_data_target;
165 GdkAtom selection_data_type;
166 gint selection_data_format;
167 gint selection_data_length;
169 g_debug( "%s: drag_dest=%p, dest=%p, selection_data=%p", thisfn, ( void * ) drag_dest, ( void * ) dest, ( void * ) selection_data );
170 g_return_val_if_fail( NACT_IS_TREE_MODEL( drag_dest ), FALSE );
172 /* gtk_selection_data_get_data() appears with Gtk+ 2.14.0 release on 2008-09-04
173 * see http://git.gnome.org/browse/gtk+/commit/?id=9eae7a1d2e7457d67ba00bb8c35775c1523fa186
175 #if GTK_CHECK_VERSION( 2, 14, 0 )
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_CHECK_VERSION( 2, 14, 0 )
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_CHECK_VERSION( 2, 14, 0 )
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_CHECK_VERSION( 2, 14, 0 )
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 chosen 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 chosen 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_CHECK_VERSION( 2, 14, 0 )
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_CHECK_VERSION( 2, 22, 0 )
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, TREE_COLUMN_NAOBJECT, &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_CHECK_VERSION( 2, 22, 0 )
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_CHECK_VERSION( 2, 22, 0 )
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 static gboolean
556 drop_inside( NactTreeModel *model, GtkTreePath *dest, GtkSelectionData *selection_data )
558 static const gchar *thisfn = "nact_tree_model_dnd_inside_drag_and_drop";
559 NactApplication *application;
560 NAUpdater *updater;
561 NactMainWindow *main_window;
562 NAObjectItem *parent;
563 gboolean copy_data;
564 GList *rows;
565 GtkTreePath *new_dest;
566 GtkTreePath *path;
567 NAObject *current;
568 NAObject *inserted;
569 GList *object_list, *it;
570 GtkTreeIter iter;
571 GList *deletable;
572 gboolean relabel;
573 NactTreeView *items_view;
575 application = NACT_APPLICATION( base_window_get_application( model->private->window ));
576 updater = nact_application_get_updater( application );
578 g_return_val_if_fail( NACT_IS_MAIN_WINDOW( model->private->window ), FALSE );
579 main_window = NACT_MAIN_WINDOW( model->private->window );
580 items_view = nact_main_window_get_items_view( main_window );
583 * NACT format (may embed profiles, or not)
584 * with profiles: only valid dest is inside an action
585 * without profile: only valid dest is outside (besides of) an action
587 parent = NULL;
588 rows = nact_clipboard_dnd_get_data( model->private->clipboard, &copy_data );
590 if( !is_drop_possible( model, dest, &parent )){
591 return( FALSE );
594 new_dest = gtk_tree_path_copy( dest );
595 if( !copy_data ){
596 drop_inside_move_dest( model, rows, &new_dest );
599 g_debug( "%s: rows has %d items, copy_data=%s", thisfn, g_list_length( rows ), copy_data ? "True":"False" );
600 object_list = NULL;
601 for( it = rows ; it ; it = it->next ){
602 path = gtk_tree_row_reference_get_path(( GtkTreeRowReference * ) it->data );
603 if( path ){
604 if( gtk_tree_model_get_iter( GTK_TREE_MODEL( model ), &iter, path )){
605 gtk_tree_model_get( GTK_TREE_MODEL( model ), &iter, TREE_COLUMN_NAOBJECT, &current, -1 );
606 g_object_unref( current );
608 if( copy_data ){
609 inserted = ( NAObject * ) na_object_duplicate( current );
610 na_object_set_origin( inserted, NULL );
611 na_object_check_status( inserted );
612 relabel = na_updater_should_pasted_be_relabeled( updater, inserted );
614 } else {
615 inserted = na_object_ref( current );
616 deletable = g_list_prepend( NULL, inserted );
617 nact_tree_ieditable_delete( NACT_TREE_IEDITABLE( items_view ), deletable, TREE_OPE_MOVE );
618 g_list_free( deletable );
619 relabel = FALSE;
622 na_object_prepare_for_paste( inserted, relabel, copy_data, parent );
623 object_list = g_list_prepend( object_list, inserted );
624 g_debug( "%s: dropped=%s", thisfn, na_object_get_label( inserted ));
626 gtk_tree_path_free( path );
629 object_list = g_list_reverse( object_list );
631 nact_tree_ieditable_insert_at_path( NACT_TREE_IEDITABLE( items_view ), object_list, new_dest );
633 na_object_free_items( object_list );
634 gtk_tree_path_free( new_dest );
636 g_list_foreach( rows, ( GFunc ) gtk_tree_row_reference_free, NULL );
637 g_list_free( rows );
639 return( TRUE );
643 * is a drop possible at given dest ?
645 * the only case where we would be led to have to modify the dest if
646 * we'd want be able to drop a profile into another profile, accepting
647 * it, actually dropping the profile just before the target
649 * -> it appears both clearer for the user interface and easyer from a
650 * code point of view to just refuse to drop a profile into a profile
652 * so this function is just to check if a drop is possible at the given
653 * dest
655 static gboolean
656 is_drop_possible( NactTreeModel *model, GtkTreePath *dest, NAObjectItem **parent )
658 gboolean drop_ok;
659 NactApplication *application;
660 NactMainWindow *main_window;
661 GtkTreeIter iter;
662 NAObjectItem *parent_dest;
664 drop_ok = FALSE;
665 parent_dest = NULL;
666 application = NACT_APPLICATION( base_window_get_application( model->private->window ));
668 g_return_val_if_fail( NACT_IS_MAIN_WINDOW( model->private->window ), FALSE );
669 main_window = NACT_MAIN_WINDOW( model->private->window );
671 /* if we can have an iter on given dest, then the dest already exists
672 * so dropped items should be of the same type that already existing
674 if( gtk_tree_model_get_iter( GTK_TREE_MODEL( model ), &iter, dest )){
675 drop_ok = is_drop_possible_before_iter( model, &iter, main_window, &parent_dest );
677 /* inserting at the end of the list
678 * parent_dest is NULL
680 } else if( gtk_tree_path_get_depth( dest ) == 1 ){
682 if( model->private->drag_has_profiles ){
683 nact_main_statusbar_display_with_timeout(
684 main_window, TREE_MODEL_STATUSBAR_CONTEXT, gettext( st_refuse_drop_profile ));
686 } else {
687 drop_ok = TRUE;
690 /* we cannot have an iter on the dest: this means that we try to
691 * insert items into not-opened dest (an empty menu or an action with
692 * zero or one profile) : check what is the parent
694 } else {
695 drop_ok = is_drop_possible_into_dest( model, dest, main_window, &parent_dest );
698 if( drop_ok ){
699 drop_ok = is_parent_accept_new_children( application, main_window, parent_dest );
702 if( drop_ok && parent ){
703 *parent = parent_dest;
706 return( drop_ok );
709 static gboolean
710 is_drop_possible_before_iter( NactTreeModel *model, GtkTreeIter *iter, NactMainWindow *window, NAObjectItem **parent )
712 static const gchar *thisfn = "nact_tree_model_dnd_is_drop_possible_before_iter";
713 gboolean drop_ok;
714 NAObject *object;
716 drop_ok = FALSE;
717 *parent = NULL;
719 gtk_tree_model_get( GTK_TREE_MODEL( model ), iter, TREE_COLUMN_NAOBJECT, &object, -1 );
720 g_object_unref( object );
721 g_debug( "%s: current object at dest is %s", thisfn, G_OBJECT_TYPE_NAME( object ));
723 if( model->private->drag_has_profiles ){
725 if( NA_IS_OBJECT_PROFILE( object )){
726 drop_ok = TRUE;
727 *parent = na_object_get_parent( object );
729 } else {
730 /* unable to drop a profile here */
731 nact_main_statusbar_display_with_timeout(
732 window, TREE_MODEL_STATUSBAR_CONTEXT, gettext( st_refuse_drop_profile ));
735 } else if( NA_IS_OBJECT_ITEM( object )){
736 drop_ok = TRUE;
737 *parent = na_object_get_parent( object );
739 } else {
740 /* unable to drop an action or a menu here */
741 nact_main_statusbar_display_with_timeout(
742 window, TREE_MODEL_STATUSBAR_CONTEXT, gettext( st_refuse_drop_item ));
745 return( drop_ok );
748 static gboolean
749 is_drop_possible_into_dest( NactTreeModel *model, GtkTreePath *dest, NactMainWindow *window, NAObjectItem **parent )
751 static const gchar *thisfn = "nact_tree_model_dnd_is_drop_possible_into_dest";
752 gboolean drop_ok;
753 GtkTreePath *path;
754 GtkTreeIter iter;
755 NAObject *object;
757 drop_ok = FALSE;
758 *parent = NULL;
760 path = gtk_tree_path_copy( dest );
762 if( gtk_tree_path_up( path )){
763 if( gtk_tree_model_get_iter( GTK_TREE_MODEL( model ), &iter, path )){
764 gtk_tree_model_get( GTK_TREE_MODEL( model ), &iter, TREE_COLUMN_NAOBJECT, &object, -1 );
765 g_object_unref( object );
766 g_debug( "%s: current object at parent dest is %s", thisfn, G_OBJECT_TYPE_NAME( object ));
768 if( model->private->drag_has_profiles ){
770 if( NA_IS_OBJECT_ACTION( object )){
771 drop_ok = TRUE;
772 *parent = NA_OBJECT_ITEM( object );
774 } else {
775 nact_main_statusbar_display_with_timeout(
776 window, TREE_MODEL_STATUSBAR_CONTEXT, gettext( st_refuse_drop_profile ));
779 } else if( NA_IS_OBJECT_MENU( object )){
780 drop_ok = TRUE;
781 *parent = na_object_get_parent( object );
783 } else {
784 nact_main_statusbar_display_with_timeout(
785 window, TREE_MODEL_STATUSBAR_CONTEXT, gettext( st_refuse_drop_item ));
790 gtk_tree_path_free( path );
792 return( drop_ok );
795 static void
796 drop_inside_move_dest( NactTreeModel *model, GList *rows, GtkTreePath **dest )
798 GList *it;
799 GtkTreePath *path;
800 gint before;
801 gint i;
802 gint *indices;
804 g_return_if_fail( dest );
806 before = 0;
807 for( it = rows ; it ; it = it->next ){
808 path = gtk_tree_row_reference_get_path(( GtkTreeRowReference * ) it->data );
809 if( path ){
810 if( gtk_tree_path_get_depth( path ) == 1 && gtk_tree_path_compare( path, *dest ) == -1 ){
811 before += 1;
813 gtk_tree_path_free( path );
817 g_debug( "drop_inside_move_dest: before=%d", before );
818 g_debug( "drop_inside_move_dest: dest=%s", gtk_tree_path_to_string( *dest ));
820 if( before ){
821 indices = gtk_tree_path_get_indices( *dest );
822 indices[0] -= before;
823 path = gtk_tree_path_new_from_indices( indices[0], -1 );
824 for( i = 1 ; i < gtk_tree_path_get_depth( *dest ); ++i ){
825 gtk_tree_path_append_index( path, indices[i] );
827 gtk_tree_path_free( *dest );
828 *dest = gtk_tree_path_copy( path );
829 gtk_tree_path_free( path );
832 g_debug( "drop_inside_move_dest: dest=%s", gtk_tree_path_to_string( *dest ));
836 * called when a drop from the outside occurs in the treeview
838 * Returns: %TRUE if the specified rows were successfully inserted at
839 * the given dest, %FALSE else.
841 * URI format only involves actions or menus
842 * so ony valid dest in outside (besides of) an action
844 static gboolean
845 drop_uri_list( NactTreeModel *model, GtkTreePath *dest, GtkSelectionData *selection_data )
847 static const gchar *thisfn = "nact_tree_model_dnd_drop_uri_list";
848 gboolean drop_done;
849 NactApplication *application;
850 NAUpdater *updater;
851 NactMainWindow *main_window;
852 NAImporterParms parms;
853 GList *it;
854 guint count;
855 GString *str;
856 GSList *im;
857 GList *imported;
858 const gchar *selection_data_data;
859 NactTreeView *view;
861 gchar *dest_str = gtk_tree_path_to_string( dest );
862 g_debug( "%s: model=%p, dest=%p (%s), selection_data=%p",
863 thisfn, ( void * ) model, ( void * ) dest, dest_str, ( void * ) selection_data );
864 g_free( dest_str );
866 drop_done = FALSE;
867 model->private->drag_has_profiles = FALSE;
869 if( !is_drop_possible( model, dest, NULL )){
870 return( FALSE );
873 application = NACT_APPLICATION( base_window_get_application( model->private->window ));
874 updater = nact_application_get_updater( application );
876 g_return_val_if_fail( NACT_IS_MAIN_WINDOW( model->private->window ), FALSE );
877 main_window = NACT_MAIN_WINDOW( model->private->window );
879 #if GTK_CHECK_VERSION( 2, 14, 0 )
880 selection_data_data = ( const gchar * ) gtk_selection_data_get_data( selection_data );
881 #else
882 selection_data_data = ( const gchar * ) selection_data->data;
883 #endif
884 g_debug( "%s", selection_data_data );
886 parms.parent = base_window_get_gtk_toplevel( BASE_WINDOW( main_window ));
887 parms.uris = g_slist_reverse( na_core_utils_slist_from_split( selection_data_data, "\r\n" ));
889 parms.mode = na_iprefs_get_import_mode( NA_PIVOT( updater ), NA_IPREFS_IMPORT_PREFERRED_MODE, NULL );
891 parms.check_fn = ( NAIImporterCheckFn ) is_dropped_already_exists;
892 parms.check_fn_data = main_window;
893 parms.results = NULL;
895 na_importer_import_from_list( NA_PIVOT( updater ), &parms );
897 /* analysing output results
898 * - first line of first message is displayed in status bar
899 * - simultaneously build the concatenation of all lines of messages
900 * - simultaneously build the list of imported items
902 count = 0;
903 str = g_string_new( "" );
904 imported = NULL;
906 for( it = parms.results ; it ; it = it->next ){
907 NAImporterResult *result = ( NAImporterResult * ) it->data;
909 if( result->messages ){
910 if( count == 0 ){
911 nact_main_statusbar_display_with_timeout(
912 main_window,
913 TREE_MODEL_STATUSBAR_CONTEXT,
914 result->messages->data );
916 count += 1;
917 for( im = result->messages ; im ; im = im->next ){
918 g_string_append_printf( str, "%s\n", ( const gchar * ) im->data );
922 if( result->imported ){
923 imported = g_list_prepend( imported, result->imported );
927 /* if there is more than one message, display them in a dialog box
929 if( count > 1 ){
930 GtkMessageDialog *dialog = GTK_MESSAGE_DIALOG( gtk_message_dialog_new(
931 parms.parent,
932 GTK_DIALOG_MODAL, GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE,
933 "%s", _( "Some messages have occurred during drop operation." )));
934 gtk_message_dialog_format_secondary_markup( dialog, "%s", str->str );
937 g_string_free( str, TRUE );
939 /* insert newly imported items in the list view
941 if( imported ){
942 na_object_dump_tree( imported );
943 view = nact_main_window_get_items_view( main_window );
944 nact_tree_ieditable_insert_at_path( NACT_TREE_IEDITABLE( view ), imported, dest );
947 drop_done = TRUE;
948 na_object_free_items( imported );
949 na_core_utils_slist_free( parms.uris );
951 for( it = parms.results ; it ; it = it->next ){
952 na_importer_free_result( it->data );
954 g_list_free( parms.results );
956 return( drop_done );
959 static NAObjectItem *
960 is_dropped_already_exists( const NAObjectItem *importing, const NactMainWindow *window )
962 NactTreeView *items_view;
964 gchar *id = na_object_get_id( importing );
965 items_view = nact_main_window_get_items_view( window );
966 NAObjectItem *exists = nact_tree_view_get_item_by_id( items_view, id );
967 g_free( id );
969 return( exists );
973 * this function works well, but only called from on_drag_motion handler...
975 /*static gboolean
976 is_row_drop_possible( NactTreeModel *model, GtkTreePath *path, GtkTreeViewDropPosition pos )
978 gboolean ok = FALSE;
979 GtkTreeModel *store;
980 GtkTreeIter iter;
981 NAObject *object;
983 store = gtk_tree_model_filter_get_model( GTK_TREE_MODEL_FILTER( model ));
984 gtk_tree_model_get_iter( store, &iter, path );
985 gtk_tree_model_get( store, &iter, IACTIONS_LIST_NAOBJECT_COLUMN, &object, -1 );
987 if( model->private->drag_has_profiles ){
988 if( NA_IS_OBJECT_PROFILE( object )){
989 ok = TRUE;
990 } else if( NA_IS_OBJECT_ACTION( object )){
991 ok = ( pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE || pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER );
993 } else {
994 if( NA_IS_OBJECT_ITEM( object )){
995 ok = TRUE;
999 g_object_unref( object );
1000 return( ok );
1004 * called when the user moves into the target widget
1005 * returns TRUE if a drop zone
1007 #if 0
1008 static gboolean
1009 on_drag_motion( GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, BaseWindow *window )
1011 NactTreeModel *model;
1012 gboolean ok = FALSE;
1013 GtkTreePath *path;
1014 GtkTreeViewDropPosition pos;
1016 model = NACT_TREE_MODEL( gtk_tree_view_get_model( GTK_TREE_VIEW( widget )));
1017 g_return_val_if_fail( NACT_IS_TREE_MODEL( model ), FALSE );
1019 if( !model->private->dispose_has_run ){
1021 if( !model->private->drag_highlight ){
1022 model->private->drag_highlight = TRUE;
1023 gtk_drag_highlight( widget );
1026 /*target = gtk_drag_dest_find_target( widget, context, NULL );
1027 if( target == GDK_NONE ){
1028 gdk_drag_status( context, 0, time );
1029 } else {
1030 gtk_drag_get_data( widget, context, target, time );
1033 if( gtk_tree_view_get_dest_row_at_pos( GTK_TREE_VIEW( widget ), x, y, &path, &pos )){
1034 ok = is_row_drop_possible( model, path, pos );
1035 if( ok ){
1036 gdk_drag_status( context, 0, time );
1040 gtk_tree_path_free( path );
1041 g_debug( "nact_tree_model_on_drag_motion: ok=%s, pos=%d", ok ? "True":"False", pos );
1044 return( ok );
1046 #endif
1049 * called when the user drops the data
1050 * returns TRUE if a drop zone
1052 /*static gboolean
1053 on_drag_drop( GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, BaseWindow *window )
1055 NactTreeModel *model;
1057 model = NACT_TREE_MODEL( gtk_tree_view_get_model( GTK_TREE_VIEW( widget )));
1058 g_return_val_if_fail( NACT_IS_TREE_MODEL( model ), FALSE );
1060 if( !model->private->dispose_has_run ){
1062 model->private->drag_drop = TRUE;
1065 return( TRUE );
1068 /* The following function taken from bugzilla
1069 * (http://bugzilla.gnome.org/attachment.cgi?id=49362&action=view)
1070 * Author: Christian Neumair
1071 * Copyright: 2005 Free Software Foundation, Inc
1072 * License: GPL
1074 * On a 32-bits system:
1075 * get_xds_atom_value: actual_length=63, actual_length=15
1076 * get_xds_atom_value: ret=file:///home/pierre/data/eclipse/nautilus-actions/trash/xds.txt0x8299
1077 * get_xds_atom_value: dup=file:///home/pierre/data/eclipse/nautilus-actions/trash/xds.txt
1078 * get_xds_atom_value: ret=file:///home/pi
1080 * idem on a 64bits system.
1082 static char *
1083 get_xds_atom_value( GdkDragContext *context )
1085 gchar *ret;
1086 gint actual_length;
1087 GdkWindow *context_source_window;
1089 #if GTK_CHECK_VERSION( 2, 22, 0 )
1090 context_source_window = gdk_drag_context_get_source_window( context );
1091 #else
1092 context_source_window = context->source_window;
1093 #endif
1095 g_return_val_if_fail( context != NULL, NULL );
1096 g_return_val_if_fail( context_source_window != NULL, NULL );
1098 gdk_property_get( context_source_window, /* a GdkWindow */
1099 XDS_ATOM, /* the property to retrieve */
1100 TEXT_ATOM, /* the desired property type */
1101 0, /* offset (in 4 bytes chunks) */
1102 MAX_XDS_ATOM_VAL_LEN, /* max length (in 4 bytes chunks) */
1103 FALSE, /* whether to delete the property */
1104 NULL, /* actual property type */
1105 NULL, /* actual format */
1106 &actual_length, /* actual length (in 4 bytes chunks) */
1107 ( guchar ** ) &ret ); /* data pointer */
1109 ret[actual_length] = '\0';
1111 return( ret );
1115 * when dropping something somewhere, we must ensure that we will be able
1116 * to register the new child
1118 static gboolean
1119 is_parent_accept_new_children( NactApplication *application, NactMainWindow *window, NAObjectItem *parent )
1121 gboolean accept_ok;
1122 NAUpdater *updater;
1124 accept_ok = FALSE;
1125 updater = nact_application_get_updater( application );
1127 /* inserting as a level zero item
1128 * ensure that level zero is writable
1130 if( parent == NULL ){
1131 if( na_updater_is_level_zero_writable( updater )){
1132 accept_ok = TRUE;
1134 } else {
1135 nact_main_statusbar_display_with_timeout(
1136 window, TREE_MODEL_STATUSBAR_CONTEXT, gettext( st_level_zero_not_writable ));
1139 /* see if the parent is writable
1141 } else if( na_updater_check_item_writability_status( updater, parent, NULL )){
1142 accept_ok = TRUE;
1144 } else {
1145 nact_main_statusbar_display_with_timeout(
1146 window, TREE_MODEL_STATUSBAR_CONTEXT, gettext( st_parent_not_writable ));
1149 return( accept_ok );
1152 static guint
1153 target_atom_to_id( GdkAtom atom )
1155 gint i;
1156 guint info = 0;
1157 gchar *atom_name;
1159 atom_name = gdk_atom_name( atom );
1160 for( i = 0 ; i < tree_model_dnd_dest_formats_count ; ++i ){
1161 if( !g_ascii_strcasecmp( tree_model_dnd_dest_formats[i].target, atom_name )){
1162 info = tree_model_dnd_dest_formats[i].info;
1163 break;
1166 g_free( atom_name );
1167 return( info );