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