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)
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"
56 struct _NautilusActionsClassPrivate
{
57 void *empty
; /* so that gcc -pedantic is happy */
60 /* private instance data
62 struct _NautilusActionsPrivate
{
63 gboolean dispose_has_run
;
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
);
111 nautilus_actions_get_type( void )
113 g_assert( st_actions_type
);
114 return( st_actions_type
);
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
,
129 sizeof( NautilusActions
),
131 ( GInstanceInitFunc
) instance_init
,
134 static const GInterfaceInfo menu_provider_iface_info
= {
135 ( GInterfaceInitFunc
) menu_provider_iface_init
,
140 static const GInterfaceInfo iabout_iface_info
= {
141 ( GInterfaceInitFunc
) iabout_iface_init
,
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
);
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 );
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
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
);
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
);
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
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
;
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" )));
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 :
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.
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
;
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
);
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
);
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
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
;
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
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
);
425 selected
= na_selected_info_get_list_from_list(( GList
* ) files
);
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
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
;
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
);
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
);
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
);
473 get_menus_items( NautilusActions
*plugin
, guint target
, GList
*selection
)
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..
523 expand_tokens( GList
*pivot_tree
, NATokens
*tokens
)
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
)
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 );
554 old
= na_object_get_tooltip( item
);
555 new = na_tokens_parse_for_display( tokens
, old
, TRUE
);
556 na_object_set_tooltip( item
, 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 );
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 );
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
);
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
);
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
)){
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
));
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 );
625 /* a NAObjectProfile is also a NAIContext
627 expand_tokens_context( NA_ICONTEXT( it
->data
), tokens
);
635 expand_tokens_context( NAIContext
*context
, NATokens
*tokens
)
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 );
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 );
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 );
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 );
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 !
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
;
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" );
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
)){
718 if( !na_icontext_is_candidate( NA_ICONTEXT( it
->data
), target
, files
)){
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
));
731 if( target
== ITEM_TARGET_TOOLBAR
){
732 menus_list
= g_list_concat( menus_list
, submenu
);
735 item
= create_item_from_menu( NA_OBJECT_MENU( it
->data
), submenu
, target
);
736 menus_list
= g_list_append( menus_list
, item
);
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
);
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
;
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
);
781 g_free( action_label
);
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
,
804 G_CALLBACK( execute_action
),
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
);
818 * called _after_ the NautilusMenuItem has been finalized
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 );*/
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
);
872 attach_submenu_to_item( NautilusMenuItem
*item
, GList
*subitems
)
874 NautilusMenu
*submenu
;
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
894 execute_action( NautilusMenuItem
*item
, NAObjectProfile
*profile
)
896 static const gchar
*thisfn
= "nautilus_actions_execute_action";
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
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
;
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
)){
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" ),
930 attach_submenu_to_item( root_item
, menu
);
931 nautilus_menu
= g_list_append( NULL
, root_item
);
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
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
;
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
)){
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
);
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" ),
979 g_signal_connect_data( about_item
,
981 G_CALLBACK( execute_about
),
986 nautilus_menu_append_item( first
, about_item
);
991 return( nautilus_menu
);
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
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
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.
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
));