1 /*
2  * Nautilus-Actions
3  * A Nautilus extension which offers configurable context menu actions.
4  *
5  * Copyright (C) 2005 The GNOME Foundation
6  * Copyright (C) 2006-2008 Frederic Ruaudel and others (see AUTHORS)
7  * Copyright (C) 2009-2014 Pierre Wieser and others (see AUTHORS)
8  *
9  * Nautilus-Actions 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.
13  *
14  * Nautilus-Actions 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 GNU
17  * General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with Nautilus-Actions; see the file COPYING. If not, see
21  * <http://www.gnu.org/licenses/>.
22  *
23  * Authors:
24  *   Frederic Ruaudel <grumz@grumz.net>
25  *   Rodrigo Moya <rodrigo@gnome-db.org>
26  *   Pierre Wieser <pwieser@trychlos.org>
27  *   ... and many others (see AUTHORS)
28  */
29 
30 #ifdef HAVE_CONFIG_H
31 #include <config.h>
32 #endif
33 
34 #include <glib/gi18n.h>
35 
36 #include <core/na-io-provider.h>
37 #include <core/na-iprefs.h>
38 
39 #include "nact-application.h"
40 #include "nact-main-statusbar.h"
41 #include "nact-main-toolbar.h"
42 #include "nact-main-tab.h"
43 #include "nact-menubar-priv.h"
44 #include "nact-sort-buttons.h"
45 #include "nact-tree-view.h"
46 
47 /* private class data
48  */
49 struct _NactMenubarClassPrivate {
50 	void *empty;						/* so that gcc -pedantic is happy */
51 };
52 
53 /* private instance data is externalized in nact-menubar-priv.h
54  * in order to be avaible to all nact-menubar-derived files.
55  */
56 
57 static const GtkActionEntry entries[] = {
58 
59 		{ "FileMenu",          NULL, N_( "_File" ) },
60 		{ "EditMenu",          NULL, N_( "_Edit" ) },
61 		{ "ViewMenu",          NULL, N_( "_View" ) },
62 		{ "ViewToolbarMenu",   NULL, N_( "_Toolbars" ) },
63 		{ "ToolsMenu",         NULL, N_( "_Tools" ) },
64 		{ "MaintainerMenu",    NULL, N_( "_Maintainer" ) },
65 		{ "HelpMenu",          NULL, N_( "_Help" ) },
66 		{ "NotebookLabelMenu", NULL, N_( "Notebook _tabs" ) },
67 
68 		{ "NewMenuItem", NULL, N_( "New _menu" ), NULL,
69 				/* i18n: tooltip displayed in the status bar when selecting the 'New menu' item */
70 				N_( "Insert a new menu at the current position" ),
71 				G_CALLBACK( nact_menubar_file_on_new_menu ) },
72 		{ "NewActionItem", GTK_STOCK_NEW, N_( "_New action" ), NULL,
73 				/* i18n: tooltip displayed in the status bar when selecting the 'New action' item */
74 				N_( "Define a new action" ),
75 				G_CALLBACK( nact_menubar_file_on_new_action ) },
76 		{ "NewProfileItem", NULL, N_( "New _profile" ), NULL,
77 				/* i18n: tooltip displayed in the status bar when selecting the 'New profile' item */
78 				N_( "Define a new profile attached to the current action" ),
79 				G_CALLBACK( nact_menubar_file_on_new_profile ) },
80 		{ "SaveItem", GTK_STOCK_SAVE, NULL, NULL,
81 				/* i18n: tooltip displayed in the status bar when selecting 'Save' item */
82 				N_( "Record all the modified actions. Invalid actions will be silently ignored" ),
83 				G_CALLBACK( nact_menubar_file_on_save ) },
84 		{ "QuitItem", GTK_STOCK_QUIT, NULL, NULL,
85 				/* i18n: tooltip displayed in the status bar when selecting 'Quit' item */
86 				N_( "Quit the application" ),
87 				G_CALLBACK( nact_menubar_file_on_quit ) },
88 		{ "CutItem" , GTK_STOCK_CUT, NULL, NULL,
89 				/* i18n: tooltip displayed in the status bar when selecting the Cut item */
90 				N_( "Cut the selected item(s) to the clipboard" ),
91 				G_CALLBACK( nact_menubar_edit_on_cut ) },
92 		{ "CopyItem" , GTK_STOCK_COPY, NULL, NULL,
93 				/* i18n: tooltip displayed in the status bar when selecting the Copy item */
94 				N_( "Copy the selected item(s) to the clipboard" ),
95 				G_CALLBACK( nact_menubar_edit_on_copy ) },
96 		{ "PasteItem" , GTK_STOCK_PASTE, NULL, NULL,
97 				/* i18n: tooltip displayed in the status bar when selecting the Paste item */
98 				N_( "Insert the content of the clipboard just before the current position" ),
99 				G_CALLBACK( nact_menubar_edit_on_paste ) },
100 		{ "PasteIntoItem" , NULL, N_( "Paste _into" ), "<Shift><Ctrl>V",
101 				/* i18n: tooltip displayed in the status bar when selecting the Paste Into item */
102 				N_( "Insert the content of the clipboard as first child of the current item" ),
103 				G_CALLBACK( nact_menubar_edit_on_paste_into ) },
104 		{ "DuplicateItem" , NULL, N_( "D_uplicate" ), "",
105 				/* i18n: tooltip displayed in the status bar when selecting the Duplicate item */
106 				N_( "Duplicate the selected item(s)" ),
107 				G_CALLBACK( nact_menubar_edit_on_duplicate ) },
108 		{ "DeleteItem", GTK_STOCK_DELETE, NULL, "Delete",
109 				/* i18n: tooltip displayed in the status bar when selecting the Delete item */
110 				N_( "Delete the selected item(s)" ),
111 				G_CALLBACK( nact_menubar_edit_on_delete ) },
112 		{ "ReloadActionsItem", GTK_STOCK_REFRESH, N_( "_Reload the items" ), "F5",
113 				/* i18n: tooltip displayed in the status bar when selecting the 'Reload' item */
114 				N_( "Cancel your current modifications and reload the initial list of menus and actions" ),
115 				G_CALLBACK( nact_menubar_edit_on_reload ) },
116 		{ "PreferencesItem", GTK_STOCK_PREFERENCES, NULL, NULL,
117 				/* i18n: tooltip displayed in the status bar when selecting the 'Preferences' item */
118 				N_( "Edit your preferences" ),
119 				G_CALLBACK( nact_menubar_edit_on_prefererences ) },
120 		{ "ExpandAllItem" , NULL, N_( "_Expand all" ), NULL,
121 				/* i18n: tooltip displayed in the status bar when selecting the Expand all item */
122 				N_( "Entirely expand the items hierarchy" ),
123 				G_CALLBACK( nact_menubar_view_on_expand_all ) },
124 		{ "CollapseAllItem" , NULL, N_( "_Collapse all" ), NULL,
125 				/* i18n: tooltip displayed in the status bar when selecting the Collapse all item */
126 				N_( "Entirely collapse the items hierarchy" ),
127 				G_CALLBACK( nact_menubar_view_on_collapse_all ) },
128 
129 		{ "ImportItem" , GTK_STOCK_CONVERT, N_( "_Import assistant..." ), "",
130 				/* i18n: tooltip displayed in the status bar when selecting the Import item */
131 				N_( "Import one or more actions from external files into your configuration" ),
132 				G_CALLBACK( nact_menubar_tools_on_import ) },
133 		{ "ExportItem", NULL, N_( "E_xport assistant..." ), NULL,
134 				/* i18n: tooltip displayed in the status bar when selecting the Export item */
135 				N_( "Export one or more actions from your configuration to external files" ),
136 				G_CALLBACK( nact_menubar_tools_on_export ) },
137 
138 		{ "DumpSelectionItem", NULL, N_( "_Dump the selection" ), NULL,
139 				/* i18n: tooltip displayed in the status bar when selecting the Dump selection item */
140 				N_( "Recursively dump selected items" ),
141 				G_CALLBACK( nact_menubar_maintainer_on_dump_selection ) },
142 		{ "BriefTreeStoreDumpItem", NULL, N_( "_Brief tree store dump" ), NULL,
143 				/* i18n: tooltip displayed in the status bar when selecting the BriefTreeStoreDump item */
144 				N_( "Briefly dump the tree store" ),
145 				G_CALLBACK( nact_menubar_maintainer_on_brief_tree_store_dump ) },
146 		{ "ListModifiedItems", NULL, N_( "_List modified items" ), NULL,
147 				/* i18n: tooltip displayed in the status bar when selecting the ListModifiedItems item */
148 				N_( "List the modified items" ),
149 				G_CALLBACK( nact_menubar_maintainer_on_list_modified_items ) },
150 		{ "DumpClipboard", NULL, N_( "_Dump the clipboard" ), NULL,
151 				/* i18n: tooltip displayed in the status bar when selecting the DumpClipboard item */
152 				N_( "Dump the content of the clipboard object" ),
153 				G_CALLBACK( nact_menubar_maintainer_on_dump_clipboard ) },
154 		{ "FunctionTest", NULL, "_Test a function", NULL,
155 				"Test a function (see nact-menubar-maintainer.c",
156 				G_CALLBACK( nact_menubar_maintainer_on_test_function ) },
157 
158 		{ "HelpItem" , GTK_STOCK_HELP, N_( "Contents" ), "F1",
159 				/* i18n: tooltip displayed in the status bar when selecting the Help item */
160 				N_( "Display help about this program" ),
161 				G_CALLBACK( nact_menubar_help_on_help ) },
162 		{ "AboutItem", GTK_STOCK_ABOUT, NULL, NULL,
163 				/* i18n: tooltip displayed in the status bar when selecting the About item */
164 				N_( "Display informations about this program" ),
165 				G_CALLBACK( nact_menubar_help_on_about ) },
166 };
167 
168 static const GtkToggleActionEntry toolbar_entries[] = {
169 
170 		{ "ViewFileToolbarItem", NULL, N_( "_File" ), NULL,
171 				/* i18n: tooltip displayed in the status bar when selecting the 'View File toolbar' item */
172 				N_( "Display the File toolbar" ),
173 				G_CALLBACK( nact_menubar_view_on_toolbar_file ), FALSE },
174 		{ "ViewEditToolbarItem", NULL, N_( "_Edit" ), NULL,
175 				/* i18n: tooltip displayed in the status bar when selecting the 'View Edit toolbar' item */
176 				N_( "Display the Edit toolbar" ),
177 				G_CALLBACK( nact_menubar_view_on_toolbar_edit ), FALSE },
178 		{ "ViewToolsToolbarItem", NULL, N_( "_Tools" ), NULL,
179 				/* i18n: tooltip displayed in the status bar when selecting 'View Tools toolbar' item */
180 				N_( "Display the Tools toolbar" ),
181 				G_CALLBACK( nact_menubar_view_on_toolbar_tools ), FALSE },
182 		{ "ViewHelpToolbarItem", NULL, N_( "_Help" ), NULL,
183 				/* i18n: tooltip displayed in the status bar when selecting 'View Help toolbar' item */
184 				N_( "Display the Help toolbar" ),
185 				G_CALLBACK( nact_menubar_view_on_toolbar_help ), FALSE },
186 };
187 
188 static const GtkRadioActionEntry tabs_pos_entries[] = {
189 
190 		{ "TabsPosLeftItem", NULL, N_( "On the _left" ), NULL,
191 				/* i18n: tooltip displayed in the status bar when selecting the 'Set tabs position on the left' item */
192 				N_( "Display the notebook tabs on the left side" ),
193 				GTK_POS_LEFT },
194 		{ "TabsPosRightItem", NULL, N_( "On the _right" ), NULL,
195 				/* i18n: tooltip displayed in the status bar when selecting the 'Set tabs position on the right' item */
196 				N_( "Display the notebook tabs on the right side" ),
197 				GTK_POS_RIGHT },
198 		{ "TabsPosTopItem", NULL, N_( "On the _top" ), NULL,
199 				/* i18n: tooltip displayed in the status bar when selecting 'Set tabs position on the top' item */
200 				N_( "Display the notebook tabs on the top side" ),
201 				GTK_POS_TOP },
202 		{ "TabsPosBottomItem", NULL, N_( "On the _bottom" ), NULL,
203 				/* i18n: tooltip displayed in the status bar when selecting 'Set tabs position on the bottom' item */
204 				N_( "Display the notebook tabs on the bottom side" ),
205 				GTK_POS_BOTTOM },
206 };
207 
208 #define MENUBAR_PROP_STATUS_CONTEXT			"menubar-status-context"
209 #define MENUBAR_PROP_MAIN_STATUS_CONTEXT	"menubar-main-status-context"
210 
211 /* signals
212  */
213 enum {
214 	UPDATE_SENSITIVITIES,
215 	LAST_SIGNAL
216 };
217 
218 static const gchar  *st_ui_menubar_actions     = PKGUIDIR "/nautilus-actions-config-tool.actions";
219 static const gchar  *st_ui_maintainer_actions  = PKGUIDIR "/nautilus-actions-maintainer.actions";
220 
221 static gint          st_signals[ LAST_SIGNAL ] = { 0 };
222 static GObjectClass *st_parent_class           = NULL;
223 
224 static GType    register_type( void );
225 static void     class_init( NactMenubarClass *klass );
226 static void     instance_init( GTypeInstance *instance, gpointer klass );
227 static void     instance_dispose( GObject *application );
228 static void     instance_finalize( GObject *application );
229 
230 static void     on_base_initialize_window( BaseWindow *window, gpointer user_data );
231 static void     on_ui_manager_proxy_connect( GtkUIManager *ui_manager, GtkAction *action, GtkWidget *proxy, BaseWindow *window );
232 static void     on_menu_item_selected( GtkMenuItem *proxy, BaseWindow *window );
233 static void     on_menu_item_deselected( GtkMenuItem *proxy, BaseWindow *window );
234 
235 static void     on_open_context_menu( BaseWindow *window, GdkEventButton *event, const gchar *popup, gpointer user_data );
236 static void     on_popup_selection_done( GtkMenuShell *menushell, BaseWindow *window );
237 static void     on_tree_view_count_changed( BaseWindow *window, gboolean reset, gint menus, gint actions, gint profiles );
238 static void     on_tree_view_focus_in( BaseWindow *window, gpointer user_data );
239 static void     on_tree_view_focus_out( BaseWindow *window, gpointer user_data );
240 static void     on_tree_view_modified_status_changed( BaseWindow *window, gboolean is_modified, gpointer user_data );
241 static void     on_tree_view_selection_changed( BaseWindow *window, GList *selected, gpointer user_data );
242 
243 static void     on_update_sensitivities( NactMenubar *bar, BaseWindow *window );
244 
245 GType
nact_menubar_get_type(void)246 nact_menubar_get_type( void )
247 {
248 	static GType type = 0;
249 
250 	if( !type ){
251 		type = register_type();
252 	}
253 
254 	return( type );
255 }
256 
257 static GType
register_type(void)258 register_type( void )
259 {
260 	static const gchar *thisfn = "nact_menubar_register_type";
261 	GType type;
262 
263 	static GTypeInfo info = {
264 		sizeof( NactMenubarClass ),
265 		( GBaseInitFunc ) NULL,
266 		( GBaseFinalizeFunc ) NULL,
267 		( GClassInitFunc ) class_init,
268 		NULL,
269 		NULL,
270 		sizeof( NactMenubar ),
271 		0,
272 		( GInstanceInitFunc ) instance_init
273 	};
274 
275 	g_debug( "%s", thisfn );
276 
277 	type = g_type_register_static( G_TYPE_OBJECT, "NactMenubar", &info, 0 );
278 
279 	return( type );
280 }
281 
282 static void
class_init(NactMenubarClass * klass)283 class_init( NactMenubarClass *klass )
284 {
285 	static const gchar *thisfn = "nact_menubar_class_init";
286 	GObjectClass *object_class;
287 
288 	g_debug( "%s: klass=%p", thisfn, ( void * ) klass );
289 
290 	st_parent_class = g_type_class_peek_parent( klass );
291 
292 	object_class = G_OBJECT_CLASS( klass );
293 	object_class->dispose = instance_dispose;
294 	object_class->finalize = instance_finalize;
295 
296 	/**
297 	 * NactMenubar::menubar-signal-update-sensitivities
298 	 *
299 	 * This signal is emitted by the NactMenubar object on itself when
300 	 * menu items sensitivities have to be refreshed.
301 	 *
302 	 * Signal arg.: None
303 	 *
304 	 * Handler prototype:
305 	 * void ( *handler )( NactMenubar *bar, gpointer user_data );
306 	 */
307 	st_signals[ UPDATE_SENSITIVITIES ] = g_signal_new(
308 			MENUBAR_SIGNAL_UPDATE_SENSITIVITIES,
309 			NACT_TYPE_MENUBAR,
310 			G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
311 			0,					/* no default handler */
312 			NULL,
313 			NULL,
314 			g_cclosure_marshal_VOID__VOID,
315 			G_TYPE_NONE,
316 			0 );
317 
318 	klass->private = g_new0( NactMenubarClassPrivate, 1 );
319 }
320 
321 static void
instance_init(GTypeInstance * instance,gpointer klass)322 instance_init( GTypeInstance *instance, gpointer klass )
323 {
324 	static const gchar *thisfn = "nact_menubar_instance_init";
325 	NactMenubar *self;
326 
327 	g_return_if_fail( NACT_IS_MENUBAR( instance ));
328 
329 	g_debug( "%s: instance=%p (%s), klass=%p",
330 			thisfn, ( void * ) instance, G_OBJECT_TYPE_NAME( instance ), ( void * ) klass );
331 
332 	self = NACT_MENUBAR( instance );
333 
334 	self->private = g_new0( NactMenubarPrivate, 1 );
335 
336 	self->private->dispose_has_run = FALSE;
337 }
338 
339 static void
instance_dispose(GObject * object)340 instance_dispose( GObject *object )
341 {
342 	static const gchar *thisfn = "nact_menubar_instance_dispose";
343 	NactMenubarPrivate *priv;
344 
345 	g_return_if_fail( NACT_IS_MENUBAR( object ));
346 
347 	priv = NACT_MENUBAR( object )->private;
348 
349 	if( !priv->dispose_has_run ){
350 
351 		g_debug( "%s: object=%p (%s)",
352 				thisfn,
353 				( void * ) object, G_OBJECT_TYPE_NAME( object ));
354 
355 		priv->dispose_has_run = TRUE;
356 
357 		base_window_signal_disconnect(
358 				priv->window,
359 				priv->update_sensitivities_handler_id );
360 
361 		g_object_unref( priv->action_group );
362 		g_object_unref( priv->notebook_group );
363 		g_object_unref( priv->ui_manager );
364 		g_object_unref( priv->sort_buttons );
365 
366 		if( priv->selected_items ){
367 			g_list_free( priv->selected_items );
368 		}
369 
370 		/* chain up to the parent class */
371 		if( G_OBJECT_CLASS( st_parent_class )->dispose ){
372 			G_OBJECT_CLASS( st_parent_class )->dispose( object );
373 		}
374 	}
375 }
376 
377 static void
instance_finalize(GObject * instance)378 instance_finalize( GObject *instance )
379 {
380 	static const gchar *thisfn = "nact_menubar_instance_finalize";
381 	NactMenubar *self;
382 
383 	g_return_if_fail( NACT_IS_MENUBAR( instance ));
384 
385 	g_debug( "%s: instance=%p (%s)", thisfn, ( void * ) instance, G_OBJECT_TYPE_NAME( instance ));
386 
387 	self = NACT_MENUBAR( instance );
388 
389 	g_free( self->private );
390 
391 	/* chain call to parent class */
392 	if( G_OBJECT_CLASS( st_parent_class )->finalize ){
393 		G_OBJECT_CLASS( st_parent_class )->finalize( instance );
394 	}
395 }
396 
397 /**
398  * nact_menubar_new:
399  * @window: the window which embeds the menubar, usually the #NactMainWindow.
400  *
401  * The created menubar attachs itself to the @window; it also connect a weak
402  * reference to this same @window, thus automatically g_object_unref() -ing
403  * itself at @window finalization time.
404  *
405  * The menubar also takes advantage of #BaseWindow messages to initialize
406  * its Gtk widgets.
407  *
408  * Returns: a new #NactMenubar object.
409  */
410 NactMenubar *
nact_menubar_new(BaseWindow * window)411 nact_menubar_new( BaseWindow *window )
412 {
413 	NactMenubar *bar;
414 
415 	g_return_val_if_fail( BASE_IS_WINDOW( window ), NULL );
416 
417 	bar = g_object_new( NACT_TYPE_MENUBAR, NULL );
418 
419 	bar->private->window = window;
420 	bar->private->sort_buttons = nact_sort_buttons_new( window );
421 
422 	base_window_signal_connect(
423 			window,
424 			G_OBJECT( window ),
425 			BASE_SIGNAL_INITIALIZE_WINDOW,
426 			G_CALLBACK( on_base_initialize_window ));
427 
428 	g_object_set_data( G_OBJECT( window ), WINDOW_DATA_MENUBAR, bar );
429 
430 	return( bar );
431 }
432 
433 static void
on_base_initialize_window(BaseWindow * window,gpointer user_data)434 on_base_initialize_window( BaseWindow *window, gpointer user_data )
435 {
436 	static const gchar *thisfn = "nact_menubar_on_base_initialize_window";
437 	GError *error;
438 	guint merge_id;
439 	GtkAccelGroup *accel_group;
440 	GtkWidget *menubar, *vbox;
441 	GtkWindow *toplevel;
442 	gboolean has_maintainer_menu;
443 	NactApplication *application;
444 	guint tabs_pos;
445 
446 	BAR_WINDOW_VOID( window );
447 
448 	if( !bar->private->dispose_has_run ){
449 
450 		g_debug( "%s: window=%p (%s), user_data=%p",
451 				thisfn,
452 				( void * ) window, G_OBJECT_TYPE_NAME( window ),
453 				( void * ) user_data );
454 
455 		/* create the menubar:
456 		 * - create action group, and insert list of actions in it
457 		 * - create the ui manager, and insert action group in it
458 		 * - merge inserted actions group with ui xml definition
459 		 * - install accelerators in the main window
460 		 * - pack menu bar in the main window
461 		 *
462 		 * "disconnect-proxy" signal is never triggered.
463 		 */
464 		bar->private->ui_manager = gtk_ui_manager_new();
465 		g_debug( "%s: ui_manager=%p", thisfn, ( void * ) bar->private->ui_manager );
466 
467 		base_window_signal_connect(
468 				window,
469 				G_OBJECT( bar->private->ui_manager ),
470 				"connect-proxy",
471 				G_CALLBACK( on_ui_manager_proxy_connect ));
472 
473 		tabs_pos = na_iprefs_get_tabs_pos( NULL );
474 		bar->private->notebook_group = gtk_action_group_new( "NotebookActions" );
475 		g_debug( "%s: notebook_group=%p", thisfn, ( void * ) bar->private->notebook_group );
476 		gtk_action_group_set_translation_domain( bar->private-> notebook_group, GETTEXT_PACKAGE );
477 		gtk_action_group_add_radio_actions( bar->private->notebook_group, tabs_pos_entries, G_N_ELEMENTS( tabs_pos_entries ), tabs_pos, G_CALLBACK( nact_menubar_view_on_tabs_pos_changed ), window );
478 		gtk_ui_manager_insert_action_group( bar->private->ui_manager, bar->private->notebook_group, 0 );
479 
480 		bar->private->action_group = gtk_action_group_new( "MenubarActions" );
481 		g_debug( "%s: action_group=%p", thisfn, ( void * ) bar->private->action_group );
482 		gtk_action_group_set_translation_domain( bar->private->action_group, GETTEXT_PACKAGE );
483 		gtk_action_group_add_actions( bar->private->action_group, entries, G_N_ELEMENTS( entries ), window );
484 		gtk_action_group_add_toggle_actions( bar->private->action_group, toolbar_entries, G_N_ELEMENTS( toolbar_entries ), window );
485 		gtk_ui_manager_insert_action_group( bar->private->ui_manager, bar->private->action_group, 0 );
486 
487 		error = NULL;
488 		merge_id = gtk_ui_manager_add_ui_from_file( bar->private->ui_manager, st_ui_menubar_actions, &error );
489 		if( merge_id == 0 || error ){
490 			g_warning( "%s: error=%s", thisfn, error->message );
491 			g_error_free( error );
492 		}
493 
494 		has_maintainer_menu = FALSE;
495 #ifdef NA_MAINTAINER_MODE
496 		has_maintainer_menu = TRUE;
497 #endif
498 		if( has_maintainer_menu ){
499 			error = NULL;
500 			merge_id = gtk_ui_manager_add_ui_from_file( bar->private->ui_manager, st_ui_maintainer_actions, &error );
501 			if( merge_id == 0 || error ){
502 				g_warning( "%s: error=%s", thisfn, error->message );
503 				g_error_free( error );
504 			}
505 		}
506 
507 		toplevel = base_window_get_gtk_toplevel( window );
508 		accel_group = gtk_ui_manager_get_accel_group( bar->private->ui_manager );
509 		gtk_window_add_accel_group( toplevel, accel_group );
510 
511 		menubar = gtk_ui_manager_get_widget( bar->private->ui_manager, "/ui/MainMenubar" );
512 		vbox = base_window_get_widget( window, "MenubarVBox" );
513 		gtk_box_pack_start( GTK_BOX( vbox ), menubar, FALSE, FALSE, 0 );
514 
515 		/* this creates a submenu in the toolbar */
516 		/*gtk_container_add( GTK_CONTAINER( vbox ), toolbar );*/
517 
518 		/* initialize the private data
519 		 */
520 		application = NACT_APPLICATION( base_window_get_application( bar->private->window ));
521 		bar->private->updater = nact_application_get_updater( application );
522 		bar->private->is_level_zero_writable = na_updater_is_level_zero_writable( bar->private->updater );
523 		bar->private->has_writable_providers =
524 				( na_io_provider_find_writable_io_provider( NA_PIVOT( bar->private->updater )) != NULL );
525 
526 		g_debug( "%s: na_updater_is_level_zero_writable=%s, na_io_provider_find_writable_io_provider=%s",
527 				thisfn,
528 				bar->private->is_level_zero_writable ? "True":"False",
529 				bar->private->has_writable_providers ? "True":"False" );
530 
531 		/* connect to all signal which may have an influence on the menu
532 		 * items sensitivity
533 		 */
534 		base_window_signal_connect(
535 				window,
536 				G_OBJECT( window ),
537 				MAIN_SIGNAL_CONTEXT_MENU,
538 				G_CALLBACK( on_open_context_menu ));
539 
540 		base_window_signal_connect(
541 				window,
542 				G_OBJECT( window ),
543 				TREE_SIGNAL_COUNT_CHANGED,
544 				G_CALLBACK( on_tree_view_count_changed ));
545 
546 		base_window_signal_connect(
547 				window,
548 				G_OBJECT( window ),
549 				TREE_SIGNAL_FOCUS_IN,
550 				G_CALLBACK( on_tree_view_focus_in ));
551 
552 		base_window_signal_connect(
553 				window,
554 				G_OBJECT( window ),
555 				TREE_SIGNAL_FOCUS_OUT,
556 				G_CALLBACK( on_tree_view_focus_out ));
557 
558 		base_window_signal_connect(
559 				window,
560 				G_OBJECT( window ),
561 				TREE_SIGNAL_MODIFIED_STATUS_CHANGED,
562 				G_CALLBACK( on_tree_view_modified_status_changed ));
563 
564 		base_window_signal_connect(
565 				window,
566 				G_OBJECT( window ),
567 				MAIN_SIGNAL_SELECTION_CHANGED,
568 				G_CALLBACK( on_tree_view_selection_changed ));
569 
570 		bar->private->update_sensitivities_handler_id =
571 				base_window_signal_connect(
572 						window,
573 						G_OBJECT( bar ),
574 						MENUBAR_SIGNAL_UPDATE_SENSITIVITIES,
575 						G_CALLBACK( on_update_sensitivities ));
576 
577 		nact_menubar_file_initialize( bar );
578 		nact_main_toolbar_init( window, bar->private->action_group );
579 	}
580 }
581 
582 /*
583  * action: GtkAction, GtkToggleAction
584  * proxy:  GtkImageMenuItem, GtkCheckMenuItem, GtkToolButton
585  */
586 static void
on_ui_manager_proxy_connect(GtkUIManager * ui_manager,GtkAction * action,GtkWidget * proxy,BaseWindow * window)587 on_ui_manager_proxy_connect( GtkUIManager *ui_manager, GtkAction *action, GtkWidget *proxy, BaseWindow *window )
588 {
589 	static const gchar *thisfn = "nact_menubar_on_ui_manager_proxy_connect";
590 
591 	g_debug( "%s: ui_manager=%p (%s), action=%p (%s), proxy=%p (%s), window=%p (%s)",
592 			thisfn,
593 			( void * ) ui_manager, G_OBJECT_TYPE_NAME( ui_manager ),
594 			( void * ) action, G_OBJECT_TYPE_NAME( action ),
595 			( void * ) proxy, G_OBJECT_TYPE_NAME( proxy ),
596 			( void * ) window, G_OBJECT_TYPE_NAME( window ));
597 
598 	if( GTK_IS_MENU_ITEM( proxy )){
599 
600 		base_window_signal_connect(
601 				window,
602 				G_OBJECT( proxy ),
603 				"select",
604 				G_CALLBACK( on_menu_item_selected ));
605 
606 		base_window_signal_connect(
607 				window,
608 				G_OBJECT( proxy ),
609 				"deselect",
610 				G_CALLBACK( on_menu_item_deselected ));
611 	}
612 }
613 
614 /*
615  * gtk_activatable_get_related_action() and gtk_action_get_tooltip()
616  * are only available starting with Gtk 2.16
617  */
618 static void
on_menu_item_selected(GtkMenuItem * proxy,BaseWindow * window)619 on_menu_item_selected( GtkMenuItem *proxy, BaseWindow *window )
620 {
621 	GtkAction *action;
622 	const gchar *tooltip;
623 
624 	/*g_debug( "nact_menubar_on_menu_item_selected: proxy=%p (%s), window=%p (%s)",
625 			( void * ) proxy, G_OBJECT_TYPE_NAME( proxy ),
626 			( void * ) window, G_OBJECT_TYPE_NAME( window ));*/
627 
628 	tooltip = NULL;
629 	action = gtk_activatable_get_related_action( GTK_ACTIVATABLE( proxy ));
630 
631 	if( action ){
632 		tooltip = gtk_action_get_tooltip( action );
633 
634 		if( tooltip ){
635 			nact_main_statusbar_display_status( NACT_MAIN_WINDOW( window ), MENUBAR_PROP_STATUS_CONTEXT, tooltip );
636 		}
637 	}
638 }
639 
640 static void
on_menu_item_deselected(GtkMenuItem * proxy,BaseWindow * window)641 on_menu_item_deselected( GtkMenuItem *proxy, BaseWindow *window )
642 {
643 	nact_main_statusbar_hide_status( NACT_MAIN_WINDOW( window ), MENUBAR_PROP_STATUS_CONTEXT );
644 }
645 
646 /*
647  * Opens a popup menu.
648  */
649 static void
on_open_context_menu(BaseWindow * window,GdkEventButton * event,const gchar * popup,gpointer user_data)650 on_open_context_menu( BaseWindow *window, GdkEventButton *event, const gchar *popup, gpointer user_data )
651 {
652 	static const gchar *thisfn = "nact_menubar_on_open_context_menu";
653 	GtkWidget *menu;
654 
655 	BAR_WINDOW_VOID( window );
656 
657 	menu = gtk_ui_manager_get_widget( bar->private->ui_manager, popup );
658 	if( menu ){
659 		bar->private->popup_handler =
660 				g_signal_connect(
661 						menu,
662 						"selection-done",
663 						G_CALLBACK( on_popup_selection_done ),
664 						window );
665 		if( event ){
666 			gtk_menu_popup( GTK_MENU( menu ), NULL, NULL, NULL, NULL, event->button, event->time );
667 		} else {
668 			gtk_menu_popup( GTK_MENU( menu ), NULL, NULL, NULL, NULL, 0, gtk_get_current_event_time ());
669 		}
670 	} else {
671 		g_warning( "%s: menu not found: %s", thisfn, popup );
672 	}
673 }
674 
675 static void
on_popup_selection_done(GtkMenuShell * menushell,BaseWindow * window)676 on_popup_selection_done(GtkMenuShell *menushell, BaseWindow *window )
677 {
678 	static const gchar *thisfn = "nact_menubar_on_popup_selection_done";
679 
680 	BAR_WINDOW_VOID( window );
681 
682 	g_debug( "%s", thisfn );
683 
684 	g_signal_handler_disconnect( menushell, bar->private->popup_handler );
685 	bar->private->popup_handler = ( gulong ) 0;
686 }
687 
688 /*
689  * when the tree view is refilled, update our internal counters so
690  * that we are knowing if we have some exportables
691  */
692 static void
on_tree_view_count_changed(BaseWindow * window,gboolean reset,gint menus,gint actions,gint profiles)693 on_tree_view_count_changed( BaseWindow *window, gboolean reset, gint menus, gint actions, gint profiles )
694 {
695 	static const gchar *thisfn = "nact_menubar_on_tree_view_count_changed";
696 	gchar *status;
697 
698 	BAR_WINDOW_VOID( window );
699 
700 	g_debug( "%s: window=%p, reset=%s, menus=%d, actions=%d, profiles=%d",
701 			thisfn, ( void * ) window, reset ? "True":"False", menus, actions, profiles );
702 
703 	if( reset ){
704 		bar->private->count_menus = menus;
705 		bar->private->count_actions = actions;
706 		bar->private->count_profiles = profiles;
707 
708 	} else {
709 		bar->private->count_menus += menus;
710 		bar->private->count_actions += actions;
711 		bar->private->count_profiles += profiles;
712 	}
713 
714 	bar->private->have_exportables = ( bar->private->count_menus + bar->private->count_actions > 0 );
715 
716 	/* i18n: note the space at the beginning of the sentence */
717 	status = g_strdup_printf(
718 			_( " %d menu(s), %d action(s), %d profile(s) are currently loaded" ),
719 			bar->private->count_menus, bar->private->count_actions, bar->private->count_profiles );
720 	nact_main_statusbar_display_status( NACT_MAIN_WINDOW( window ), MENUBAR_PROP_MAIN_STATUS_CONTEXT, status );
721 	g_free( status );
722 
723 	g_signal_emit_by_name( bar, MENUBAR_SIGNAL_UPDATE_SENSITIVITIES );
724 }
725 
726 static void
on_tree_view_focus_in(BaseWindow * window,gpointer user_data)727 on_tree_view_focus_in( BaseWindow *window, gpointer user_data )
728 {
729 	BAR_WINDOW_VOID( window );
730 
731 	g_debug( "nact_menubar_on_tree_view_focus_in" );
732 
733 	bar->private->treeview_has_focus = TRUE;
734 	g_signal_emit_by_name( bar, MENUBAR_SIGNAL_UPDATE_SENSITIVITIES );
735 }
736 
737 static void
on_tree_view_focus_out(BaseWindow * window,gpointer user_data)738 on_tree_view_focus_out( BaseWindow *window, gpointer user_data )
739 {
740 	BAR_WINDOW_VOID( window );
741 
742 	g_debug( "nact_menubar_on_tree_view_focus_out" );
743 
744 	bar->private->treeview_has_focus = FALSE;
745 	g_signal_emit_by_name( bar, MENUBAR_SIGNAL_UPDATE_SENSITIVITIES );
746 }
747 
748 /*
749  * the count of modified NAObjectItem has changed
750  */
751 static void
on_tree_view_modified_status_changed(BaseWindow * window,gboolean is_modified,gpointer user_data)752 on_tree_view_modified_status_changed( BaseWindow *window, gboolean is_modified, gpointer user_data )
753 {
754 	static const gchar *thisfn = "nact_menubar_on_tree_view_modified_status_changed";
755 
756 	g_debug( "%s: window=%p, is_modified=%s, user_data=%p",
757 			thisfn, ( void * ) window, is_modified ? "True":"False", ( void * ) user_data );
758 
759 	BAR_WINDOW_VOID( window );
760 
761 	if( !bar->private->dispose_has_run ){
762 
763 		bar->private->is_tree_modified = is_modified;
764 		g_signal_emit_by_name( bar, MENUBAR_SIGNAL_UPDATE_SENSITIVITIES );
765 	}
766 }
767 
768 /*
769  * when the selection changes in the tree view, see what is selected
770  *
771  * It happens that this function is triggered after all tabs have already
772  * dealt with the MAIN_SIGNAL_SELECTION_CHANGED signal
773  *
774  * We are trying to precompute here all indicators which are needed to
775  * make actions sensitive. As a multiple selection may have multiple
776  * sort of indicators, we assure here that at least one item will be a
777  * valid candidate to the target action, the action taking care itself
778  * of applying to valid candidates, and rejecting the others.
779  */
780 static void
on_tree_view_selection_changed(BaseWindow * window,GList * selected,gpointer user_data)781 on_tree_view_selection_changed( BaseWindow *window, GList *selected, gpointer user_data )
782 {
783 	static const gchar *thisfn = "nact_menubar_on_tree_view_selection_changed";
784 	NAObject *first;
785 	NAObject *selected_action;
786 	NAObject *row, *parent;
787 	GList *is;
788 
789 	BAR_WINDOW_VOID( window );
790 
791 	g_debug( "%s: selected_items=%p (count=%d)", thisfn, ( void * ) selected, g_list_length( selected ));
792 
793 	/* count the items
794 	 */
795 	bar->private->count_selected = g_list_length( selected );
796 
797 	if( selected ){
798 		na_object_item_count_items( selected, &bar->private->selected_menus, &bar->private->selected_actions, &bar->private->selected_profiles, FALSE );
799 		g_debug( "%s: selected_menus=%d, selected_actions=%d, selected_profiles=%d",
800 				thisfn,
801 				bar->private->selected_menus, bar->private->selected_actions, bar->private->selected_profiles );
802 	}
803 
804 	/* take a ref of the list of selected items
805 	 */
806 	if( bar->private->selected_items ){
807 		g_list_free( bar->private->selected_items );
808 	}
809 	bar->private->selected_items = g_list_copy( selected );
810 
811 	/* check if the parent of the first selected item is writable
812 	 * (File: New menu/New action)
813 	 * (Edit: Paste menu or action)
814 	 */
815 	first = NULL;
816 	if( selected ){
817 		first = ( NAObject *) selected->data;
818 		if( NA_IS_OBJECT_PROFILE( first )){
819 			first = NA_OBJECT( na_object_get_parent( first ));
820 		}
821 		first = ( NAObject * ) na_object_get_parent( first );
822 	}
823 	if( first ){
824 		bar->private->is_parent_writable = na_object_is_finally_writable( first, NULL );
825 		g_debug( "%s: parent of first selected is not null: is_parent_writable=%s",
826 				thisfn, bar->private->is_parent_writable ? "True":"False" );
827 	} else {
828 		bar->private->is_parent_writable = bar->private->is_level_zero_writable;
829 		g_debug( "%s: first selected is at level zero: is_level_zero_writable=%s",
830 				thisfn, bar->private->is_level_zero_writable ? "True":"False" );
831 	}
832 
833 	/* check is only an action is selected, or only profile(s) of a same action
834 	 * (File: New profile)
835 	 * (Edit: Paste a profile)
836 	 */
837 	bar->private->enable_new_profile = TRUE;
838 	selected_action = NULL;
839 	for( is = selected ; is ; is = is->next ){
840 
841 		if( NA_IS_OBJECT_MENU( is->data )){
842 			bar->private->enable_new_profile = FALSE;
843 			break;
844 
845 		} else if( NA_IS_OBJECT_ACTION( is->data )){
846 			if( !selected_action ){
847 				selected_action = NA_OBJECT( is->data );
848 			} else {
849 				bar->private->enable_new_profile = FALSE;
850 				break;
851 			}
852 
853 		} else if( NA_IS_OBJECT_PROFILE( is->data )){
854 			first = NA_OBJECT( na_object_get_parent( is->data ));
855 			if( !selected_action ){
856 				selected_action = first;
857 			} else if( selected_action != first ){
858 				bar->private->enable_new_profile = FALSE;
859 				break;
860 			}
861 		}
862 	}
863 	if( selected_action ){
864 		bar->private->is_action_writable = na_object_is_finally_writable( selected_action, NULL );
865 	} else {
866 		bar->private->enable_new_profile = FALSE;
867 	}
868 
869 	/* check that selection is not empty and that each selected item is writable
870 	 * and that all parents are writable
871 	 * if some selection is at level zero, then it must be writable
872 	 * (Edit: Cut/Delete)
873 	 */
874 	if( selected ){
875 		bar->private->are_parents_writable = TRUE;
876 		bar->private->are_items_writable = TRUE;
877 		for( is = selected ; is ; is = is->next ){
878 			row = ( NAObject * ) is->data;
879 			if( NA_IS_OBJECT_PROFILE( row )){
880 				row = NA_OBJECT( na_object_get_parent( row ));
881 			}
882 			gchar *label = na_object_get_label( row );
883 			gboolean writable = na_object_is_finally_writable( row, NULL );
884 			g_debug( "%s: label=%s, writable=%s", thisfn, label, writable ? "True":"False" );
885 			g_free( label );
886 			bar->private->are_items_writable &= writable;
887 			parent = ( NAObject * ) na_object_get_parent( row );
888 			if( parent ){
889 				bar->private->are_parents_writable &= na_object_is_finally_writable( parent, NULL );
890 			} else {
891 				bar->private->are_parents_writable &= bar->private->is_level_zero_writable;
892 			}
893 		}
894 	}
895 
896 	g_signal_emit_by_name( bar, MENUBAR_SIGNAL_UPDATE_SENSITIVITIES );
897 }
898 
899 static void
on_update_sensitivities(NactMenubar * bar,BaseWindow * window)900 on_update_sensitivities( NactMenubar *bar, BaseWindow *window )
901 {
902 	static const gchar *thisfn = "nact_menubar_on_update_sensitivities";
903 
904 	g_debug( "%s: bar=%p, window=%p", thisfn, ( void * ) bar, ( void * ) window );
905 
906 	nact_menubar_file_on_update_sensitivities( bar );
907 	nact_menubar_edit_on_update_sensitivities( bar );
908 	nact_menubar_view_on_update_sensitivities( bar );
909 	nact_menubar_tools_on_update_sensitivities( bar );
910 	nact_menubar_maintainer_on_update_sensitivities( bar );
911 	nact_menubar_help_on_update_sensitivities( bar );
912 }
913 
914 /**
915  * nact_menubar_enable_item:
916  * @bar: this #NactMenubar instance.
917  * @name: the name of the item in a menu.
918  * @enabled: whether this item should be enabled or not.
919  *
920  * Enable/disable an item in an menu.
921  */
922 void
nact_menubar_enable_item(const NactMenubar * bar,const gchar * name,gboolean enabled)923 nact_menubar_enable_item( const NactMenubar *bar, const gchar *name, gboolean enabled )
924 {
925 	GtkAction *action;
926 
927 	if( !bar->private->dispose_has_run ){
928 
929 		action = gtk_action_group_get_action( bar->private->action_group, name );
930 		gtk_action_set_sensitive( action, enabled );
931 	}
932 }
933 
934 /**
935  * nact_menubar_save_items:
936  * @window: the #BaseWindow
937  */
938 void
nact_menubar_save_items(BaseWindow * window)939 nact_menubar_save_items( BaseWindow *window )
940 {
941 	nact_menubar_file_save_items( window );
942 }
943