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.
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 <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-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
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:
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.
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
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
;
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
);
178 selection_data_selection
= selection_data
->selection
;
180 atom_name
= gdk_atom_name( selection_data_selection
);
181 g_debug( "%s: selection=%s", thisfn
, atom_name
);
184 #if GTK_CHECK_VERSION( 2, 14, 0 )
185 selection_data_target
= gtk_selection_data_get_target( selection_data
);
187 selection_data_target
= selection_data
->target
;
189 atom_name
= gdk_atom_name( selection_data_target
);
190 g_debug( "%s: target=%s", thisfn
, atom_name
);
193 #if GTK_CHECK_VERSION( 2, 14, 0 )
194 selection_data_type
= gtk_selection_data_get_data_type( selection_data
);
196 selection_data_type
= selection_data
->type
;
198 atom_name
= gdk_atom_name( selection_data_type
);
199 g_debug( "%s: type=%s", thisfn
, 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
);
206 selection_data_format
= selection_data
->format
;
207 selection_data_length
= selection_data
->length
;
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
);
219 case NACT_XCHANGE_FORMAT_NACT
:
220 result
= drop_inside( NACT_TREE_MODEL( drag_dest
), dest
, selection_data
);
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
);
238 * nact_tree_model_dnd_idrag_dest_row_drop_possible:
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.
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
);
258 * nact_tree_model_dnd_imulti_drag_source_drag_data_get:
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)
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
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,
292 nact_tree_model_dnd_imulti_drag_source_drag_data_get( EggTreeMultiDragSource
*drag_source
,
293 GdkDragContext
*context
,
294 GtkSelectionData
*selection_data
,
298 static const gchar
*thisfn
= "nact_tree_model_dnd_imulti_drag_source_drag_data_get";
300 NactTreeModel
*model
;
302 gboolean ret
= FALSE
;
303 gchar
*dest_folder
, *folder
;
304 gboolean is_writable
;
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
);
313 selection_data_target
= selection_data
->target
;
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
);
320 context_suggested_action
= context
->suggested_action
;
321 context_selected_action
= context
->action
;
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
,
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
)){
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
);
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 );
364 nact_clipboard_dnd_set( model
->private->clipboard
, info
, rows
, dest_folder
, TRUE
);
367 g_free( dest_folder
);
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
));
390 * nact_tree_model_dnd_imulti_drag_source_drag_data_delete:
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
);
405 * nact_tree_model_dnd_imulti_drag_source_get_drag_actions:
409 nact_tree_model_dnd_imulti_drag_source_get_drag_actions( EggTreeMultiDragSource
*drag_source
)
411 return( GDK_ACTION_COPY
| GDK_ACTION_MOVE
);
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:
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.
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
;
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
);
474 * nact_tree_model_dnd_on_drag_begin:
475 * @widget: the GtkTreeView from which item is to be dragged.
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.
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
);
505 context_source_window
= context
->source_window
;
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:
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
);
541 context_source_window
= context
->source_window
;
544 gdk_property_delete( context_source_window
, XDS_ATOM
);
549 * called when a drop occurs in the treeview for a move/copy inside of
552 * Returns: %TRUE if the specified rows were successfully inserted at
553 * the given dest, %FALSE else.
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
;
561 NactMainWindow
*main_window
;
562 NAObjectItem
*parent
;
565 GtkTreePath
*new_dest
;
569 GList
*object_list
, *it
;
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
588 rows
= nact_clipboard_dnd_get_data( model
->private->clipboard
, ©_data
);
590 if( !is_drop_possible( model
, dest
, &parent
)){
594 new_dest
= gtk_tree_path_copy( dest
);
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" );
601 for( it
= rows
; it
; it
= it
->next
){
602 path
= gtk_tree_row_reference_get_path(( GtkTreeRowReference
* ) it
->data
);
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
, ¤t
, -1 );
606 g_object_unref( current
);
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
);
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
);
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
);
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
656 is_drop_possible( NactTreeModel
*model
, GtkTreePath
*dest
, NAObjectItem
**parent
)
659 NactApplication
*application
;
660 NactMainWindow
*main_window
;
662 NAObjectItem
*parent_dest
;
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
));
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
695 drop_ok
= is_drop_possible_into_dest( model
, dest
, main_window
, &parent_dest
);
699 drop_ok
= is_parent_accept_new_children( application
, main_window
, parent_dest
);
702 if( drop_ok
&& parent
){
703 *parent
= parent_dest
;
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";
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
)){
727 *parent
= na_object_get_parent( object
);
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
)){
737 *parent
= na_object_get_parent( object
);
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
));
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";
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
)){
772 *parent
= NA_OBJECT_ITEM( object
);
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
)){
781 *parent
= na_object_get_parent( object
);
784 nact_main_statusbar_display_with_timeout(
785 window
, TREE_MODEL_STATUSBAR_CONTEXT
, gettext( st_refuse_drop_item
));
790 gtk_tree_path_free( path
);
796 drop_inside_move_dest( NactTreeModel
*model
, GList
*rows
, GtkTreePath
**dest
)
804 g_return_if_fail( dest
);
807 for( it
= rows
; it
; it
= it
->next
){
808 path
= gtk_tree_row_reference_get_path(( GtkTreeRowReference
* ) it
->data
);
810 if( gtk_tree_path_get_depth( path
) == 1 && gtk_tree_path_compare( path
, *dest
) == -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
));
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
845 drop_uri_list( NactTreeModel
*model
, GtkTreePath
*dest
, GtkSelectionData
*selection_data
)
847 static const gchar
*thisfn
= "nact_tree_model_dnd_drop_uri_list";
849 NactApplication
*application
;
851 NactMainWindow
*main_window
;
852 NAImporterParms parms
;
858 const gchar
*selection_data_data
;
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
);
867 model
->private->drag_has_profiles
= FALSE
;
869 if( !is_drop_possible( model
, dest
, NULL
)){
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
);
882 selection_data_data
= ( const gchar
* ) selection_data
->data
;
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
903 str
= g_string_new( "" );
906 for( it
= parms
.results
; it
; it
= it
->next
){
907 NAImporterResult
*result
= ( NAImporterResult
* ) it
->data
;
909 if( result
->messages
){
911 nact_main_statusbar_display_with_timeout(
913 TREE_MODEL_STATUSBAR_CONTEXT
,
914 result
->messages
->data
);
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
930 GtkMessageDialog
*dialog
= GTK_MESSAGE_DIALOG( gtk_message_dialog_new(
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
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
);
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
);
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
);
973 * this function works well, but only called from on_drag_motion handler...
976 is_row_drop_possible( NactTreeModel *model, GtkTreePath *path, GtkTreeViewDropPosition pos )
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 )){
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 );
994 if( NA_IS_OBJECT_ITEM( object )){
999 g_object_unref( object );
1004 * called when the user moves into the target widget
1005 * returns TRUE if a drop zone
1009 on_drag_motion( GtkWidget
*widget
, GdkDragContext
*context
, gint x
, gint y
, guint time
, BaseWindow
*window
)
1011 NactTreeModel
*model
;
1012 gboolean ok
= FALSE
;
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 );
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
);
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
);
1049 * called when the user drops the data
1050 * returns TRUE if a drop zone
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;
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
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.
1083 get_xds_atom_value( GdkDragContext
*context
)
1087 GdkWindow
*context_source_window
;
1089 #if GTK_CHECK_VERSION( 2, 22, 0 )
1090 context_source_window
= gdk_drag_context_get_source_window( context
);
1092 context_source_window
= context
->source_window
;
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';
1115 * when dropping something somewhere, we must ensure that we will be able
1116 * to register the new child
1119 is_parent_accept_new_children( NactApplication
*application
, NactMainWindow
*window
, NAObjectItem
*parent
)
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
)){
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
)){
1145 nact_main_statusbar_display_with_timeout(
1146 window
, TREE_MODEL_STATUSBAR_CONTEXT
, gettext( st_parent_not_writable
));
1149 return( accept_ok
);
1153 target_atom_to_id( GdkAtom atom
)
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
;
1166 g_free( atom_name
);