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 #include <libintl.h>
36 
37 #include <api/na-core-utils.h>
38 #include <api/na-timeout.h>
39 
40 #include <core/na-io-provider.h>
41 #include <core/na-iprefs.h>
42 
43 #include "nact-application.h"
44 #include "nact-main-statusbar.h"
45 #include "nact-main-tab.h"
46 #include "nact-menubar-priv.h"
47 #include "nact-tree-ieditable.h"
48 
49 static NATimeout st_autosave_prefs_timeout = { 0 };
50 static guint     st_event_autosave         = 0;
51 
52 static gchar *st_save_error       = N_( "Save error" );
53 static gchar *st_save_warning     = N_( "Some items may not have been saved" );
54 static gchar *st_level_zero_write = N_( "Unable to rewrite the level-zero items list" );
55 static gchar *st_delete_error     = N_( "Some items have not been deleted" );
56 
57 static gboolean save_item( BaseWindow *window, NAUpdater *updater, NAObjectItem *item, GSList **messages );
58 static void     install_autosave( NactMenubar *bar );
59 static void     on_autosave_prefs_changed( const gchar *group, const gchar *key, gconstpointer new_value, gpointer user_data );
60 static void     on_autosave_prefs_timeout( NactMenubar *bar );
61 static gboolean autosave_callback( NactMenubar *bar );
62 static void     autosave_destroyed( NactMenubar *bar );
63 
64 /*
65  * nact_menubar_file_initialize:
66  * @bar: this #NactMenubar object.
67  */
68 void
nact_menubar_file_initialize(NactMenubar * bar)69 nact_menubar_file_initialize( NactMenubar *bar )
70 {
71 	install_autosave( bar );
72 }
73 
74 /**
75  * nact_menubar_file_on_update_sensitivities:
76  * @bar: this #NactMenubar object.
77  *
78  * Update sensitivity of items of the File menu.
79  */
80 void
nact_menubar_file_on_update_sensitivities(const NactMenubar * bar)81 nact_menubar_file_on_update_sensitivities( const NactMenubar *bar )
82 {
83 	static const gchar *thisfn = "nact_menubar_file_on_update_sensitivities";
84 	gboolean new_item_enabled;
85 
86 	/* new menu / new action
87 	 * new item will be inserted just before the beginning of selection
88 	 * parent of the first selected row must be writable
89 	 * we must have at least one writable provider
90 	 */
91 	new_item_enabled = bar->private->is_parent_writable && bar->private->has_writable_providers;
92 	g_debug( "%s: is_parent_writable=%s, has_writable_providers=%s, new_item_enabled=%s",
93 			thisfn,
94 			bar->private->is_parent_writable ? "True":"False",
95 			bar->private->has_writable_providers ? "True":"False",
96 			new_item_enabled ? "True":"False" );
97 	nact_menubar_enable_item( bar, "NewMenuItem", new_item_enabled );
98 	nact_menubar_enable_item( bar, "NewActionItem", new_item_enabled );
99 
100 	/* new profile enabled if selection is relative to only one writable action
101 	 * i.e. contains profile(s) of the same action, or only contains one action
102 	 * action must be writable
103 	 */
104 	nact_menubar_enable_item( bar, "NewProfileItem",
105 			bar->private->enable_new_profile && bar->private->is_action_writable );
106 
107 	/* save enabled if at least one item has been modified
108 	 * or level-zero has been resorted and is writable
109 	 */
110 	nact_menubar_enable_item( bar, "SaveItem", ( bar->private->is_tree_modified ));
111 
112 	/* quit always enabled */
113 }
114 
115 /**
116  * nact_menubar_file_on_new_menu:
117  * @gtk_action: the #GtkAction action.
118  * @window: the #BaseWindow main window.
119  *
120  * Triggers File / New menu item.
121  */
122 void
nact_menubar_file_on_new_menu(GtkAction * gtk_action,BaseWindow * window)123 nact_menubar_file_on_new_menu( GtkAction *gtk_action, BaseWindow *window )
124 {
125 	NAObjectMenu *menu;
126 	NactApplication *application;
127 	NAUpdater *updater;
128 	NactTreeView *items_view;
129 	GList *items;
130 
131 	g_return_if_fail( GTK_IS_ACTION( gtk_action ));
132 	g_return_if_fail( NACT_IS_MAIN_WINDOW( window ));
133 
134 	menu = na_object_menu_new_with_defaults();
135 	na_object_check_status( menu );
136 	application = NACT_APPLICATION( base_window_get_application( window ));
137 	updater = nact_application_get_updater( application );
138 	na_updater_check_item_writability_status( updater, NA_OBJECT_ITEM( menu ));
139 	items = g_list_prepend( NULL, menu );
140 	items_view = nact_main_window_get_items_view( NACT_MAIN_WINDOW( window ));
141 	nact_tree_ieditable_insert_items( NACT_TREE_IEDITABLE( items_view ), items, NULL );
142 	na_object_free_items( items );
143 }
144 
145 /**
146  * nact_menubar_file_on_new_action:
147  * @gtk_action: the #GtkAction action.
148  * @window: the #BaseWindow main window.
149  *
150  * Triggers File / New action item.
151  */
152 void
nact_menubar_file_on_new_action(GtkAction * gtk_action,BaseWindow * window)153 nact_menubar_file_on_new_action( GtkAction *gtk_action, BaseWindow *window )
154 {
155 	NAObjectAction *action;
156 	NactApplication *application;
157 	NAUpdater *updater;
158 	NactTreeView *items_view;
159 	GList *items;
160 
161 	g_return_if_fail( GTK_IS_ACTION( gtk_action ));
162 	g_return_if_fail( NACT_IS_MAIN_WINDOW( window ));
163 
164 	action = na_object_action_new_with_defaults();
165 	na_object_check_status( action );
166 	application = NACT_APPLICATION( base_window_get_application( window ));
167 	updater = nact_application_get_updater( application );
168 	na_updater_check_item_writability_status( updater, NA_OBJECT_ITEM( action ));
169 	items = g_list_prepend( NULL, action );
170 	items_view = nact_main_window_get_items_view( NACT_MAIN_WINDOW( window ));
171 	nact_tree_ieditable_insert_items( NACT_TREE_IEDITABLE( items_view ), items, NULL );
172 	na_object_free_items( items );
173 }
174 
175 /**
176  * nact_menubar_file_on_new_profile:
177  * @gtk_action: the #GtkAction action.
178  * @window: the #BaseWindow main window.
179  *
180  * Triggers File / New profile item.
181  */
182 void
nact_menubar_file_on_new_profile(GtkAction * gtk_action,BaseWindow * window)183 nact_menubar_file_on_new_profile( GtkAction *gtk_action, BaseWindow *window )
184 {
185 	NAObjectAction *action;
186 	NAObjectProfile *profile;
187 	NactTreeView *items_view;
188 	GList *items;
189 
190 	g_return_if_fail( GTK_IS_ACTION( gtk_action ));
191 	g_return_if_fail( NACT_IS_MAIN_WINDOW( window ));
192 
193 	g_object_get(
194 			G_OBJECT( window ),
195 			MAIN_PROP_ITEM, &action,
196 			NULL );
197 
198 	profile = na_object_profile_new_with_defaults();
199 	na_object_attach_profile( action, profile );
200 
201 	na_object_set_label( profile, _( "New profile" ));
202 	na_object_set_new_id( profile, action );
203 
204 	na_object_check_status( profile );
205 
206 	items = g_list_prepend( NULL, profile );
207 	items_view = nact_main_window_get_items_view( NACT_MAIN_WINDOW( window ));
208 	nact_tree_ieditable_insert_items( NACT_TREE_IEDITABLE( items_view ), items, NULL );
209 	na_object_free_items( items );
210 }
211 
212 /**
213  * nact_menubar_file_on_save:
214  * @gtk_action: the #GtkAction action.
215  * @window: the #BaseWindow main window.
216  *
217  * Triggers File /Save item.
218  *
219  * Saving is not only saving modified items, but also saving hierarchy
220  * (and order if alpha order is not set).
221  *
222  * This is the same function that nact_menubar_file_save_items(), just with
223  * different arguments.
224  */
225 void
nact_menubar_file_on_save(GtkAction * gtk_action,BaseWindow * window)226 nact_menubar_file_on_save( GtkAction *gtk_action, BaseWindow *window )
227 {
228 	static const gchar *thisfn = "nact_menubar_file_on_save";
229 
230 	g_debug( "%s: gtk_action=%p, window=%p", thisfn, ( void * ) gtk_action, ( void * ) window );
231 	g_return_if_fail( GTK_IS_ACTION( gtk_action ));
232 	g_return_if_fail( NACT_IS_MAIN_WINDOW( window ));
233 
234 	nact_menubar_file_save_items( window );
235 }
236 
237 /**
238  * nact_menubar_file_save_items:
239  * @gtk_action: the #GtkAction action.
240  * @window: the #BaseWindow main window.
241  *
242  * Save items.
243  * This is the same function that nact_menubar_file_on_save(), just
244  * with different arguments.
245  *
246  * Synopsis:
247  * - rewrite the level-zero items list
248  * - delete the items which are marked to be deleted
249  * - rewrite (i.e. delete/write) updated items
250  *
251  * The difficulty here is that some sort of pseudo-transactionnal process
252  * must be setup:
253  *
254  * - if the level-zero items list cannot be updated, then an error message
255  *   is displayed, and we abort the whole processus
256  *
257  * - if some items cannot be actually deleted, then an error message is
258  *   displayed, and the whole processus is aborted;
259  *   plus:
260  *   a/ items which have not been deleted must be restored (maybe marked
261  *      as deleted ?) -> so these items are modified
262  *   b/ the level-zero list must be updated with these restored items
263  *      and reset modified
264  *
265  * - idem if some items cannot be actually rewritten...
266  */
267 void
nact_menubar_file_save_items(BaseWindow * window)268 nact_menubar_file_save_items( BaseWindow *window )
269 {
270 	static const gchar *thisfn = "nact_menubar_file_save_items";
271 	NactTreeView *items_view;
272 	GList *items, *it;
273 	GList *new_pivot;
274 	NAObjectItem *duplicate;
275 	GSList *messages;
276 	gchar *msg;
277 
278 	BAR_WINDOW_VOID( window );
279 
280 	g_debug( "%s: window=%p", thisfn, ( void * ) window );
281 
282 	/* always write the level zero list of items as the first save phase
283 	 * and reset the corresponding modification flag
284 	 */
285 	items_view = nact_main_window_get_items_view( NACT_MAIN_WINDOW( window ));
286 	items = nact_tree_view_get_items( items_view );
287 	na_object_dump_tree( items );
288 	messages = NULL;
289 
290 	if( nact_tree_ieditable_is_level_zero_modified( NACT_TREE_IEDITABLE( items_view ))){
291 		if( !na_iprefs_write_level_zero( items, &messages )){
292 			if( g_slist_length( messages )){
293 				msg = na_core_utils_slist_join_at_end( messages, "\n" );
294 			} else {
295 				msg = g_strdup( gettext( st_level_zero_write ));
296 			}
297 			base_window_display_error_dlg( window, gettext( st_save_error ), msg );
298 			g_free( msg );
299 			na_core_utils_slist_free( messages );
300 			messages = NULL;
301 		}
302 
303 	} else {
304 		g_signal_emit_by_name( window, TREE_SIGNAL_LEVEL_ZERO_CHANGED, FALSE );
305 	}
306 
307 	/* remove deleted items
308 	 * so that new actions with same id do not risk to be deleted later
309 	 * not deleted items are reinserted in the tree
310 	 */
311 	if( !nact_tree_ieditable_remove_deleted( NACT_TREE_IEDITABLE( items_view ), &messages )){
312 		if( g_slist_length( messages )){
313 			msg = na_core_utils_slist_join_at_end( messages, "\n" );
314 		} else {
315 			msg = g_strdup( gettext( st_delete_error ));
316 		}
317 		base_window_display_error_dlg( window, gettext( st_save_error ), msg );
318 		g_free( msg );
319 		na_core_utils_slist_free( messages );
320 		messages = NULL;
321 
322 	} else {
323 		na_object_free_items( items );
324 		items = nact_tree_view_get_items( items_view );
325 	}
326 
327 	/* recursively save the modified items
328 	 * check is useless here if item was not modified, but not very costly;
329 	 * above all, it is less costly to check the status here, than to check
330 	 * recursively each and every modified item
331 	 */
332 	new_pivot = NULL;
333 
334 	for( it = items ; it ; it = it->next ){
335 		save_item( window, bar->private->updater, NA_OBJECT_ITEM( it->data ), &messages );
336 		duplicate = NA_OBJECT_ITEM( na_object_duplicate( it->data, DUPLICATE_REC ));
337 		na_object_reset_origin( it->data, duplicate );
338 		na_object_check_status( it->data );
339 		new_pivot = g_list_prepend( new_pivot, duplicate );
340 	}
341 
342 	if( g_slist_length( messages )){
343 		msg = na_core_utils_slist_join_at_end( messages, "\n" );
344 		base_window_display_error_dlg( window, gettext( st_save_warning ), msg );
345 		g_free( msg );
346 		na_core_utils_slist_free( messages );
347 		messages = NULL;
348 	}
349 
350 	na_pivot_set_new_items( NA_PIVOT( bar->private->updater ), g_list_reverse( new_pivot ));
351 	na_object_free_items( items );
352 	nact_main_window_block_reload( NACT_MAIN_WINDOW( window ));
353 	g_signal_emit_by_name( window, TREE_SIGNAL_MODIFIED_STATUS_CHANGED, FALSE );
354 }
355 
356 /*
357  * iterates here on each and every NAObjectItem row stored in the tree
358  */
359 static gboolean
save_item(BaseWindow * window,NAUpdater * updater,NAObjectItem * item,GSList ** messages)360 save_item( BaseWindow *window, NAUpdater *updater, NAObjectItem *item, GSList **messages )
361 {
362 	static const gchar *thisfn = "nact_menubar_file_save_item";
363 	gboolean ret;
364 	NAIOProvider *provider_before;
365 	NAIOProvider *provider_after;
366 	GList *subitems, *it;
367 	gchar *label;
368 	guint save_ret;
369 
370 	g_return_val_if_fail( NACT_IS_MAIN_WINDOW( window ), FALSE );
371 	g_return_val_if_fail( NA_IS_UPDATER( updater ), FALSE );
372 	g_return_val_if_fail( NA_IS_OBJECT_ITEM( item ), FALSE );
373 
374 	ret = TRUE;
375 
376 	if( NA_IS_OBJECT_MENU( item )){
377 		subitems = na_object_get_items( item );
378 		for( it = subitems ; it ; it = it->next ){
379 			ret &= save_item( window, updater, NA_OBJECT_ITEM( it->data ), messages );
380 		}
381 	}
382 
383 	provider_before = na_object_get_provider( item );
384 
385 	if( na_object_is_modified( item )){
386 		label = na_object_get_label( item );
387 		g_debug( "%s: saving %p (%s) '%s'", thisfn, ( void * ) item, G_OBJECT_TYPE_NAME( item ), label );
388 		g_free( label );
389 
390 		save_ret = na_updater_write_item( updater, item, messages );
391 		ret = ( save_ret == NA_IIO_PROVIDER_CODE_OK );
392 
393 		if( ret ){
394 			if( NA_IS_OBJECT_ACTION( item )){
395 				na_object_reset_last_allocated( item );
396 			}
397 
398 			provider_after = na_object_get_provider( item );
399 			if( provider_after != provider_before ){
400 				g_signal_emit_by_name( window, MAIN_SIGNAL_ITEM_UPDATED, item, MAIN_DATA_PROVIDER );
401 			}
402 
403 		} else {
404 			g_warning( "%s: unable to write item: save_ret=%d", thisfn, save_ret );
405 		}
406 	}
407 
408 	return( ret );
409 }
410 
411 /**
412  * nact_menubar_file_on_quit:
413  * @gtk_action: the #GtkAction action.
414  * @window: the #BaseWindow main window.
415  *
416  * Triggers the File / Quit item.
417  */
418 void
nact_menubar_file_on_quit(GtkAction * gtk_action,BaseWindow * window)419 nact_menubar_file_on_quit( GtkAction *gtk_action, BaseWindow *window )
420 {
421 	static const gchar *thisfn = "nact_menubar_file_on_quit";
422 
423 	g_debug( "%s: item=%p, window=%p", thisfn, ( void * ) gtk_action, ( void * ) window );
424 	g_return_if_fail( GTK_IS_ACTION( gtk_action ) || gtk_action == NULL );
425 
426 	nact_main_window_quit( NACT_MAIN_WINDOW( window ));
427 }
428 
429 /*
430  * nact_menubar_file_install_autosave:
431  * @bar: this #NactMenubar instance.
432  *
433  * Setup the autosave feature and initialize its monitoring.
434  */
435 static void
install_autosave(NactMenubar * bar)436 install_autosave( NactMenubar *bar )
437 {
438 	st_autosave_prefs_timeout.timeout = 100;
439 	st_autosave_prefs_timeout.handler = ( NATimeoutFunc ) on_autosave_prefs_timeout;
440 	st_autosave_prefs_timeout.user_data = bar;
441 
442 	na_settings_register_key_callback( NA_IPREFS_MAIN_SAVE_AUTO, G_CALLBACK( on_autosave_prefs_changed ), NULL );
443 	na_settings_register_key_callback( NA_IPREFS_MAIN_SAVE_PERIOD, G_CALLBACK( on_autosave_prefs_changed ), NULL );
444 
445 	on_autosave_prefs_timeout( bar );
446 }
447 
448 static void
on_autosave_prefs_changed(const gchar * group,const gchar * key,gconstpointer new_value,gpointer user_data)449 on_autosave_prefs_changed( const gchar *group, const gchar *key, gconstpointer new_value, gpointer user_data )
450 {
451 	na_timeout_event( &st_autosave_prefs_timeout );
452 }
453 
454 static void
on_autosave_prefs_timeout(NactMenubar * bar)455 on_autosave_prefs_timeout( NactMenubar *bar )
456 {
457 	static const gchar *thisfn = "nact_menubar_file_on_autosave_prefs_timeout";
458 	gboolean autosave_on;
459 	guint autosave_period;
460 
461 	g_return_if_fail( NACT_IS_MENUBAR( bar ));
462 
463 	autosave_on = na_settings_get_boolean( NA_IPREFS_MAIN_SAVE_AUTO, NULL, NULL );
464 	autosave_period = na_settings_get_uint( NA_IPREFS_MAIN_SAVE_PERIOD, NULL, NULL );
465 
466 	if( st_event_autosave ){
467 		if( !g_source_remove( st_event_autosave )){
468 			g_warning( "%s: unable to remove autosave event source", thisfn );
469 		}
470 		st_event_autosave = 0;
471 	}
472 
473 	if( autosave_on ){
474 		st_event_autosave = g_timeout_add_seconds_full(
475 				G_PRIORITY_DEFAULT,
476 				autosave_period * 60,
477 				( GSourceFunc ) autosave_callback,
478 				bar,
479 				( GDestroyNotify ) autosave_destroyed );
480 	}
481 }
482 
483 static gboolean
autosave_callback(NactMenubar * bar)484 autosave_callback( NactMenubar *bar )
485 {
486 	const gchar *context = "autosave-context";
487 	g_debug( "nact_menubar_file_autosave_callback" );
488 
489 	nact_main_statusbar_display_status( NACT_MAIN_WINDOW( bar->private->window ), context, _( "Automatically saving pending modifications..." ));
490 	nact_menubar_file_save_items( bar->private->window );
491 	nact_main_statusbar_hide_status( NACT_MAIN_WINDOW( bar->private->window ), context );
492 
493 	return( TRUE );
494 }
495 
496 static void
autosave_destroyed(NactMenubar * bar)497 autosave_destroyed( NactMenubar *bar )
498 {
499 	g_debug( "nact_menubar_file_autosave_destroyed" );
500 }
501