Only GConf writing operations are marked deprecated
[nautilus-actions.git] / src / plugin-menu / nautilus-actions.c
blob4e4b10acb87ca8b3dddedb05c9ab795c1c484976
1 /*
2 * Nautilus-Actions
3 * A Nautilus extension which offers configurable context menu actions.
5 * Copyright (C) 2005 The GNOME Foundation
6 * Copyright (C) 2006, 2007, 2008 Frederic Ruaudel and others (see AUTHORS)
7 * Copyright (C) 2009, 2010, 2011 Pierre Wieser and others (see AUTHORS)
9 * This Program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License as
11 * published by the Free Software Foundation; either version 2 of
12 * the License, or (at your option) any later version.
14 * This Program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public
20 * License along with this Library; see the file COPYING. If not,
21 * write to the Free Software Foundation, Inc., 59 Temple Place,
22 * Suite 330, Boston, MA 02111-1307, USA.
24 * Authors:
25 * Frederic Ruaudel <grumz@grumz.net>
26 * Rodrigo Moya <rodrigo@gnome-db.org>
27 * Pierre Wieser <pwieser@trychlos.org>
28 * ... and many others (see AUTHORS)
31 #ifdef HAVE_CONFIG_H
32 #include <config.h>
33 #endif
35 #include <string.h>
37 #include <glib/gi18n.h>
39 #include <libnautilus-extension/nautilus-extension-types.h>
40 #include <libnautilus-extension/nautilus-file-info.h>
41 #include <libnautilus-extension/nautilus-menu-provider.h>
43 #include <api/na-core-utils.h>
44 #include <api/na-object-api.h>
45 #include <api/na-timeout.h>
47 #include <core/na-pivot.h>
48 #include <core/na-iabout.h>
49 #include <core/na-selected-info.h>
50 #include <core/na-tokens.h>
52 #include "nautilus-actions.h"
54 /* private class data
56 struct _NautilusActionsClassPrivate {
57 void *empty; /* so that gcc -pedantic is happy */
60 /* private instance data
62 struct _NautilusActionsPrivate {
63 gboolean dispose_has_run;
64 NAPivot *pivot;
65 gulong items_changed_handler;
66 gulong settings_changed_handler;
67 NATimeout change_timeout;
70 static GObjectClass *st_parent_class = NULL;
71 static GType st_actions_type = 0;
72 static gint st_burst_timeout = 100; /* burst timeout in msec */
74 static void class_init( NautilusActionsClass *klass );
75 static void instance_init( GTypeInstance *instance, gpointer klass );
76 static void instance_constructed( GObject *object );
77 static void instance_dispose( GObject *object );
78 static void instance_finalize( GObject *object );
80 static void iabout_iface_init( NAIAboutInterface *iface );
81 static gchar *iabout_get_application_name( NAIAbout *instance );
83 static void menu_provider_iface_init( NautilusMenuProviderIface *iface );
84 static GList *menu_provider_get_background_items( NautilusMenuProvider *provider, GtkWidget *window, NautilusFileInfo *current_folder );
85 static GList *menu_provider_get_file_items( NautilusMenuProvider *provider, GtkWidget *window, GList *files );
86 static GList *menu_provider_get_toolbar_items( NautilusMenuProvider *provider, GtkWidget *window, NautilusFileInfo *current_folder );
88 static GList *get_menus_items( NautilusActions *plugin, guint target, GList *selection );
89 static GList *expand_tokens( GList *tree, NATokens *tokens );
90 static NAObjectItem *expand_tokens_item( NAObjectItem *item, NATokens *tokens );
91 static void expand_tokens_context( NAIContext *context, NATokens *tokens );
92 static GList *build_nautilus_menus( NautilusActions *plugin, GList *tree, guint target, GList *files, NATokens *tokens );
93 static NAObjectProfile *get_candidate_profile( NautilusActions *plugin, NAObjectAction *action, guint target, GList *files );
94 static NautilusMenuItem *create_item_from_profile( NAObjectProfile *profile, guint target, GList *files, NATokens *tokens );
95 static NautilusMenuItem *create_item_from_menu( NAObjectMenu *menu, GList *subitems, guint target );
96 static NautilusMenuItem *create_menu_item( NAObjectItem *item, guint target );
97 static void attach_submenu_to_item( NautilusMenuItem *item, GList *subitems );
98 static void weak_notify_profile( NAObjectProfile *profile, NautilusMenuItem *item );
100 static void execute_action( NautilusMenuItem *item, NAObjectProfile *profile );
102 static GList *create_root_menu( NautilusActions *plugin, GList *nautilus_menu );
103 static GList *add_about_item( NautilusActions *plugin, GList *nautilus_menu );
104 static void execute_about( NautilusMenuItem *item, NautilusActions *plugin );
106 static void on_pivot_items_changed_handler( NAPivot *pivot, NautilusActions *plugin );
107 static void on_settings_key_changed_handler( const gchar *group, const gchar *key, gconstpointer new_value, gboolean mandatory, NautilusActions *plugin );
108 static void on_change_event_timeout( NautilusActions *plugin );
110 GType
111 nautilus_actions_get_type( void )
113 g_assert( st_actions_type );
114 return( st_actions_type );
117 void
118 nautilus_actions_register_type( GTypeModule *module )
120 static const gchar *thisfn = "nautilus_actions_register_type";
122 static const GTypeInfo info = {
123 sizeof( NautilusActionsClass ),
124 ( GBaseInitFunc ) NULL,
125 ( GBaseFinalizeFunc ) NULL,
126 ( GClassInitFunc ) class_init,
127 NULL,
128 NULL,
129 sizeof( NautilusActions ),
131 ( GInstanceInitFunc ) instance_init,
134 static const GInterfaceInfo menu_provider_iface_info = {
135 ( GInterfaceInitFunc ) menu_provider_iface_init,
136 NULL,
137 NULL
140 static const GInterfaceInfo iabout_iface_info = {
141 ( GInterfaceInitFunc ) iabout_iface_init,
142 NULL,
143 NULL
146 g_assert( st_actions_type == 0 );
148 g_debug( "%s: module=%p", thisfn, ( void * ) module );
150 st_actions_type = g_type_module_register_type( module, G_TYPE_OBJECT, "NautilusActions", &info, 0 );
152 g_type_module_add_interface( module, st_actions_type, NAUTILUS_TYPE_MENU_PROVIDER, &menu_provider_iface_info );
154 g_type_module_add_interface( module, st_actions_type, NA_IABOUT_TYPE, &iabout_iface_info );
157 static void
158 class_init( NautilusActionsClass *klass )
160 static const gchar *thisfn = "nautilus_actions_class_init";
161 GObjectClass *gobject_class;
163 g_debug( "%s: klass=%p", thisfn, ( void * ) klass );
165 st_parent_class = g_type_class_peek_parent( klass );
167 gobject_class = G_OBJECT_CLASS( klass );
168 gobject_class->constructed = instance_constructed;
169 gobject_class->dispose = instance_dispose;
170 gobject_class->finalize = instance_finalize;
172 klass->private = g_new0( NautilusActionsClassPrivate, 1 );
175 static void
176 instance_init( GTypeInstance *instance, gpointer klass )
178 static const gchar *thisfn = "nautilus_actions_instance_init";
179 NautilusActions *self;
181 g_return_if_fail( NAUTILUS_IS_ACTIONS( instance ));
183 g_debug( "%s: instance=%p, klass=%p", thisfn, ( void * ) instance, ( void * ) klass );
185 self = NAUTILUS_ACTIONS( instance );
187 self->private = g_new0( NautilusActionsPrivate, 1 );
189 self->private->dispose_has_run = FALSE;
190 self->private->change_timeout.timeout = st_burst_timeout;
191 self->private->change_timeout.handler = ( NATimeoutFunc ) on_change_event_timeout;
192 self->private->change_timeout.user_data = self;
193 self->private->change_timeout.source_id = 0;
197 * Runtime modification management:
198 * We have to react to some runtime environment modifications:
200 * - whether the items list has changed (we have to reload a new pivot)
201 * > registering for notifications against NAPivot
203 * - whether to add the 'About Nautilus-Actions' item
204 * - whether to create a 'Nautilus-Actions actions' root menu
205 * > registering for notifications against NASettings
207 static void
208 instance_constructed( GObject *object )
210 static const gchar *thisfn = "nautilus_actions_instance_constructed";
211 NautilusActions *self;
212 NASettings *settings;
214 g_return_if_fail( NAUTILUS_IS_ACTIONS( object ));
216 self = NAUTILUS_ACTIONS( object );
218 if( !self->private->dispose_has_run ){
219 g_debug( "%s: object=%p", thisfn, ( void * ) object );
221 self->private->pivot = na_pivot_new();
223 /* setup NAPivot properties before loading items
225 na_pivot_set_loadable( self->private->pivot, !PIVOT_LOAD_DISABLED & !PIVOT_LOAD_INVALID );
226 na_pivot_load_items( self->private->pivot );
228 /* register against NAPivot to be notified of items changes
230 self->private->items_changed_handler =
231 g_signal_connect( self->private->pivot,
232 PIVOT_SIGNAL_ITEMS_CHANGED, G_CALLBACK( on_pivot_items_changed_handler ), self );
234 /* register against NASettings to be notified of changes on
235 * our runtime preferences
236 * because we only monitor here four runtime keys, we prefer the
237 * callback way that the signal one
239 settings = na_pivot_get_settings( self->private->pivot );
241 na_settings_register_key_callback( settings,
242 NA_IPREFS_IO_PROVIDERS_READ_STATUS, G_CALLBACK( on_settings_key_changed_handler ), self );
243 na_settings_register_key_callback( settings,
244 NA_IPREFS_ITEMS_ADD_ABOUT_ITEM, G_CALLBACK( on_settings_key_changed_handler ), self );
245 na_settings_register_key_callback( settings,
246 NA_IPREFS_ITEMS_CREATE_ROOT_MENU, G_CALLBACK( on_settings_key_changed_handler ), self );
247 na_settings_register_key_callback( settings,
248 NA_IPREFS_ITEMS_LEVEL_ZERO_ORDER, G_CALLBACK( on_settings_key_changed_handler ), self );
249 na_settings_register_key_callback( settings,
250 NA_IPREFS_ITEMS_LIST_ORDER_MODE, G_CALLBACK( on_settings_key_changed_handler ), self );
252 /* chain up to the parent class */
253 if( G_OBJECT_CLASS( st_parent_class )->constructed ){
254 G_OBJECT_CLASS( st_parent_class )->constructed( object );
259 static void
260 instance_dispose( GObject *object )
262 static const gchar *thisfn = "nautilus_actions_instance_dispose";
263 NautilusActions *self;
265 g_debug( "%s: object=%p", thisfn, ( void * ) object );
266 g_return_if_fail( NAUTILUS_IS_ACTIONS( object ));
267 self = NAUTILUS_ACTIONS( object );
269 if( !self->private->dispose_has_run ){
271 self->private->dispose_has_run = TRUE;
273 if( self->private->items_changed_handler ){
274 g_signal_handler_disconnect( self->private->pivot, self->private->items_changed_handler );
276 g_object_unref( self->private->pivot );
278 /* chain up to the parent class */
279 if( G_OBJECT_CLASS( st_parent_class )->dispose ){
280 G_OBJECT_CLASS( st_parent_class )->dispose( object );
285 static void
286 instance_finalize( GObject *object )
288 static const gchar *thisfn = "nautilus_actions_instance_finalize";
289 NautilusActions *self;
291 g_debug( "%s: object=%p", thisfn, ( void * ) object );
292 g_return_if_fail( NAUTILUS_IS_ACTIONS( object ));
293 self = NAUTILUS_ACTIONS( object );
295 g_free( self->private );
297 /* chain up to the parent class */
298 if( G_OBJECT_CLASS( st_parent_class )->finalize ){
299 G_OBJECT_CLASS( st_parent_class )->finalize( object );
304 * This function notifies Nautilus file manager that the context menu
305 * items may have changed, and that it should reload them.
307 * Patch has been provided by Frederic Ruaudel, the initial author of
308 * Nautilus-Actions, and applied on Nautilus 2.15.4 development branch
309 * on 2006-06-16. It was released with Nautilus 2.16.0
311 #ifndef HAVE_NAUTILUS_MENU_PROVIDER_EMIT_ITEMS_UPDATED_SIGNAL
312 static void nautilus_menu_provider_emit_items_updated_signal (NautilusMenuProvider *provider)
314 /* -> fake function for backward compatibility
315 * -> do nothing
318 #endif
320 static void
321 iabout_iface_init( NAIAboutInterface *iface )
323 static const gchar *thisfn = "nautilus_actions_iabout_iface_init";
325 g_debug( "%s: iface=%p", thisfn, ( void * ) iface );
327 iface->get_application_name = iabout_get_application_name;
330 static gchar *
331 iabout_get_application_name( NAIAbout *instance )
333 /* i18n: title of the About dialog box, when seen from Nautilus file manager */
334 return( g_strdup( _( "Nautilus-Actions" )));
337 static void
338 menu_provider_iface_init( NautilusMenuProviderIface *iface )
340 static const gchar *thisfn = "nautilus_actions_menu_provider_iface_init";
342 g_debug( "%s: iface=%p", thisfn, ( void * ) iface );
344 iface->get_file_items = menu_provider_get_file_items;
345 iface->get_background_items = menu_provider_get_background_items;
346 iface->get_toolbar_items = menu_provider_get_toolbar_items;
350 * this function is called when nautilus has to paint a folder background
351 * one of the first calls is with current_folder = 'x-nautilus-desktop:///'
352 * the menu items are available :
353 * a) in File menu
354 * b) in contextual menu of the folder if there is no current selection
356 * get_background_items is very similar, from the user point of view, to
357 * get_file_items when:
358 * - either there is zero item selected - current_folder should so be the
359 * folder currently displayed in the file manager view
360 * - or when there is only one selected directory
362 * Note that 'x-nautilus-desktop:///' cannot be interpreted by
363 * #NASelectedInfo::query_file_attributes() function. It so never participate
364 * to the display of actions.
366 static GList *
367 menu_provider_get_background_items( NautilusMenuProvider *provider, GtkWidget *window, NautilusFileInfo *current_folder )
369 static const gchar *thisfn = "nautilus_actions_menu_provider_get_background_items";
370 GList *nautilus_menus_list = NULL;
371 gchar *uri;
372 GList *selected;
374 g_return_val_if_fail( NAUTILUS_IS_ACTIONS( provider ), NULL );
376 if( !NAUTILUS_ACTIONS( provider )->private->dispose_has_run ){
378 selected = na_selected_info_get_list_from_item( current_folder );
380 if( selected ){
381 uri = nautilus_file_info_get_uri( current_folder );
382 g_debug( "%s: provider=%p, window=%p, current_folder=%p (%s)",
383 thisfn, ( void * ) provider, ( void * ) window, ( void * ) current_folder, uri );
384 g_free( uri );
386 nautilus_menus_list = get_menus_items( NAUTILUS_ACTIONS( provider ), ITEM_TARGET_LOCATION, selected );
387 na_selected_info_free_list( selected );
391 return( nautilus_menus_list );
395 * this function is called each time the selection changed
396 * menus items are available :
397 * a) in Edit menu while the selection stays unchanged
398 * b) in contextual menu while the selection stays unchanged
400 static GList *
401 menu_provider_get_file_items( NautilusMenuProvider *provider, GtkWidget *window, GList *files )
403 static const gchar *thisfn = "nautilus_actions_menu_provider_get_file_items";
404 GList *nautilus_menus_list = NULL;
405 GList *selected;
407 g_return_val_if_fail( NAUTILUS_IS_ACTIONS( provider ), NULL );
409 if( !NAUTILUS_ACTIONS( provider )->private->dispose_has_run ){
411 /* no need to go further if there is no files in the list */
412 if( !g_list_length( files )){
413 return(( GList * ) NULL );
416 #ifdef NA_MAINTAINER_MODE
417 GList *im;
418 for( im = files ; im ; im = im->next ){
419 gchar *uri = nautilus_file_info_get_uri( NAUTILUS_FILE_INFO( im->data ));
420 g_debug( "%s: uri=%s", thisfn, uri );
421 g_free( uri );
423 #endif
425 selected = na_selected_info_get_list_from_list(( GList * ) files );
427 if( selected ){
428 g_debug( "%s: provider=%p, window=%p, files=%p, count=%d",
429 thisfn, ( void * ) provider, ( void * ) window, ( void * ) files, g_list_length( files ));
431 nautilus_menus_list = get_menus_items( NAUTILUS_ACTIONS( provider ), ITEM_TARGET_SELECTION, selected );
432 na_selected_info_free_list( selected );
436 return( nautilus_menus_list );
440 * as of 2.26, this function is only called for folders, but for the
441 * desktop (x-nautilus-desktop:///) which seems to be only called by
442 * get_background_items ; also, only actions (not menus) are displayed
444 static GList *
445 menu_provider_get_toolbar_items( NautilusMenuProvider *provider, GtkWidget *window, NautilusFileInfo *current_folder )
447 static const gchar *thisfn = "nautilus_actions_menu_provider_get_toolbar_items";
448 GList *nautilus_menus_list = NULL;
449 gchar *uri;
450 GList *selected;
452 g_return_val_if_fail( NAUTILUS_IS_ACTIONS( provider ), NULL );
454 if( !NAUTILUS_ACTIONS( provider )->private->dispose_has_run ){
456 selected = na_selected_info_get_list_from_item( current_folder );
458 if( selected ){
459 uri = nautilus_file_info_get_uri( current_folder );
460 g_debug( "%s: provider=%p, window=%p, current_folder=%p (%s)",
461 thisfn, ( void * ) provider, ( void * ) window, ( void * ) current_folder, uri );
462 g_free( uri );
464 nautilus_menus_list = get_menus_items( NAUTILUS_ACTIONS( provider ), ITEM_TARGET_TOOLBAR, selected );
465 na_selected_info_free_list( selected );
469 return( nautilus_menus_list );
472 static GList *
473 get_menus_items( NautilusActions *plugin, guint target, GList *selection )
475 GList *menus_list;
476 NATokens *tokens;
477 GList *pivot_tree, *copy_tree;
478 NASettings *settings;
479 gboolean items_add_about_item;
480 gboolean items_create_root_menu;
482 g_return_val_if_fail( NA_IS_PIVOT( plugin->private->pivot ), NULL );
484 tokens = na_tokens_new_from_selection( selection );
485 pivot_tree = na_pivot_get_items( plugin->private->pivot );
486 copy_tree = expand_tokens( pivot_tree, tokens );
488 menus_list = build_nautilus_menus( plugin, copy_tree, target, selection, tokens );
490 na_object_free_items( copy_tree );
491 g_object_unref( tokens );
493 if( target != ITEM_TARGET_TOOLBAR ){
495 settings = na_pivot_get_settings( plugin->private->pivot );
497 items_create_root_menu = na_settings_get_boolean( settings, NA_IPREFS_ITEMS_CREATE_ROOT_MENU, NULL, NULL );
498 if( items_create_root_menu ){
499 menus_list = create_root_menu( plugin, menus_list );
502 items_add_about_item = na_settings_get_boolean( settings, NA_IPREFS_ITEMS_ADD_ABOUT_ITEM, NULL, NULL );
503 if( items_add_about_item ){
504 menus_list = add_about_item( plugin, menus_list );
508 return( menus_list );
512 * create a copy of the tree where almost all fields which may embed
513 * parameters have been expanded
514 * here, 'almost' should be read as:
515 * - all displayable fields, or fields which may have an impact on the display
516 * (e.g. label, tooltip, icon name)
517 * - all fields which we do not need later
519 * we keep until the last item activation the Exec key, whose first parameter
520 * actualy determines the form (singular or plural) of the execution..
522 static GList *
523 expand_tokens( GList *pivot_tree, NATokens *tokens )
525 GList *tree, *it;
527 tree = NULL;
529 for( it = pivot_tree ; it ; it = it->next ){
530 NAObjectItem *item = NA_OBJECT_ITEM( na_object_duplicate( it->data ));
531 tree = g_list_prepend( tree, expand_tokens_item( item, tokens ));
534 return( g_list_reverse( tree ));
537 static NAObjectItem *
538 expand_tokens_item( NAObjectItem *item, NATokens *tokens )
540 gchar *old, *new;
541 GSList *subitems_slist, *its, *new_slist;
542 GList *subitems, *it, *new_list;
543 NAObjectItem *expanded_item;
545 /* label, tooltip and icon name
546 * plus the toolbar label if this is an action
548 old = na_object_get_label( item );
549 new = na_tokens_parse_for_display( tokens, old, TRUE );
550 na_object_set_label( item, new );
551 g_free( old );
552 g_free( new );
554 old = na_object_get_tooltip( item );
555 new = na_tokens_parse_for_display( tokens, old, TRUE );
556 na_object_set_tooltip( item, new );
557 g_free( old );
558 g_free( new );
560 old = na_object_get_icon( item );
561 new = na_tokens_parse_for_display( tokens, old, TRUE );
562 na_object_set_icon( item, new );
563 g_free( old );
564 g_free( new );
566 if( NA_IS_OBJECT_ACTION( item )){
567 old = na_object_get_toolbar_label( item );
568 new = na_tokens_parse_for_display( tokens, old, TRUE );
569 na_object_set_toolbar_label( item, new );
570 g_free( old );
571 g_free( new );
574 /* A NAObjectItem, whether it is an action or a menu, is also a NAIContext
576 expand_tokens_context( NA_ICONTEXT( item ), tokens );
578 /* subitems lists, whether this is the profiles list of an action
579 * or the items list of a menu, may be dynamic and embed a command;
580 * this command itself may embed parameters
582 subitems_slist = na_object_get_items_slist( item );
583 new_slist = NULL;
584 for( its = subitems_slist ; its ; its = its->next ){
585 old = ( gchar * ) its->data;
586 if( old[0] == '[' && old[strlen(old)-1] == ']' ){
587 new = na_tokens_parse_for_display( tokens, old, FALSE );
588 } else {
589 new = g_strdup( old );
591 new_slist = g_slist_prepend( new_slist, new );
593 na_object_set_items_slist( item, new_slist );
594 na_core_utils_slist_free( subitems_slist );
595 na_core_utils_slist_free( new_slist );
597 /* last, deal with subitems
599 subitems = na_object_get_items( item );
601 if( NA_IS_OBJECT_MENU( item )){
602 new_list = NULL;
604 for( it = subitems ; it ; it = it->next ){
605 expanded_item = expand_tokens_item( NA_OBJECT_ITEM( it->data ), tokens );
606 new_list = g_list_prepend( new_list, expanded_item );
609 na_object_set_items( item, g_list_reverse( new_list ));
611 } else {
612 g_return_val_if_fail( NA_IS_OBJECT_ACTION( item ), NULL );
614 for( it = subitems ; it ; it = it->next ){
616 /* desktop Exec key = GConf path+parameters
617 * do not touch them here
619 old = na_object_get_working_dir( it->data );
620 new = na_tokens_parse_for_display( tokens, old, FALSE );
621 na_object_set_working_dir( it->data, new );
622 g_free( old );
623 g_free( new );
625 /* a NAObjectProfile is also a NAIContext
627 expand_tokens_context( NA_ICONTEXT( it->data ), tokens );
631 return( item );
634 static void
635 expand_tokens_context( NAIContext *context, NATokens *tokens )
637 gchar *old, *new;
639 old = na_object_get_try_exec( context );
640 new = na_tokens_parse_for_display( tokens, old, FALSE );
641 na_object_set_try_exec( context, new );
642 g_free( old );
643 g_free( new );
645 old = na_object_get_show_if_registered( context );
646 new = na_tokens_parse_for_display( tokens, old, FALSE );
647 na_object_set_show_if_registered( context, new );
648 g_free( old );
649 g_free( new );
651 old = na_object_get_show_if_true( context );
652 new = na_tokens_parse_for_display( tokens, old, FALSE );
653 na_object_set_show_if_true( context, new );
654 g_free( old );
655 g_free( new );
657 old = na_object_get_show_if_running( context );
658 new = na_tokens_parse_for_display( tokens, old, FALSE );
659 na_object_set_show_if_running( context, new );
660 g_free( old );
661 g_free( new );
665 * @plugin: this #NautilusActions module instance.
666 * @tree: a copy of the #NAPivot tree, where all fields - but
667 * displayable parameters expanded
668 * @target: whether we target location or context menu, or toolbar.
669 * @files: the current selection in the file-manager, as a #GList of #NASelectedInfo items
670 * @tokens: a #NATokens object which embeds all possible values, regarding the
671 * current selection, for all parameters
673 * When building a menu for the toolbar, do not use menus hierarchy
675 * As menus, actions and profiles may embed parameters in their data,
676 * all the hierarchy must be recursively re-parsed, and should be
677 * re-checked for validity !
679 static GList *
680 build_nautilus_menus( NautilusActions *plugin, GList *tree, guint target, GList *files, NATokens *tokens )
682 static const gchar *thisfn = "nautilus_actions_build_nautilus_menus";
683 GList *menus_list = NULL;
684 GList *subitems, *submenu;
685 GList *it;
686 NAObjectProfile *profile;
687 NautilusMenuItem *item;
689 g_debug( "%s: plugin=%p, tree=%p, target=%d, files=%p (count=%d)",
690 thisfn, ( void * ) plugin, ( void * ) tree, target,
691 ( void * ) files, g_list_length( files ));
693 for( it = tree ; it ; it = it->next ){
695 g_return_val_if_fail( NA_IS_OBJECT_ITEM( it->data ), NULL );
697 #ifdef NA_MAINTAINER_MODE
698 /* check this here as a security though NAPivot should only have
699 * loaded valid and enabled items
701 if( !na_object_is_enabled( it->data )){
702 gchar *label = na_object_get_label( it->data );
703 g_warning( "%s: '%s' item: enabled=%s, valid=%s", thisfn, label,
704 na_object_is_enabled( it->data ) ? "True":"False",
705 na_object_is_valid( it->data ) ? "True":"False" );
706 g_free( label );
707 continue;
709 #endif
711 /* but we have to re-check for validity as a label may become
712 * dynamically empty - thus the NAObjectItem invalid :(
714 if( !na_object_is_valid( it->data )){
715 continue;
718 if( !na_icontext_is_candidate( NA_ICONTEXT( it->data ), target, files )){
719 continue;
722 /* recursively build sub-menus
724 if( NA_IS_OBJECT_MENU( it->data )){
725 subitems = na_object_get_items( it->data );
726 g_debug( "%s: menu has %d items", thisfn, g_list_length( subitems ));
727 submenu = build_nautilus_menus( plugin, subitems, target, files, tokens );
728 g_debug( "%s: submenu has %d items", thisfn, g_list_length( submenu ));
730 if( submenu ){
731 if( target == ITEM_TARGET_TOOLBAR ){
732 menus_list = g_list_concat( menus_list, submenu );
734 } else {
735 item = create_item_from_menu( NA_OBJECT_MENU( it->data ), submenu, target );
736 menus_list = g_list_append( menus_list, item );
739 continue;
742 g_return_val_if_fail( NA_IS_OBJECT_ACTION( it->data ), NULL );
744 profile = get_candidate_profile( plugin, NA_OBJECT_ACTION( it->data ), target, files );
745 if( profile ){
746 item = create_item_from_profile( profile, target, files, tokens );
747 menus_list = g_list_append( menus_list, item );
751 return( menus_list );
755 * could also be a NAObjectAction method - but this is not used elsewhere
757 static NAObjectProfile *
758 get_candidate_profile( NautilusActions *plugin, NAObjectAction *action, guint target, GList *files )
760 static const gchar *thisfn = "nautilus_actions_get_candidate_profile";
761 NAObjectProfile *candidate = NULL;
762 gchar *action_label;
763 gchar *profile_label;
764 GList *profiles, *ip;
766 action_label = na_object_get_label( action );
767 profiles = na_object_get_items( action );
769 for( ip = profiles ; ip && !candidate ; ip = ip->next ){
770 NAObjectProfile *profile = NA_OBJECT_PROFILE( ip->data );
772 if( na_icontext_is_candidate( NA_ICONTEXT( profile ), target, files )){
773 profile_label = na_object_get_label( profile );
774 g_debug( "%s: selecting %s (profile=%p '%s')", thisfn, action_label, ( void * ) profile, profile_label );
775 g_free( profile_label );
777 candidate = profile;
781 g_free( action_label );
783 return( candidate );
786 static NautilusMenuItem *
787 create_item_from_profile( NAObjectProfile *profile, guint target, GList *files, NATokens *tokens )
789 NautilusMenuItem *item;
790 NAObjectAction *action;
791 NAObjectProfile *duplicate;
793 action = NA_OBJECT_ACTION( na_object_get_parent( profile ));
794 duplicate = NA_OBJECT_PROFILE( na_object_duplicate( profile ));
795 na_object_set_parent( duplicate, NULL );
797 item = create_menu_item( NA_OBJECT_ITEM( action ), target );
799 /* attach a weak ref on the Nautilus menu item: our profile will be
800 * unreffed in weak notify function
802 g_signal_connect( item,
803 "activate",
804 G_CALLBACK( execute_action ),
805 duplicate );
807 g_object_weak_ref( G_OBJECT( item ), ( GWeakNotify ) weak_notify_profile, duplicate );
809 g_object_set_data_full( G_OBJECT( item ),
810 "nautilus-actions-tokens",
811 g_object_ref( tokens ),
812 ( GDestroyNotify ) g_object_unref );
814 return( item );
818 * called _after_ the NautilusMenuItem has been finalized
820 static void
821 weak_notify_profile( NAObjectProfile *profile, NautilusMenuItem *item )
823 g_debug( "nautilus_actions_weak_notify_profile: profile=%p (ref_count=%d)",
824 ( void * ) profile, G_OBJECT( profile )->ref_count );
826 g_object_unref( profile );
830 * note that each appended NautilusMenuItem is ref-ed by the NautilusMenu
831 * we can so safely release our own ref on subitems after this function
833 static NautilusMenuItem *
834 create_item_from_menu( NAObjectMenu *menu, GList *subitems, guint target )
836 /*static const gchar *thisfn = "nautilus_actions_create_item_from_menu";*/
837 NautilusMenuItem *item;
839 item = create_menu_item( NA_OBJECT_ITEM( menu ), target );
841 attach_submenu_to_item( item, subitems );
842 nautilus_menu_item_list_free( subitems );
844 /*g_debug( "%s: returning item=%p", thisfn, ( void * ) item );*/
845 return( item );
848 static NautilusMenuItem *
849 create_menu_item( NAObjectItem *item, guint target )
851 NautilusMenuItem *menu_item;
852 gchar *id, *name, *label, *tooltip, *icon;
854 id = na_object_get_id( item );
855 name = g_strdup_printf( "%s-%s-%s-%d", PACKAGE, G_OBJECT_TYPE_NAME( item ), id, target );
856 label = na_object_get_label( item );
857 tooltip = na_object_get_tooltip( item );
858 icon = na_object_get_icon( item );
860 menu_item = nautilus_menu_item_new( name, label, tooltip, icon );
862 g_free( icon );
863 g_free( tooltip );
864 g_free( label );
865 g_free( name );
866 g_free( id );
868 return( menu_item );
871 static void
872 attach_submenu_to_item( NautilusMenuItem *item, GList *subitems )
874 NautilusMenu *submenu;
875 GList *it;
877 submenu = nautilus_menu_new();
878 nautilus_menu_item_set_submenu( item, submenu );
880 for( it = subitems ; it ; it = it->next ){
881 nautilus_menu_append_item( submenu, NAUTILUS_MENU_ITEM( it->data ));
886 * callback triggered when an item is activated
887 * path and parameters must yet been parsed against tokens
889 * note that if first parameter if of singular form, then we have to loop
890 * againt the selected, each time replacing the singular parameters with
891 * the current item of the selection
893 static void
894 execute_action( NautilusMenuItem *item, NAObjectProfile *profile )
896 static const gchar *thisfn = "nautilus_actions_execute_action";
897 NATokens *tokens;
899 g_debug( "%s: item=%p, profile=%p", thisfn, ( void * ) item, ( void * ) profile );
901 tokens = NA_TOKENS( g_object_get_data( G_OBJECT( item ), "nautilus-actions-tokens" ));
902 na_tokens_execute_action( tokens, profile );
906 * create a root submenu
908 static GList *
909 create_root_menu( NautilusActions *plugin, GList *menu )
911 static const gchar *thisfn = "nautilus_actions_create_root_menu";
912 GList *nautilus_menu;
913 NautilusMenuItem *root_item;
914 gchar *icon;
916 g_debug( "%s: plugin=%p, menu=%p (%d items)",
917 thisfn, ( void * ) plugin, ( void * ) menu, g_list_length( menu ));
919 if( !menu || !g_list_length( menu )){
920 return( NULL );
923 icon = na_iabout_get_icon_name();
924 root_item = nautilus_menu_item_new( "NautilusActionsExtensions",
925 /* i18n: label of an automagic root submenu */
926 _( "Nautilus-Actions actions" ),
927 /* i18n: tooltip of an automagic root submenu */
928 _( "A submenu which embeds the currently available Nautilus-Actions actions and menus" ),
929 icon );
930 attach_submenu_to_item( root_item, menu );
931 nautilus_menu = g_list_append( NULL, root_item );
932 g_free( icon );
934 return( nautilus_menu );
938 * if there is a root submenu,
939 * then add the about item to the end of the first level of this menu
941 static GList *
942 add_about_item( NautilusActions *plugin, GList *menu )
944 static const gchar *thisfn = "nautilus_actions_add_about_item";
945 GList *nautilus_menu;
946 gboolean have_root_menu;
947 NautilusMenuItem *root_item;
948 NautilusMenuItem *about_item;
949 NautilusMenu *first;
950 gchar *icon;
952 g_debug( "%s: plugin=%p, menu=%p (%d items)",
953 thisfn, ( void * ) plugin, ( void * ) menu, g_list_length( menu ));
955 if( !menu || !g_list_length( menu )){
956 return( NULL );
959 have_root_menu = FALSE;
960 nautilus_menu = menu;
962 if( g_list_length( menu ) == 1 ){
963 root_item = NAUTILUS_MENU_ITEM( menu->data );
964 g_object_get( G_OBJECT( root_item ), "menu", &first, NULL );
965 if( first ){
966 g_return_val_if_fail( NAUTILUS_IS_MENU( first ), NULL );
967 have_root_menu = TRUE;
971 if( have_root_menu ){
972 icon = na_iabout_get_icon_name();
974 about_item = nautilus_menu_item_new( "AboutNautilusActions",
975 _( "About Nautilus-Actions" ),
976 _( "Display some informations about Nautilus-Actions" ),
977 icon );
979 g_signal_connect_data( about_item,
980 "activate",
981 G_CALLBACK( execute_about ),
982 plugin,
983 NULL,
984 0 );
986 nautilus_menu_append_item( first, about_item );
988 g_free( icon );
991 return( nautilus_menu );
994 static void
995 execute_about( NautilusMenuItem *item, NautilusActions *plugin )
997 g_return_if_fail( NAUTILUS_IS_ACTIONS( plugin ));
998 g_return_if_fail( NA_IS_IABOUT( plugin ));
1000 na_iabout_display( NA_IABOUT( plugin ));
1004 * Not only the items list itself, but also several runtime preferences have
1005 * an effect on the display of items in file manager context menu.
1007 * We of course monitor here all these informations; only asking NAPivot
1008 * for reloading its items when we detect the end of a burst of changes.
1010 * Only when NAPivot has finished with reloading its items list, then we
1011 * inform the file manager that its items list has changed.
1014 /* signal emitted by NAPivot at the end of a burst of 'item-changed' signals
1015 * from i/o providers
1017 static void
1018 on_pivot_items_changed_handler( NAPivot *pivot, NautilusActions *plugin )
1020 g_return_if_fail( NA_IS_PIVOT( pivot ));
1021 g_return_if_fail( NAUTILUS_IS_ACTIONS( plugin ));
1023 if( !plugin->private->dispose_has_run ){
1025 na_timeout_event( &plugin->private->change_timeout );
1029 /* callback triggered by NASettings at the end of a burst of 'changed' signals
1030 * on runtime preferences which may affect the way file manager displays
1031 * its context menus
1033 static void
1034 on_settings_key_changed_handler( const gchar *group, const gchar *key, gconstpointer new_value, gboolean mandatory, NautilusActions *plugin )
1036 g_return_if_fail( NAUTILUS_IS_ACTIONS( plugin ));
1038 if( !plugin->private->dispose_has_run ){
1040 na_timeout_event( &plugin->private->change_timeout );
1045 * automatically reloads the items, then signal the file manager.
1047 static void
1048 on_change_event_timeout( NautilusActions *plugin )
1050 static const gchar *thisfn = "nautilus_actions_on_change_event_timeout";
1051 g_debug( "%s: timeout expired", thisfn );
1053 na_pivot_load_items( plugin->private->pivot );
1054 nautilus_menu_provider_emit_items_updated_signal( NAUTILUS_MENU_PROVIDER( plugin ));