Change help menu item from Gtk default to Gnome (apparent) standard
[nautilus-actions.git] / src / nact / nact-tree-model-dnd.c
blob2bf439e7d9ed3802c36d14974aea0f8822e861c4
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 Pierre Wieser and others (see AUTHORS)
9 * This Program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License as
11 * published by the Free Software Foundation; either version 2 of
12 * the License, or (at your option) any later version.
14 * This Program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public
20 * License along with this Library; see the file COPYING. If not,
21 * write to the Free Software Foundation, Inc., 59 Temple Place,
22 * Suite 330, Boston, MA 02111-1307, USA.
24 * Authors:
25 * Frederic Ruaudel <grumz@grumz.net>
26 * Rodrigo Moya <rodrigo@gnome-db.org>
27 * Pierre Wieser <pwieser@trychlos.org>
28 * ... and many others (see AUTHORS)
31 #ifdef HAVE_CONFIG_H
32 #include <config.h>
33 #endif
35 #include <gconf/gconf-client.h>
36 #include <glib/gi18n.h>
37 #include <string.h>
39 #include <api/na-core-utils.h>
40 #include <api/na-object-api.h>
42 #include <core/na-iprefs.h>
43 #include <core/na-gnome-vfs-uri.h>
44 #include <core/na-importer.h>
46 #include "nact-application.h"
47 #include "nact-clipboard.h"
48 #include "nact-iactions-list.h"
49 #include "nact-iprefs.h"
50 #include "nact-main-menubar-edit.h"
51 #include "nact-main-statusbar.h"
52 #include "nact-main-window.h"
53 #include "nact-tree-model.h"
54 #include "nact-tree-model-dnd.h"
55 #include "nact-tree-model-priv.h"
58 * call once egg_tree_multi_drag_add_drag_support( treeview ) at init time (before gtk_main)
60 * when we start with drag
61 * call once egg_tree_multi_dnd_on_button_press_event( treeview, event, drag_source )
62 * call many egg_tree_multi_dnd_on_motion_event( treeview, event, drag_source )
63 * until mouse quits the selected area
65 * as soon as mouse has quitted the selected area
66 * call once egg_tree_multi_dnd_stop_drag_check( treeview )
67 * call once nact_tree_model_imulti_drag_source_row_draggable: drag_source=0x92a0d70, path_list=0x9373c90
68 * call once nact_clipboard_on_drag_begin( treeview, context, main_window )
70 * when we drop (e.g. in Nautilus)
71 * call once egg_tree_multi_dnd_on_drag_data_get( treeview, context, selection_data, info=0, time )
72 * call once nact_tree_model_imulti_drag_source_drag_data_get( drag_source, context, selection_data, path_list, atom=XdndDirectSave0 )
73 * call once nact_tree_model_idrag_dest_drag_data_received
74 * call once nact_clipboard_on_drag_end( treeview, context, main_window )
76 * when we drop in Nautilus Actions
77 * call once egg_tree_multi_dnd_on_drag_data_get( treeview, context, selection_data, info=0, time )
78 * call once nact_tree_model_imulti_drag_source_drag_data_get( drag_source, context, selection_data, path_list, atom=XdndNautilusActions )
79 * call once nact_clipboard_get_data_for_intern_use
80 * call once nact_tree_model_idrag_dest_drag_data_received
81 * call once nact_clipboard_on_drag_end( treeview, context, main_window )
84 #define MAX_XDS_ATOM_VAL_LEN 4096
85 #define TEXT_ATOM gdk_atom_intern( "text/plain", FALSE )
86 #define XDS_ATOM gdk_atom_intern( "XdndDirectSave0", FALSE )
87 #define XDS_FILENAME "xds.txt"
89 #define NACT_ATOM gdk_atom_intern( "XdndNautilusActions", FALSE )
91 /* as a dnd source, we provide
92 * - a special XdndNautilusAction format for internal move/copy
93 * - a XdndDirectSave, suitable for exporting to a file manager
94 * (note that Nautilus recognized the "XdndDirectSave0" format as XDS
95 * protocol)
96 * - a text (xml) format, to go to clipboard or a text editor
98 static GtkTargetEntry dnd_source_formats[] = {
99 { "XdndNautilusActions", GTK_TARGET_SAME_WIDGET, NACT_XCHANGE_FORMAT_NACT },
100 { "XdndDirectSave0", GTK_TARGET_OTHER_APP, NACT_XCHANGE_FORMAT_XDS },
101 { "application/xml", GTK_TARGET_OTHER_APP, NACT_XCHANGE_FORMAT_APPLICATION_XML },
102 { "text/plain", GTK_TARGET_OTHER_APP, NACT_XCHANGE_FORMAT_TEXT_PLAIN },
105 /* as a dnd dest, we accept
106 * - of course, the same special XdndNautilusAction format for internal move/copy
107 * - a list of uris, to be imported
108 * - a XML buffer, to be imported
109 * - a plain text, which we are goint to try to import as a XML description
111 GtkTargetEntry tree_model_dnd_dest_formats[] = {
112 { "XdndNautilusActions", 0, NACT_XCHANGE_FORMAT_NACT },
113 { "text/uri-list", 0, NACT_XCHANGE_FORMAT_URI_LIST },
114 { "application/xml", 0, NACT_XCHANGE_FORMAT_APPLICATION_XML },
115 { "text/plain", 0, NACT_XCHANGE_FORMAT_TEXT_PLAIN },
118 guint tree_model_dnd_dest_formats_count = G_N_ELEMENTS( tree_model_dnd_dest_formats );
120 static const gchar *st_refuse_drop_profile = N_( "Unable to drop a profile here" );
121 static const gchar *st_refuse_drop_item = N_( "Unable to drop an action or a menu here" );
122 static const gchar *st_parent_not_writable = N_( "Unable to drop here as parent is not writable" );
123 static const gchar *st_level_zero_not_writable = N_( "Unable to drop here as level zero is not writable" );
125 static gboolean drop_inside( NactTreeModel *model, GtkTreePath *dest, GtkSelectionData *selection_data );
126 static gboolean is_drop_possible( NactTreeModel *model, GtkTreePath *dest, NAObjectItem **parent );
127 static gboolean is_drop_possible_before_iter( NactTreeModel *model, GtkTreeIter *iter, NactMainWindow *window, NAObjectItem **parent );
128 static gboolean is_drop_possible_into_dest( NactTreeModel *model, GtkTreePath *dest, NactMainWindow *window, NAObjectItem **parent );
129 static void drop_inside_move_dest( NactTreeModel *model, GList *rows, GtkTreePath **dest );
130 static gboolean drop_uri_list( NactTreeModel *model, GtkTreePath *dest, GtkSelectionData *selection_data );
131 static NAObjectItem *is_dropped_already_exists( const NAObjectItem *importing, const NactMainWindow *window );
132 static char *get_xds_atom_value( GdkDragContext *context );
133 static gboolean is_parent_accept_new_childs( NactApplication *application, NactMainWindow *window, NAObjectItem *parent );
134 static guint target_atom_to_id( GdkAtom atom );
137 * nact_tree_model_dnd_idrag_dest_drag_data_received:
138 * @drag_dest:
139 * @dest:
140 * @selection_data:
142 * Called when a drop from the outside occurs in the treeview;
143 * this may be an import action, or a move/copy inside of the tree.
145 * Returns: %TRUE if the specified rows were successfully inserted at
146 * the given dest, %FALSE else.
148 * When importing:
149 * - selection=XdndSelection
150 * - target=text/uri-list
151 * - type=text/uri-list
153 * When moving/copy from the treeview to the treeview:
154 * - selection=XdndSelection
155 * - target=XdndNautilusActions
156 * - type=XdndNautilusActions
158 gboolean
159 nact_tree_model_dnd_idrag_dest_drag_data_received( GtkTreeDragDest *drag_dest, GtkTreePath *dest, GtkSelectionData *selection_data )
161 static const gchar *thisfn = "nact_tree_model_dnd_idrag_dest_drag_data_received";
162 gboolean result = FALSE;
163 gchar *atom_name;
164 guint info;
165 gchar *path_str;
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 );
172 g_free( atom_name );
174 atom_name = gdk_atom_name( selection_data->target );
175 g_debug( "%s: target=%s", thisfn, atom_name );
176 g_free( atom_name );
178 atom_name = gdk_atom_name( selection_data->type );
179 g_debug( "%s: type=%s", thisfn, atom_name );
180 g_free( 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 );
189 g_free( path_str );
191 switch( info ){
192 case NACT_XCHANGE_FORMAT_NACT:
193 result = drop_inside( NACT_TREE_MODEL( drag_dest ), dest, selection_data );
194 break;
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 );
201 break;
203 default:
204 break;
207 return( result );
211 * nact_tree_model_dnd_idrag_dest_row_drop_possible:
212 * @drag_dest:
213 * @dest_path:
214 * @selection_data:
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.
220 gboolean
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 );
227 return( TRUE );
231 * nact_tree_model_dnd_imulti_drag_source_drag_data_get:
232 * @context: contains
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)
236 * @selection_data:
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
242 * destination
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,
262 * %FALSE else.
264 gboolean
265 nact_tree_model_dnd_imulti_drag_source_drag_data_get( EggTreeMultiDragSource *drag_source,
266 GdkDragContext *context,
267 GtkSelectionData *selection_data,
268 GList *rows,
269 guint info )
271 static const gchar *thisfn = "nact_tree_model_dnd_imulti_drag_source_drag_data_get";
272 gchar *atom_name;
273 NactTreeModel *model;
274 gchar *data;
275 gboolean ret = FALSE;
276 gchar *dest_folder, *folder;
277 gboolean is_writable;
278 gboolean copy_data;
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,
283 atom_name );
284 g_free( atom_name );
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 )){
292 return( FALSE );
295 switch( info ){
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 );
300 ret = TRUE;
301 break;
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 );
316 if( is_writable ){
317 nact_clipboard_dnd_set( model->private->clipboard, info, rows, dest_folder, TRUE );
320 g_free( dest_folder );
321 g_free( folder );
322 ret = is_writable;
323 break;
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 ));
329 g_free( data );
330 ret = TRUE;
331 break;
333 default:
334 break;
338 return( ret );
342 * nact_tree_model_dnd_imulti_drag_source_drag_data_delete:
343 * @drag_source:
344 * @rows:
346 gboolean
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 );
353 return( TRUE );
357 * nact_tree_model_dnd_imulti_drag_source_get_drag_actions:
358 * @drag_source:
360 GdkDragAction
361 nact_tree_model_dnd_imulti_drag_source_get_drag_actions( EggTreeMultiDragSource *drag_source )
363 return( GDK_ACTION_COPY | GDK_ACTION_MOVE );
366 GtkTargetList *
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:
378 * @drag_source:
379 * @rows:
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.
385 gboolean
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;
390 GtkTreeModel *store;
391 GtkTreePath *path;
392 GtkTreeIter iter;
393 NAObject *object;
394 GList *it;
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 );
422 return( TRUE );
426 * nact_tree_model_dnd_on_drag_begin:
427 * @widget: the GtkTreeView from which item is to be dragged.
428 * @context:
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.
434 void
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 );
453 gdk_property_change(
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:
461 * @widget:
462 * @context:
463 * @window:
465 void
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
487 * the tree
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
499 * hierarchy
501 * adjusting the path: quid if the target dest is not at the same level
503 static gboolean
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;
508 NAUpdater *updater;
509 NactMainWindow *main_window;
510 NAObjectItem *parent;
511 gboolean copy_data;
512 GList *rows;
513 GtkTreePath *new_dest;
514 GtkTreePath *path;
515 NAObject *current;
516 NAObject *inserted;
517 GList *object_list, *it;
518 GtkTreeIter iter;
519 GList *deletable;
520 gboolean relabel;
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
531 parent = NULL;
532 rows = nact_clipboard_dnd_get_data( model->private->clipboard, &copy_data );
534 if( !is_drop_possible( model, dest, &parent )){
535 return( FALSE );
538 new_dest = gtk_tree_path_copy( dest );
539 if( !copy_data ){
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" );
544 object_list = NULL;
545 for( it = rows ; it ; it = it->next ){
546 path = gtk_tree_row_reference_get_path(( GtkTreeRowReference * ) it->data );
547 if( path ){
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, &current, -1 );
550 g_object_unref( current );
552 if( copy_data ){
553 inserted = ( NAObject * ) na_object_duplicate( current );
554 na_object_set_origin( inserted, NULL );
555 na_object_check_status( inserted );
557 } else {
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 );
585 g_list_free( rows );
587 return( TRUE );
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
601 * dest
603 static gboolean
604 is_drop_possible( NactTreeModel *model, GtkTreePath *dest, NAObjectItem **parent )
606 gboolean drop_ok;
607 NactApplication *application;
608 NactMainWindow *main_window;
609 GtkTreeIter iter;
610 NAObjectItem *parent_dest;
612 drop_ok = FALSE;
613 parent_dest = NULL;
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 );
632 } else {
633 drop_ok = TRUE;
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
640 } else {
641 drop_ok = is_drop_possible_into_dest( model, dest, main_window, &parent_dest );
644 if( drop_ok ){
645 drop_ok = is_parent_accept_new_childs( application, main_window, parent_dest );
648 if( drop_ok && parent ){
649 *parent = parent_dest;
652 return( drop_ok );
655 static gboolean
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";
659 gboolean drop_ok;
660 NAObject *object;
662 drop_ok = FALSE;
663 *parent = NULL;
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 )){
672 drop_ok = TRUE;
673 *parent = na_object_get_parent( object );
675 } else {
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 )){
682 drop_ok = TRUE;
683 *parent = na_object_get_parent( object );
685 } else {
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 );
691 return( drop_ok );
694 static gboolean
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";
698 gboolean drop_ok;
699 GtkTreePath *path;
700 GtkTreeIter iter;
701 NAObject *object;
703 drop_ok = FALSE;
704 *parent = NULL;
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 )){
717 drop_ok = TRUE;
718 *parent = NA_OBJECT_ITEM( object );
720 } else {
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 )){
726 drop_ok = TRUE;
727 *parent = na_object_get_parent( object );
729 } else {
730 nact_main_statusbar_display_with_timeout(
731 window, TREE_MODEL_STATUSBAR_CONTEXT, st_refuse_drop_item );
736 gtk_tree_path_free( path );
738 return( drop_ok );
741 static void
742 drop_inside_move_dest( NactTreeModel *model, GList *rows, GtkTreePath **dest )
744 GList *it;
745 GtkTreePath *path;
746 gint before;
747 gint i;
748 gint *indices;
750 g_return_if_fail( dest );
752 before = 0;
753 for( it = rows ; it ; it = it->next ){
754 path = gtk_tree_row_reference_get_path(( GtkTreeRowReference * ) it->data );
755 if( path ){
756 if( gtk_tree_path_get_depth( path ) == 1 && gtk_tree_path_compare( path, *dest ) == -1 ){
757 before += 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 ));
766 if( before ){
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
790 static gboolean
791 drop_uri_list( NactTreeModel *model, GtkTreePath *dest, GtkSelectionData *selection_data )
793 static const gchar *thisfn = "nact_tree_model_dnd_drop_uri_list";
794 gboolean drop_done;
795 NactApplication *application;
796 NAUpdater *updater;
797 NactMainWindow *main_window;
798 NAImporterParms parms;
799 GConfClient *gconf;
800 GList *it;
801 guint count;
802 GString *str;
803 GSList *im;
804 GList *imported;
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 );
809 g_free( dest_str );
811 drop_done = FALSE;
812 model->private->drag_has_profiles = FALSE;
814 if( !is_drop_possible( model, dest, NULL )){
815 return( FALSE );
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
842 count = 0;
843 str = g_string_new( "" );
844 imported = NULL;
846 for( it = parms.results ; it ; it = it->next ){
847 NAImporterResult *result = ( NAImporterResult * ) it->data;
849 if( result->messages ){
850 if( count == 0 ){
851 nact_main_statusbar_display_with_timeout(
852 main_window,
853 TREE_MODEL_STATUSBAR_CONTEXT,
854 result->messages->data );
856 count += 1;
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
869 if( count > 1 ){
870 GtkMessageDialog *dialog = GTK_MESSAGE_DIALOG( gtk_message_dialog_new(
871 parms.parent,
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 );
884 drop_done = TRUE;
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 );
898 return( drop_done );
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 );
906 g_free( id );
908 return( exists );
912 * this function works well, but only called from on_drag_motion handler...
914 /*static gboolean
915 is_row_drop_possible( NactTreeModel *model, GtkTreePath *path, GtkTreeViewDropPosition pos )
917 gboolean ok = FALSE;
918 GtkTreeModel *store;
919 GtkTreeIter iter;
920 NAObject *object;
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 )){
928 ok = TRUE;
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 );
932 } else {
933 if( NA_IS_OBJECT_ITEM( object )){
934 ok = TRUE;
938 g_object_unref( object );
939 return( ok );
943 * called when the user moves into the target widget
944 * returns TRUE if a drop zone
946 #if 0
947 static gboolean
948 on_drag_motion( GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, BaseWindow *window )
950 NactTreeModel *model;
951 gboolean ok = FALSE;
952 GtkTreePath *path;
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 );
968 } else {
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 );
974 if( ok ){
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 );
983 return( ok );
985 #endif
988 * called when the user drops the data
989 * returns TRUE if a drop zone
991 /*static gboolean
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;
1004 return( 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
1011 * License: GPL
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.
1021 static char *
1022 get_xds_atom_value( GdkDragContext *context )
1024 gchar *ret;
1025 gint actual_length;
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';
1043 return( ret );
1047 * when dropping something somewhere, we must ensure that we will be able
1048 * to register the new child
1050 static gboolean
1051 is_parent_accept_new_childs( NactApplication *application, NactMainWindow *window, NAObjectItem *parent )
1053 gboolean accept_ok;
1054 NAUpdater *updater;
1056 accept_ok = FALSE;
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 ))){
1065 accept_ok = TRUE;
1067 } else {
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 )){
1075 accept_ok = TRUE;
1077 } else {
1078 nact_main_statusbar_display_with_timeout(
1079 window, TREE_MODEL_STATUSBAR_CONTEXT, st_parent_not_writable );
1082 return( accept_ok );
1085 static guint
1086 target_atom_to_id( GdkAtom atom )
1088 gint i;
1089 guint info = 0;
1090 gchar *atom_name;
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;
1096 break;
1099 g_free( atom_name );
1100 return( info );