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 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.
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)
35 #include <gconf/gconf-client.h>
36 #include <glib/gi18n.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
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:
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.
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
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
;
167 g_debug( "%s: drag_dest=%p, dest=%p, selection_data=%p", thisfn
, ( void * ) drag_dest
, ( void * ) dest
, ( void * ) selection_data
);
168 g_return_val_if_fail( NACT_IS_TREE_MODEL( drag_dest
), FALSE
);
170 atom_name
= gdk_atom_name( selection_data
->selection
);
171 g_debug( "%s: selection=%s", thisfn
, atom_name
);
174 atom_name
= gdk_atom_name( selection_data
->target
);
175 g_debug( "%s: target=%s", thisfn
, atom_name
);
178 atom_name
= gdk_atom_name( selection_data
->type
);
179 g_debug( "%s: type=%s", thisfn
, atom_name
);
182 g_debug( "%s: format=%d, length=%d", thisfn
, selection_data
->format
, selection_data
->length
);
184 info
= target_atom_to_id( selection_data
->type
);
185 g_debug( "%s: info=%u", thisfn
, info
);
187 path_str
= gtk_tree_path_to_string( dest
);
188 g_debug( "%s: dest_path=%s", thisfn
, path_str
);
192 case NACT_XCHANGE_FORMAT_NACT
:
193 result
= drop_inside( NACT_TREE_MODEL( drag_dest
), dest
, selection_data
);
196 /* drop some actions from outside
197 * most probably from the file manager as a list of uris
199 case NACT_XCHANGE_FORMAT_URI_LIST
:
200 result
= drop_uri_list( NACT_TREE_MODEL( drag_dest
), dest
, selection_data
);
211 * nact_tree_model_dnd_idrag_dest_row_drop_possible:
216 * Seems to only be called when the drop in _on_ a row (a square is
217 * displayed), but not when dropped between two rows (a line is displayed),
218 * nor during the motion.
221 nact_tree_model_dnd_idrag_dest_row_drop_possible( GtkTreeDragDest
*drag_dest
, GtkTreePath
*dest_path
, GtkSelectionData
*selection_data
)
223 static const gchar
*thisfn
= "nact_tree_model_dnd_idrag_dest_row_drop_possible";
225 g_debug( "%s: drag_dest=%p, dest_path=%p, selection_data=%p", thisfn
, ( void * ) drag_dest
, ( void * ) dest_path
, ( void * ) selection_data
);
231 * nact_tree_model_dnd_imulti_drag_source_drag_data_get:
233 * - the suggested action, as choosen by the drop site,
234 * between those we have provided in imulti_drag_source_get_drag_actions()
235 * - the target folder (XDS protocol)
237 * @rows: list of row references which are to be dropped
238 * @info: the suggested format, as choosen by the drop site, between those
239 * we have provided in imulti_drag_source_get_target_list()
241 * This function is called when we release the selected items onto the
244 * NACT_XCHANGE_FORMAT_NACT:
245 * internal format for drag and drop inside the treeview:
246 * - copy in the clipboard the list of row references
247 * - selection data is empty
249 * NACT_XCHANGE_FORMAT_XDS:
250 * exchange format to drop to outside:
251 * - copy in the clipboard the list of row references
252 * - set the destination folder
253 * - selection data is 'success' or 'failure'
255 * NACT_XCHANGE_FORMAT_APPLICATION_XML:
256 * NACT_XCHANGE_FORMAT_TEXT_PLAIN:
257 * exchange format to export to outside:
258 * - do not use dnd clipboard
259 * - selection data receives the export in text format
261 * Returns: %TRUE if required data was actually provided by the source,
265 nact_tree_model_dnd_imulti_drag_source_drag_data_get( EggTreeMultiDragSource
*drag_source
,
266 GdkDragContext
*context
,
267 GtkSelectionData
*selection_data
,
271 static const gchar
*thisfn
= "nact_tree_model_dnd_imulti_drag_source_drag_data_get";
273 NactTreeModel
*model
;
275 gboolean ret
= FALSE
;
276 gchar
*dest_folder
, *folder
;
277 gboolean is_writable
;
280 atom_name
= gdk_atom_name( selection_data
->target
);
281 g_debug( "%s: drag_source=%p, context=%p, action=%d, selection_data=%p, rows=%p, atom=%s",
282 thisfn
, ( void * ) drag_source
, ( void * ) context
, ( int ) context
->suggested_action
, ( void * ) selection_data
, ( void * ) rows
,
286 model
= NACT_TREE_MODEL( drag_source
);
287 g_return_val_if_fail( model
->private->window
, FALSE
);
289 if( !model
->private->dispose_has_run
){
291 if( !rows
|| !g_list_length( rows
)){
296 case NACT_XCHANGE_FORMAT_NACT
:
297 copy_data
= ( context
->action
== GDK_ACTION_COPY
);
298 gtk_selection_data_set( selection_data
, selection_data
->target
, 8, ( guchar
* ) "", 0 );
299 nact_clipboard_dnd_set( model
->private->clipboard
, info
, rows
, NULL
, copy_data
);
303 case NACT_XCHANGE_FORMAT_XDS
:
304 /* get the dest default filename as an uri
305 * e.g. file:///home/pierre/data/eclipse/nautilus-actions/trash/xds.txt
307 folder
= get_xds_atom_value( context
);
308 dest_folder
= g_path_get_dirname( folder
);
310 /* check that target folder is writable
312 is_writable
= na_core_utils_dir_is_writable_uri( dest_folder
);
313 g_debug( "%s: dest_folder=%s, is_writable=%s", thisfn
, dest_folder
, is_writable
? "True":"False" );
314 gtk_selection_data_set( selection_data
, selection_data
->target
, 8, ( guchar
* )( is_writable
? "S" : "F" ), 1 );
317 nact_clipboard_dnd_set( model
->private->clipboard
, info
, rows
, dest_folder
, TRUE
);
320 g_free( dest_folder
);
325 case NACT_XCHANGE_FORMAT_APPLICATION_XML
:
326 case NACT_XCHANGE_FORMAT_TEXT_PLAIN
:
327 data
= nact_clipboard_dnd_get_text( model
->private->clipboard
, rows
);
328 gtk_selection_data_set( selection_data
, selection_data
->target
, 8, ( guchar
* ) data
, strlen( data
));
342 * nact_tree_model_dnd_imulti_drag_source_drag_data_delete:
347 nact_tree_model_dnd_imulti_drag_source_drag_data_delete( EggTreeMultiDragSource
*drag_source
, GList
*rows
)
349 static const gchar
*thisfn
= "nact_tree_model_dnd_imulti_drag_source_drag_data_delete";
351 g_debug( "%s: drag_source=%p, path_list=%p", thisfn
, ( void * ) drag_source
, ( void * ) rows
);
357 * nact_tree_model_dnd_imulti_drag_source_get_drag_actions:
361 nact_tree_model_dnd_imulti_drag_source_get_drag_actions( EggTreeMultiDragSource
*drag_source
)
363 return( GDK_ACTION_COPY
| GDK_ACTION_MOVE
);
367 nact_tree_model_dnd_imulti_drag_source_get_format_list( EggTreeMultiDragSource
*drag_source
)
369 GtkTargetList
*target_list
;
371 target_list
= gtk_target_list_new( dnd_source_formats
, G_N_ELEMENTS( dnd_source_formats
));
373 return( target_list
);
377 * nact_tree_model_dnd_imulti_drag_source_row_draggable:
381 * All selectable rows are draggable.
382 * Nonetheless, it's a good place to store the dragged row references.
383 * We only make use of them in on_drag_motion handler.
386 nact_tree_model_dnd_imulti_drag_source_row_draggable( EggTreeMultiDragSource
*drag_source
, GList
*rows
)
388 static const gchar
*thisfn
= "nact_tree_model_dnd_imulti_drag_source_row_draggable";
389 NactTreeModel
*model
;
396 g_debug( "%s: drag_source=%p, rows=%p (%d items)",
397 thisfn
, ( void * ) drag_source
, ( void * ) rows
, g_list_length( rows
));
399 g_return_val_if_fail( NACT_IS_TREE_MODEL( drag_source
), FALSE
);
400 model
= NACT_TREE_MODEL( drag_source
);
402 if( !model
->private->dispose_has_run
){
404 model
->private->drag_has_profiles
= FALSE
;
405 store
= gtk_tree_model_filter_get_model( GTK_TREE_MODEL_FILTER( model
));
407 for( it
= rows
; it
&& !model
->private->drag_has_profiles
; it
= it
->next
){
409 path
= gtk_tree_row_reference_get_path(( GtkTreeRowReference
* ) it
->data
);
410 gtk_tree_model_get_iter( store
, &iter
, path
);
411 gtk_tree_model_get( store
, &iter
, IACTIONS_LIST_NAOBJECT_COLUMN
, &object
, -1 );
413 if( NA_IS_OBJECT_PROFILE( object
)){
414 model
->private->drag_has_profiles
= TRUE
;
417 g_object_unref( object
);
418 gtk_tree_path_free( path
);
426 * nact_tree_model_dnd_on_drag_begin:
427 * @widget: the GtkTreeView from which item is to be dragged.
429 * @window: the parent #NactMainWindow instance.
431 * This function is called once, at the beginning of the drag operation,
432 * when we are dragging from the IActionsList treeview.
435 nact_tree_model_dnd_on_drag_begin( GtkWidget
*widget
, GdkDragContext
*context
, BaseWindow
*window
)
437 static const gchar
*thisfn
= "nact_tree_model_dnd_on_drag_begin";
438 NactTreeModel
*model
;
440 g_debug( "%s: widget=%p, context=%p, window=%p",
441 thisfn
, ( void * ) widget
, ( void * ) context
, ( void * ) window
);
443 model
= NACT_TREE_MODEL( gtk_tree_view_get_model( GTK_TREE_VIEW( widget
)));
444 g_return_if_fail( NACT_IS_TREE_MODEL( model
));
446 if( !model
->private->dispose_has_run
){
448 model
->private->drag_highlight
= FALSE
;
449 model
->private->drag_drop
= FALSE
;
451 nact_clipboard_dnd_clear( model
->private->clipboard
);
454 context
->source_window
,
455 XDS_ATOM
, TEXT_ATOM
, 8, GDK_PROP_MODE_REPLACE
, ( guchar
* ) XDS_FILENAME
, strlen( XDS_FILENAME
));
460 * nact_tree_model_dnd_on_drag_end:
466 nact_tree_model_dnd_on_drag_end( GtkWidget
*widget
, GdkDragContext
*context
, BaseWindow
*window
)
468 static const gchar
*thisfn
= "nact_tree_model_dnd_on_drag_end";
469 NactTreeModel
*model
;
471 g_debug( "%s: widget=%p, context=%p, window=%p",
472 thisfn
, ( void * ) widget
, ( void * ) context
, ( void * ) window
);
474 model
= NACT_TREE_MODEL( gtk_tree_view_get_model( GTK_TREE_VIEW( widget
)));
475 g_return_if_fail( NACT_IS_TREE_MODEL( model
));
477 if( !model
->private->dispose_has_run
){
479 nact_clipboard_dnd_drag_end( model
->private->clipboard
);
480 nact_clipboard_dnd_clear( model
->private->clipboard
);
481 gdk_property_delete( context
->source_window
, XDS_ATOM
);
486 * called when a drop occurs in the treeview for a move/copy inside of
489 * Returns: %TRUE if the specified rows were successfully inserted at
490 * the given dest, %FALSE else.
492 * The dest path is computed based on the current appearance of the list
493 * Drop should so occurs inside an inchanged list to keep a valid path
494 * in the case of a move, this leads to :
495 * 1) marks dragged items as 'to be deleted'
496 * 2) insert new dropped items
497 * 3) remove 'to be deleted' items
498 * -> not an easy idea as we want modify the id of all the dragged
501 * adjusting the path: quid if the target dest is not at the same level
504 drop_inside( NactTreeModel
*model
, GtkTreePath
*dest
, GtkSelectionData
*selection_data
)
506 static const gchar
*thisfn
= "nact_tree_model_dnd_inside_drag_and_drop";
507 NactApplication
*application
;
509 NactMainWindow
*main_window
;
510 NAObjectItem
*parent
;
513 GtkTreePath
*new_dest
;
517 GList
*object_list
, *it
;
522 application
= NACT_APPLICATION( base_window_get_application( model
->private->window
));
523 updater
= nact_application_get_updater( application
);
524 main_window
= NACT_MAIN_WINDOW( base_application_get_main_window( BASE_APPLICATION( application
)));
527 * NACT format (may embed profiles, or not)
528 * with profiles: only valid dest is inside an action
529 * without profile: only valid dest is outside (besides of) an action
532 rows
= nact_clipboard_dnd_get_data( model
->private->clipboard
, ©_data
);
534 if( !is_drop_possible( model
, dest
, &parent
)){
538 new_dest
= gtk_tree_path_copy( dest
);
540 drop_inside_move_dest( model
, rows
, &new_dest
);
543 g_debug( "%s: rows has %d items, copy_data=%s", thisfn
, g_list_length( rows
), copy_data
? "True":"False" );
545 for( it
= rows
; it
; it
= it
->next
){
546 path
= gtk_tree_row_reference_get_path(( GtkTreeRowReference
* ) it
->data
);
548 if( gtk_tree_model_get_iter( GTK_TREE_MODEL( model
), &iter
, path
)){
549 gtk_tree_model_get( GTK_TREE_MODEL( model
), &iter
, IACTIONS_LIST_NAOBJECT_COLUMN
, ¤t
, -1 );
550 g_object_unref( current
);
553 inserted
= ( NAObject
* ) na_object_duplicate( current
);
554 na_object_set_origin( inserted
, NULL
);
555 na_object_check_status( inserted
);
558 inserted
= na_object_ref( current
);
559 deletable
= g_list_prepend( NULL
, inserted
);
560 nact_iactions_list_bis_delete( NACT_IACTIONS_LIST( main_window
), deletable
, FALSE
);
561 g_list_free( deletable
);
564 relabel
= nact_main_menubar_edit_is_pasted_object_relabeled( inserted
, NA_PIVOT( updater
));
565 na_object_prepare_for_paste( inserted
, relabel
, copy_data
, parent
);
566 object_list
= g_list_prepend( object_list
, inserted
);
567 g_debug( "%s: dropped=%s", thisfn
, na_object_get_label( inserted
));
569 gtk_tree_path_free( path
);
572 object_list
= g_list_reverse( object_list
);
574 nact_iactions_list_bis_insert_at_path( NACT_IACTIONS_LIST( main_window
), object_list
, new_dest
);
576 if( gtk_tree_path_get_depth( new_dest
) == 1 ){
577 g_signal_emit_by_name( main_window
, MAIN_WINDOW_SIGNAL_LEVEL_ZERO_ORDER_CHANGED
, GINT_TO_POINTER( TRUE
));
580 g_list_foreach( object_list
, ( GFunc
) na_object_object_unref
, NULL
);
581 g_list_free( object_list
);
582 gtk_tree_path_free( new_dest
);
584 g_list_foreach( rows
, ( GFunc
) gtk_tree_row_reference_free
, NULL
);
591 * is a drop possible at given dest ?
593 * the only case where we would be led to have to modify the dest if
594 * we'd want be able to drop a profile into another profile, accepting
595 * it, actually dropping the profile just before the target
597 * -> it appears both clearer for the user interface and easyer from a
598 * code point of view to just refuse to drop a profile into a profile
600 * so this function is just to check if a drop is possible at the given
604 is_drop_possible( NactTreeModel
*model
, GtkTreePath
*dest
, NAObjectItem
**parent
)
607 NactApplication
*application
;
608 NactMainWindow
*main_window
;
610 NAObjectItem
*parent_dest
;
614 application
= NACT_APPLICATION( base_window_get_application( model
->private->window
));
615 main_window
= NACT_MAIN_WINDOW( base_application_get_main_window( BASE_APPLICATION( application
)));
617 /* if we can have an iter on given dest, then the dest already exists
618 * so dropped items should be of the same type that already existing
620 if( gtk_tree_model_get_iter( GTK_TREE_MODEL( model
), &iter
, dest
)){
621 drop_ok
= is_drop_possible_before_iter( model
, &iter
, main_window
, &parent_dest
);
623 /* inserting at the end of the list
624 * parent_dest is NULL
626 } else if( gtk_tree_path_get_depth( dest
) == 1 ){
628 if( model
->private->drag_has_profiles
){
629 nact_main_statusbar_display_with_timeout(
630 main_window
, TREE_MODEL_STATUSBAR_CONTEXT
, st_refuse_drop_profile
);
636 /* we cannot have an iter on the dest: this means that we try to
637 * insert items into not-opened dest (an empty menu or an action with
638 * zero or one profile) : check what is the parent
641 drop_ok
= is_drop_possible_into_dest( model
, dest
, main_window
, &parent_dest
);
645 drop_ok
= is_parent_accept_new_childs( application
, main_window
, parent_dest
);
648 if( drop_ok
&& parent
){
649 *parent
= parent_dest
;
656 is_drop_possible_before_iter( NactTreeModel
*model
, GtkTreeIter
*iter
, NactMainWindow
*window
, NAObjectItem
**parent
)
658 static const gchar
*thisfn
= "nact_tree_model_dnd_is_drop_possible_before_iter";
665 gtk_tree_model_get( GTK_TREE_MODEL( model
), iter
, IACTIONS_LIST_NAOBJECT_COLUMN
, &object
, -1 );
666 g_object_unref( object
);
667 g_debug( "%s: current object at dest is %s", thisfn
, G_OBJECT_TYPE_NAME( object
));
669 if( model
->private->drag_has_profiles
){
671 if( NA_IS_OBJECT_PROFILE( object
)){
673 *parent
= na_object_get_parent( object
);
676 /* unable to drop a profile here */
677 nact_main_statusbar_display_with_timeout(
678 window
, TREE_MODEL_STATUSBAR_CONTEXT
, st_refuse_drop_profile
);
681 } else if( NA_IS_OBJECT_ITEM( object
)){
683 *parent
= na_object_get_parent( object
);
686 /* unable to drop an action or a menu here */
687 nact_main_statusbar_display_with_timeout(
688 window
, TREE_MODEL_STATUSBAR_CONTEXT
, st_refuse_drop_item
);
695 is_drop_possible_into_dest( NactTreeModel
*model
, GtkTreePath
*dest
, NactMainWindow
*window
, NAObjectItem
**parent
)
697 static const gchar
*thisfn
= "nact_tree_model_dnd_is_drop_possible_into_dest";
706 path
= gtk_tree_path_copy( dest
);
708 if( gtk_tree_path_up( path
)){
709 if( gtk_tree_model_get_iter( GTK_TREE_MODEL( model
), &iter
, path
)){
710 gtk_tree_model_get( GTK_TREE_MODEL( model
), &iter
, IACTIONS_LIST_NAOBJECT_COLUMN
, &object
, -1 );
711 g_object_unref( object
);
712 g_debug( "%s: current object at parent dest is %s", thisfn
, G_OBJECT_TYPE_NAME( object
));
714 if( model
->private->drag_has_profiles
){
716 if( NA_IS_OBJECT_ACTION( object
)){
718 *parent
= NA_OBJECT_ITEM( object
);
721 nact_main_statusbar_display_with_timeout(
722 window
, TREE_MODEL_STATUSBAR_CONTEXT
, st_refuse_drop_profile
);
725 } else if( NA_IS_OBJECT_MENU( object
)){
727 *parent
= na_object_get_parent( object
);
730 nact_main_statusbar_display_with_timeout(
731 window
, TREE_MODEL_STATUSBAR_CONTEXT
, st_refuse_drop_item
);
736 gtk_tree_path_free( path
);
742 drop_inside_move_dest( NactTreeModel
*model
, GList
*rows
, GtkTreePath
**dest
)
750 g_return_if_fail( dest
);
753 for( it
= rows
; it
; it
= it
->next
){
754 path
= gtk_tree_row_reference_get_path(( GtkTreeRowReference
* ) it
->data
);
756 if( gtk_tree_path_get_depth( path
) == 1 && gtk_tree_path_compare( path
, *dest
) == -1 ){
759 gtk_tree_path_free( path
);
763 g_debug( "drop_inside_move_dest: before=%d", before
);
764 g_debug( "drop_inside_move_dest: dest=%s", gtk_tree_path_to_string( *dest
));
767 indices
= gtk_tree_path_get_indices( *dest
);
768 indices
[0] -= before
;
769 path
= gtk_tree_path_new_from_indices( indices
[0], -1 );
770 for( i
= 1 ; i
< gtk_tree_path_get_depth( *dest
); ++i
){
771 gtk_tree_path_append_index( path
, indices
[i
] );
773 gtk_tree_path_free( *dest
);
774 *dest
= gtk_tree_path_copy( path
);
775 gtk_tree_path_free( path
);
778 g_debug( "drop_inside_move_dest: dest=%s", gtk_tree_path_to_string( *dest
));
782 * called when a drop from the outside occurs in the treeview
784 * Returns: %TRUE if the specified rows were successfully inserted at
785 * the given dest, %FALSE else.
787 * URI format only involves actions or menus
788 * so ony valid dest in outside (besides of) an action
791 drop_uri_list( NactTreeModel
*model
, GtkTreePath
*dest
, GtkSelectionData
*selection_data
)
793 static const gchar
*thisfn
= "nact_tree_model_dnd_drop_uri_list";
795 NactApplication
*application
;
797 NactMainWindow
*main_window
;
798 NAImporterParms parms
;
806 gchar
*dest_str
= gtk_tree_path_to_string( dest
);
807 g_debug( "%s: model=%p, dest=%p (%s), selection_data=%p",
808 thisfn
, ( void * ) model
, ( void * ) dest
, dest_str
, ( void * ) selection_data
);
812 model
->private->drag_has_profiles
= FALSE
;
814 if( !is_drop_possible( model
, dest
, NULL
)){
818 application
= NACT_APPLICATION( base_window_get_application( model
->private->window
));
819 updater
= nact_application_get_updater( application
);
820 main_window
= NACT_MAIN_WINDOW( base_application_get_main_window( BASE_APPLICATION( application
)));
822 g_debug( "%s", ( const gchar
* ) selection_data
->data
);
824 parms
.parent
= base_window_get_toplevel( BASE_WINDOW( main_window
));
825 parms
.uris
= g_slist_reverse( na_core_utils_slist_from_split(( const gchar
* ) selection_data
->data
, "\r\n" ));
827 gconf
= gconf_client_get_default();
828 parms
.mode
= na_iprefs_get_import_mode( gconf
, IPREFS_IMPORT_ITEMS_IMPORT_MODE
);
829 g_object_unref( gconf
);
831 parms
.check_fn
= ( NAIImporterCheckFn
) is_dropped_already_exists
;
832 parms
.check_fn_data
= main_window
;
833 parms
.results
= NULL
;
835 na_importer_import_from_list( NA_PIVOT( updater
), &parms
);
837 /* analysing output results
838 * - first line of first message is displayed in status bar
839 * - simultaneously build the concatenation of all lines of messages
840 * - simultaneously build the list of imported items
843 str
= g_string_new( "" );
846 for( it
= parms
.results
; it
; it
= it
->next
){
847 NAImporterResult
*result
= ( NAImporterResult
* ) it
->data
;
849 if( result
->messages
){
851 nact_main_statusbar_display_with_timeout(
853 TREE_MODEL_STATUSBAR_CONTEXT
,
854 result
->messages
->data
);
857 for( im
= result
->messages
; im
; im
= im
->next
){
858 g_string_append_printf( str
, "%s\n", ( const gchar
* ) im
->data
);
862 if( result
->imported
){
863 imported
= g_list_prepend( imported
, result
->imported
);
867 /* if there is more than one message, display them in a dialog box
870 GtkMessageDialog
*dialog
= GTK_MESSAGE_DIALOG( gtk_message_dialog_new(
872 GTK_DIALOG_MODAL
, GTK_MESSAGE_WARNING
, GTK_BUTTONS_CLOSE
,
873 "%s", _( "Some messages have occurred during drop operation." )));
874 gtk_message_dialog_format_secondary_markup( dialog
, "%s", str
->str
);
877 g_string_free( str
, TRUE
);
879 /* check status of newly imported items, and insert them in the list view
881 for( it
= imported
; it
; it
= it
->next
){
882 na_object_check_status( it
->data
);
883 na_object_dump( it
->data
);
887 nact_iactions_list_bis_insert_at_path( NACT_IACTIONS_LIST( main_window
), imported
, dest
);
888 nact_tree_model_dump( model
);
890 na_object_unref_items( imported
);
891 na_core_utils_slist_free( parms
.uris
);
893 for( it
= parms
.results
; it
; it
= it
->next
){
894 na_importer_free_result( it
->data
);
896 g_list_free( parms
.results
);
901 static NAObjectItem
*
902 is_dropped_already_exists( const NAObjectItem
*importing
, const NactMainWindow
*window
)
904 gchar
*id
= na_object_get_id( importing
);
905 NAObjectItem
*exists
= nact_main_window_get_item( window
, id
);
912 * this function works well, but only called from on_drag_motion handler...
915 is_row_drop_possible( NactTreeModel *model, GtkTreePath *path, GtkTreeViewDropPosition pos )
922 store = gtk_tree_model_filter_get_model( GTK_TREE_MODEL_FILTER( model ));
923 gtk_tree_model_get_iter( store, &iter, path );
924 gtk_tree_model_get( store, &iter, IACTIONS_LIST_NAOBJECT_COLUMN, &object, -1 );
926 if( model->private->drag_has_profiles ){
927 if( NA_IS_OBJECT_PROFILE( object )){
929 } else if( NA_IS_OBJECT_ACTION( object )){
930 ok = ( pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE || pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER );
933 if( NA_IS_OBJECT_ITEM( object )){
938 g_object_unref( object );
943 * called when the user moves into the target widget
944 * returns TRUE if a drop zone
948 on_drag_motion( GtkWidget
*widget
, GdkDragContext
*context
, gint x
, gint y
, guint time
, BaseWindow
*window
)
950 NactTreeModel
*model
;
953 GtkTreeViewDropPosition pos
;
955 model
= NACT_TREE_MODEL( gtk_tree_view_get_model( GTK_TREE_VIEW( widget
)));
956 g_return_val_if_fail( NACT_IS_TREE_MODEL( model
), FALSE
);
958 if( !model
->private->dispose_has_run
){
960 if( !model
->private->drag_highlight
){
961 model
->private->drag_highlight
= TRUE
;
962 gtk_drag_highlight( widget
);
965 /*target = gtk_drag_dest_find_target( widget, context, NULL );
966 if( target == GDK_NONE ){
967 gdk_drag_status( context, 0, time );
969 gtk_drag_get_data( widget, context, target, time );
972 if( gtk_tree_view_get_dest_row_at_pos( GTK_TREE_VIEW( widget
), x
, y
, &path
, &pos
)){
973 ok
= is_row_drop_possible( model
, path
, pos
);
975 gdk_drag_status( context
, 0, time
);
979 gtk_tree_path_free( path
);
980 g_debug( "nact_tree_model_on_drag_motion: ok=%s, pos=%d", ok
? "True":"False", pos
);
988 * called when the user drops the data
989 * returns TRUE if a drop zone
992 on_drag_drop( GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, BaseWindow *window )
994 NactTreeModel *model;
996 model = NACT_TREE_MODEL( gtk_tree_view_get_model( GTK_TREE_VIEW( widget )));
997 g_return_val_if_fail( NACT_IS_TREE_MODEL( model ), FALSE );
999 if( !model->private->dispose_has_run ){
1001 model->private->drag_drop = TRUE;
1007 /* The following function taken from bugzilla
1008 * (http://bugzilla.gnome.org/attachment.cgi?id=49362&action=view)
1009 * Author: Christian Neumair
1010 * Copyright: 2005 Free Software Foundation, Inc
1013 * On a 32-bits system:
1014 * get_xds_atom_value: actual_length=63, actual_length=15
1015 * get_xds_atom_value: ret=file:///home/pierre/data/eclipse/nautilus-actions/trash/xds.txt0x8299
1016 * get_xds_atom_value: dup=file:///home/pierre/data/eclipse/nautilus-actions/trash/xds.txt
1017 * get_xds_atom_value: ret=file:///home/pi
1019 * idem on a 64bits system.
1022 get_xds_atom_value( GdkDragContext
*context
)
1027 g_return_val_if_fail( context
!= NULL
, NULL
);
1028 g_return_val_if_fail( context
->source_window
!= NULL
, NULL
);
1030 gdk_property_get( context
->source_window
, /* a GdkWindow */
1031 XDS_ATOM
, /* the property to retrieve */
1032 TEXT_ATOM
, /* the desired property type */
1033 0, /* offset (in 4 bytes chunks) */
1034 MAX_XDS_ATOM_VAL_LEN
, /* max length (in 4 bytes chunks) */
1035 FALSE
, /* whether to delete the property */
1036 NULL
, /* actual property type */
1037 NULL
, /* actual format */
1038 &actual_length
, /* actual length (in 4 bytes chunks) */
1039 ( guchar
** ) &ret
); /* data pointer */
1041 ret
[actual_length
] = '\0';
1047 * when dropping something somewhere, we must ensure that we will be able
1048 * to register the new child
1051 is_parent_accept_new_childs( NactApplication
*application
, NactMainWindow
*window
, NAObjectItem
*parent
)
1057 updater
= nact_application_get_updater( application
);
1059 /* inserting as a level zero item
1060 * ensure that level zero is writable
1062 if( parent
== NULL
){
1064 if( na_pivot_is_level_zero_writable( NA_PIVOT( updater
))){
1068 nact_main_statusbar_display_with_timeout(
1069 window
, TREE_MODEL_STATUSBAR_CONTEXT
, st_level_zero_not_writable
);
1072 /* see if the parent is writable
1074 } else if( na_updater_is_item_writable( updater
, parent
, NULL
)){
1078 nact_main_statusbar_display_with_timeout(
1079 window
, TREE_MODEL_STATUSBAR_CONTEXT
, st_parent_not_writable
);
1082 return( accept_ok
);
1086 target_atom_to_id( GdkAtom atom
)
1092 atom_name
= gdk_atom_name( atom
);
1093 for( i
= 0 ; i
< tree_model_dnd_dest_formats_count
; ++i
){
1094 if( !g_ascii_strcasecmp( tree_model_dnd_dest_formats
[i
].target
, atom_name
)){
1095 info
= tree_model_dnd_dest_formats
[i
].info
;
1099 g_free( atom_name
);