[nautilus/undo-manager: 1/3] Initial import of the patch
- From: Cosimo Cecchi <cosimoc src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [nautilus/undo-manager: 1/3] Initial import of the patch
- Date: Mon, 29 Nov 2010 09:22:32 +0000 (UTC)
commit 34201b4058f8eb8025591ab8c9ce28ae2233318f
Author: Cosimo Cecchi <cosimoc gnome org>
Date: Mon Nov 22 11:58:44 2010 +0100
Initial import of the patch
libnautilus-private/Makefile.am | 2 +
libnautilus-private/nautilus-file-operations.c | 133 ++-
libnautilus-private/nautilus-file-private.h | 2 +
libnautilus-private/nautilus-file.c | 54 +-
libnautilus-private/nautilus-undostack-manager.c | 1958 ++++++++++++++++++++++
libnautilus-private/nautilus-undostack-manager.h | 152 ++
src/file-manager/fm-actions.h | 2 +
src/file-manager/fm-directory-view.c | 164 ++
src/file-manager/nautilus-directory-view-ui.xml | 4 +
src/nautilus-shell-ui.xml | 2 +
src/nautilus-window-menus.c | 4 +
11 files changed, 2470 insertions(+), 7 deletions(-)
---
diff --git a/libnautilus-private/Makefile.am b/libnautilus-private/Makefile.am
index 7db9176..6401073 100644
--- a/libnautilus-private/Makefile.am
+++ b/libnautilus-private/Makefile.am
@@ -189,6 +189,8 @@ libnautilus_private_la_SOURCES = \
nautilus-window-info.h \
nautilus-window-slot-info.c \
nautilus-window-slot-info.h \
+ nautilus-undostack-manager.c \
+ nautilus-undostack-manager.h \
$(NULL)
nodist_libnautilus_private_la_SOURCES =\
diff --git a/libnautilus-private/nautilus-file-operations.c b/libnautilus-private/nautilus-file-operations.c
index e351fdf..885f012 100644
--- a/libnautilus-private/nautilus-file-operations.c
+++ b/libnautilus-private/nautilus-file-operations.c
@@ -64,6 +64,7 @@
#include "nautilus-trash-monitor.h"
#include "nautilus-file-utilities.h"
#include "nautilus-file-conflict-dialog.h"
+#include "nautilus-undostack-manager.h"
/* TODO: TESTING!!! */
@@ -77,6 +78,7 @@ typedef struct {
GCancellable *cancellable;
GHashTable *skip_files;
GHashTable *skip_readdir_error;
+ NautilusUndoStackActionData *undo_redo_data;
gboolean skip_all_error;
gboolean skip_all_conflict;
gboolean merge_all;
@@ -952,6 +954,10 @@ finalize_common (CommonJob *common)
if (common->skip_readdir_error) {
g_hash_table_destroy (common->skip_readdir_error);
}
+
+ nautilus_undo_stack_manager_add_action (nautilus_undo_stack_manager_get (),
+ common->undo_redo_data);
+
g_object_unref (common->progress);
g_object_unref (common->cancellable);
g_free (common);
@@ -1753,6 +1759,8 @@ trash_files (CommonJob *job, GList *files, int *files_skipped)
char *primary, *secondary, *details;
int response;
+ guint64 mtime;
+
if (job_aborted (job)) {
return;
}
@@ -1769,6 +1777,9 @@ trash_files (CommonJob *job, GList *files, int *files_skipped)
file = l->data;
error = NULL;
+
+ mtime = nautilus_undo_stack_manager_get_file_modification_time (file);
+
if (!g_file_trash (file, job->cancellable, &error)) {
if (job->skip_all_error) {
(*files_skipped)++;
@@ -1815,7 +1826,9 @@ trash_files (CommonJob *job, GList *files, int *files_skipped)
total_files--;
} else {
nautilus_file_changes_queue_file_removed (file);
-
+
+ nautilus_undo_stack_action_data_add_trashed_file (job->undo_redo_data, file, mtime);
+
files_trashed++;
report_trash_progress (job, files_trashed, total_files);
}
@@ -1959,6 +1972,16 @@ trash_or_delete_internal (GList *files,
inhibit_power_manager ((CommonJob *)job, _("Deleting Files"));
}
+ /* FIXME: Disabled, because of missing mechanism to restore a file from trash in a clean way
+ * see http://www.mail-archive.com/nautilus-list gnome org/msg04664.html
+ */
+ if (try_trash && !nautilus_undo_stack_manager_is_undo_redo (nautilus_undo_stack_manager_get ())) {
+ GFile* src_dir;
+ job->common.undo_redo_data = nautilus_undo_stack_action_data_new (NAUTILUS_UNDO_STACK_MOVE_TO_TRASH, g_list_length(files));
+ src_dir = g_file_get_parent (files->data);
+ nautilus_undo_stack_action_data_set_src_dir (job->common.undo_redo_data, src_dir);
+ }
+
g_io_scheduler_push_job (delete_job,
job,
NULL,
@@ -3351,6 +3374,9 @@ create_dest_dir (CommonJob *job,
return CREATE_DEST_DIR_FAILED;
}
nautilus_file_changes_queue_file_added (*dest);
+
+ nautilus_undo_stack_action_data_add_origin_target_pair (job->undo_redo_data, src, *dest);
+
return CREATE_DEST_DIR_SUCCESS;
}
@@ -3962,6 +3988,8 @@ copy_move_file (CopyMoveJob *copy_job,
unique_name_nr = 1;
+ /* TODO-UNDO: Here we should get the previous file name */
+
/* another file in the same directory might have handled the invalid
* filename condition for us
*/
@@ -4103,7 +4131,9 @@ copy_move_file (CopyMoveJob *copy_job,
dest,
FALSE);
}
-
+
+ nautilus_undo_stack_action_data_add_origin_target_pair (job->undo_redo_data, src, dest);
+
g_object_unref (dest);
return;
}
@@ -4523,6 +4553,15 @@ nautilus_file_operations_copy (GList *files,
inhibit_power_manager ((CommonJob *)job, _("Copying Files"));
+ if (!nautilus_undo_stack_manager_is_undo_redo (nautilus_undo_stack_manager_get ())) {
+ GFile* src_dir;
+ job->common.undo_redo_data = nautilus_undo_stack_action_data_new (NAUTILUS_UNDO_STACK_COPY, g_list_length(files));
+ src_dir = g_file_get_parent (files->data);
+ nautilus_undo_stack_action_data_set_src_dir (job->common.undo_redo_data, src_dir);
+ g_object_ref (target_dir);
+ nautilus_undo_stack_action_data_set_dest_dir (job->common.undo_redo_data, target_dir);
+ }
+
g_io_scheduler_push_job (copy_job,
job,
NULL, /* destroy notify */
@@ -4680,7 +4719,9 @@ move_file_prepare (CopyMoveJob *move_job,
} else {
nautilus_file_changes_queue_schedule_position_remove (dest);
}
-
+
+ nautilus_undo_stack_action_data_add_origin_target_pair (job->undo_redo_data, src, dest);
+
return;
}
@@ -5048,6 +5089,19 @@ nautilus_file_operations_move (GList *files,
inhibit_power_manager ((CommonJob *)job, _("Moving Files"));
+ if (!nautilus_undo_stack_manager_is_undo_redo (nautilus_undo_stack_manager_get ())) {
+ GFile* src_dir;
+ if (g_file_has_uri_scheme (g_list_first(files)->data, "trash")) {
+ job->common.undo_redo_data = nautilus_undo_stack_action_data_new (NAUTILUS_UNDO_STACK_RESTORE_FROM_TRASH, g_list_length(files));
+ } else {
+ job->common.undo_redo_data = nautilus_undo_stack_action_data_new (NAUTILUS_UNDO_STACK_MOVE, g_list_length(files));
+ }
+ src_dir = g_file_get_parent (files->data);
+ nautilus_undo_stack_action_data_set_src_dir (job->common.undo_redo_data, src_dir);
+ g_object_ref (target_dir);
+ nautilus_undo_stack_action_data_set_dest_dir (job->common.undo_redo_data, target_dir);
+ }
+
g_io_scheduler_push_job (move_job,
job,
NULL, /* destroy notify */
@@ -5141,6 +5195,9 @@ link_file (CopyMoveJob *job,
path,
common->cancellable,
&error)) {
+
+ nautilus_undo_stack_action_data_add_origin_target_pair (common->undo_redo_data, src, dest);
+
g_free (path);
if (debuting_files) {
g_hash_table_replace (debuting_files, g_object_ref (dest), GINT_TO_POINTER (TRUE));
@@ -5351,6 +5408,15 @@ nautilus_file_operations_link (GList *files,
}
job->debuting_files = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, NULL);
+ if (!nautilus_undo_stack_manager_is_undo_redo (nautilus_undo_stack_manager_get ())) {
+ GFile* src_dir;
+ job->common.undo_redo_data = nautilus_undo_stack_action_data_new (NAUTILUS_UNDO_STACK_CREATE_LINK, g_list_length(files));
+ src_dir = g_file_get_parent (files->data);
+ nautilus_undo_stack_action_data_set_src_dir (job->common.undo_redo_data, src_dir);
+ g_object_ref (target_dir);
+ nautilus_undo_stack_action_data_set_dest_dir (job->common.undo_redo_data, target_dir);
+ }
+
g_io_scheduler_push_job (link_job,
job,
NULL, /* destroy notify */
@@ -5382,6 +5448,15 @@ nautilus_file_operations_duplicate (GList *files,
}
job->debuting_files = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, NULL);
+ if (!nautilus_undo_stack_manager_is_undo_redo (nautilus_undo_stack_manager_get ())) {
+ GFile* src_dir;
+ job->common.undo_redo_data = nautilus_undo_stack_action_data_new (NAUTILUS_UNDO_STACK_DUPLICATE, g_list_length(files));
+ src_dir = g_file_get_parent (files->data);
+ nautilus_undo_stack_action_data_set_src_dir (job->common.undo_redo_data, src_dir);
+ g_object_ref (src_dir);
+ nautilus_undo_stack_action_data_set_dest_dir (job->common.undo_redo_data, src_dir);
+ }
+
g_io_scheduler_push_job (copy_job,
job,
NULL, /* destroy notify */
@@ -5451,6 +5526,9 @@ set_permissions_file (SetPermissionsJob *job,
if (!job_aborted (common) &&
g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_MODE)) {
current = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE);
+
+ nautilus_undo_stack_action_data_add_file_permissions (common->undo_redo_data, file, current);
+
current = (current & ~mask) | value;
g_file_set_attribute_uint32 (file, G_FILE_ATTRIBUTE_UNIX_MODE,
@@ -5533,7 +5611,14 @@ nautilus_file_set_permissions_recursive (const char *directory,
job->dir_mask = dir_mask;
job->done_callback = callback;
job->done_callback_data = callback_data;
-
+
+ if (!nautilus_undo_stack_manager_is_undo_redo (nautilus_undo_stack_manager_get ())) {
+ job->common.undo_redo_data = nautilus_undo_stack_action_data_new (NAUTILUS_UNDO_STACK_RECURSIVE_SET_PERMISSIONS, 1);
+ g_object_ref (job->file);
+ nautilus_undo_stack_action_data_set_dest_dir (job->common.undo_redo_data, job->file);
+ nautilus_undo_stack_action_data_set_recursive_permissions (job->common.undo_redo_data, file_permissions, file_mask, dir_permissions, dir_mask);
+ }
+
g_io_scheduler_push_job (set_permissions_job,
job,
NULL,
@@ -5624,6 +5709,7 @@ nautilus_file_operations_copy_move (const GList *item_uris,
if (target_dir == NULL ||
(src_dir != NULL &&
g_file_equal (src_dir, dest))) {
+
nautilus_file_operations_duplicate (locations,
relative_item_points,
parent_window,
@@ -5646,11 +5732,13 @@ nautilus_file_operations_copy_move (const GList *item_uris,
cb_data = g_slice_new0 (MoveTrashCBData);
cb_data->real_callback = done_callback;
cb_data->real_data = done_callback_data;
+
nautilus_file_operations_trash_or_delete (locations,
parent_window,
(NautilusDeleteCallback) callback_for_move_to_trash,
cb_data);
} else {
+
nautilus_file_operations_move (locations,
relative_item_points,
dest,
@@ -5658,6 +5746,7 @@ nautilus_file_operations_copy_move (const GList *item_uris,
done_callback, done_callback_data);
}
} else {
+
nautilus_file_operations_link (locations,
relative_item_points,
dest,
@@ -5778,6 +5867,13 @@ create_job (GIOSchedulerJob *io_job,
res = g_file_make_directory (dest,
common->cancellable,
&error);
+
+ if (res) {
+ nautilus_undo_stack_action_data_set_create_data (common->undo_redo_data,
+ dest,
+ NULL);
+ }
+
} else {
if (job->src) {
res = g_file_copy (job->src,
@@ -5786,6 +5882,13 @@ create_job (GIOSchedulerJob *io_job,
common->cancellable,
NULL, NULL,
&error);
+
+ if (res) {
+ nautilus_undo_stack_action_data_set_create_data (common->undo_redo_data,
+ dest,
+ g_file_get_uri (job->src));
+ }
+
} else {
data = "";
length = 0;
@@ -5808,6 +5911,12 @@ create_job (GIOSchedulerJob *io_job,
res = g_output_stream_close (G_OUTPUT_STREAM (out),
common->cancellable,
&error);
+
+ if (res) {
+ nautilus_undo_stack_action_data_set_create_data (common->undo_redo_data,
+ dest,
+ g_strdup (data));
+ }
}
/* This will close if the write failed and we didn't close */
@@ -5972,6 +6081,10 @@ nautilus_file_operations_new_folder (GtkWidget *parent_view,
job->has_position = TRUE;
}
+ if (!nautilus_undo_stack_manager_is_undo_redo (nautilus_undo_stack_manager_get ())) {
+ job->common.undo_redo_data = nautilus_undo_stack_action_data_new (NAUTILUS_UNDO_STACK_CREATE_FOLDER, 1);
+ }
+
g_io_scheduler_push_job (create_job,
job,
NULL, /* destroy notify */
@@ -6010,6 +6123,10 @@ nautilus_file_operations_new_file_from_template (GtkWidget *parent_view,
job->src = g_file_new_for_uri (template_uri);
}
+ if (!nautilus_undo_stack_manager_is_undo_redo (nautilus_undo_stack_manager_get ())) {
+ job->common.undo_redo_data = nautilus_undo_stack_action_data_new (NAUTILUS_UNDO_STACK_CREATE_FILE_FROM_TEMPLATE, 1);
+ }
+
g_io_scheduler_push_job (create_job,
job,
NULL, /* destroy notify */
@@ -6047,6 +6164,10 @@ nautilus_file_operations_new_file (GtkWidget *parent_view,
job->length = length;
job->filename = g_strdup (target_filename);
+ if (!nautilus_undo_stack_manager_is_undo_redo (nautilus_undo_stack_manager_get ())) {
+ job->common.undo_redo_data = nautilus_undo_stack_action_data_new (NAUTILUS_UNDO_STACK_CREATE_EMPTY_FILE, length);
+ }
+
g_io_scheduler_push_job (create_job,
job,
NULL, /* destroy notify */
@@ -6091,7 +6212,7 @@ delete_trash_file (CommonJob *job,
g_object_unref (enumerator);
}
}
-
+
if (!job_aborted (job) && del_file) {
g_file_delete (file, job->cancellable, NULL);
}
@@ -6109,6 +6230,8 @@ empty_trash_job_done (gpointer user_data)
if (job->done_callback) {
job->done_callback (job->done_callback_data);
}
+
+ nautilus_undo_stack_manager_trash_has_emptied (nautilus_undo_stack_manager_get ());
finalize_common ((CommonJob *)job);
return FALSE;
diff --git a/libnautilus-private/nautilus-file-private.h b/libnautilus-private/nautilus-file-private.h
index 8cc5795..e22f800 100644
--- a/libnautilus-private/nautilus-file-private.h
+++ b/libnautilus-private/nautilus-file-private.h
@@ -30,6 +30,7 @@
#include <libnautilus-private/nautilus-monitor.h>
#include <eel/eel-glib-extensions.h>
#include <eel/eel-string.h>
+#include <libnautilus-private/nautilus-undostack-manager.h>
#define NAUTILUS_FILE_LARGE_TOP_LEFT_TEXT_MAXIMUM_CHARACTERS_PER_LINE 80
#define NAUTILUS_FILE_LARGE_TOP_LEFT_TEXT_MAXIMUM_LINES 24
@@ -230,6 +231,7 @@ typedef struct {
gpointer data;
GDestroyNotify free_data;
+ NautilusUndoStackActionData *undo_redo_data;
} NautilusFileOperation;
NautilusFile *nautilus_file_new_from_info (NautilusDirectory *directory,
diff --git a/libnautilus-private/nautilus-file.c b/libnautilus-private/nautilus-file.c
index f431f92..959ceaf 100644
--- a/libnautilus-private/nautilus-file.c
+++ b/libnautilus-private/nautilus-file.c
@@ -1652,6 +1652,9 @@ nautilus_file_operation_free (NautilusFileOperation *op)
if (op->free_data) {
op->free_data (op->data);
}
+
+ nautilus_undo_stack_manager_add_action (nautilus_undo_stack_manager_get (), op->undo_redo_data);
+
g_free (op);
}
@@ -1756,6 +1759,10 @@ rename_callback (GObject *source_object,
res, &error);
if (new_file != NULL) {
+
+
+ nautilus_undo_stack_action_data_set_rename_information (op->undo_redo_data, G_FILE (source_object), new_file);
+
g_file_query_info_async (new_file,
NAUTILUS_FILE_DEFAULT_ATTRIBUTES,
0,
@@ -1926,10 +1933,14 @@ nautilus_file_rename (NautilusFile *file,
/* Set up a renaming operation. */
op = nautilus_file_operation_new (file, callback, callback_data);
op->is_rename = TRUE;
+ location = nautilus_file_get_location (file);
- /* Do the renaming. */
+ /* Tell the undo manager a rename is taking place */
+ if (!nautilus_undo_stack_manager_is_undo_redo (nautilus_undo_stack_manager_get ())) {
+ op->undo_redo_data = nautilus_undo_stack_action_data_new (NAUTILUS_UNDO_STACK_RENAME, 1);
+ }
- location = nautilus_file_get_location (file);
+ /* Do the renaming. */
g_file_set_display_name_async (location,
new_file_name,
G_PRIORITY_DEFAULT,
@@ -5044,9 +5055,21 @@ nautilus_file_set_permissions (NautilusFile *file,
return;
}
+ if (!nautilus_undo_stack_manager_is_undo_redo (nautilus_undo_stack_manager_get ())) {
+ NautilusUndoStackActionData *undo_redo_data;
+
+ undo_redo_data = nautilus_undo_stack_action_data_new (NAUTILUS_UNDO_STACK_SET_PERMISSIONS, 1);
+ nautilus_undo_stack_action_data_set_file_permissions (undo_redo_data,
+ nautilus_file_get_location (file),
+ file->details->permissions,
+ new_permissions);
+ nautilus_undo_stack_manager_add_action (nautilus_undo_stack_manager_get (), undo_redo_data);
+ }
+
info = g_file_info_new ();
g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE, new_permissions);
nautilus_file_set_attributes (file, info, callback, callback_data);
+
g_object_unref (info);
}
@@ -5346,6 +5369,20 @@ nautilus_file_set_owner (NautilusFile *file,
(* callback) (file, NULL, NULL, callback_data);
return;
}
+
+ if (!nautilus_undo_stack_manager_is_undo_redo (nautilus_undo_stack_manager_get ())) {
+ NautilusUndoStackActionData *undo_redo_data;
+ char* current_owner;
+
+ current_owner = nautilus_file_get_owner_as_string (file, FALSE);
+ undo_redo_data = nautilus_undo_stack_action_data_new (NAUTILUS_UNDO_STACK_CHANGE_OWNER, 1);
+ nautilus_undo_stack_action_data_set_owner_change_information (undo_redo_data,
+ nautilus_file_get_location (file),
+ current_owner,
+ user_name_or_id);
+ nautilus_undo_stack_manager_add_action (nautilus_undo_stack_manager_get (), undo_redo_data);
+ g_free(current_owner);
+ }
info = g_file_info_new ();
g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID, new_id);
@@ -5610,6 +5647,19 @@ nautilus_file_set_group (NautilusFile *file,
return;
}
+ if (!nautilus_undo_stack_manager_is_undo_redo (nautilus_undo_stack_manager_get ())) {
+ NautilusUndoStackActionData *undo_redo_data;
+ char *current_group;
+
+ current_group = nautilus_file_get_group_name (file);
+ undo_redo_data = nautilus_undo_stack_action_data_new (NAUTILUS_UNDO_STACK_CHANGE_GROUP, 1);
+ nautilus_undo_stack_action_data_set_group_change_information (undo_redo_data,
+ nautilus_file_get_location (file),
+ current_group,
+ group_name_or_id);
+ nautilus_undo_stack_manager_add_action (nautilus_undo_stack_manager_get (), undo_redo_data);
+ g_free (current_group);
+ }
info = g_file_info_new ();
g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_GID, new_id);
diff --git a/libnautilus-private/nautilus-undostack-manager.c b/libnautilus-private/nautilus-undostack-manager.c
new file mode 100644
index 0000000..41b36f1
--- /dev/null
+++ b/libnautilus-private/nautilus-undostack-manager.c
@@ -0,0 +1,1958 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* nautilus-undostack-manager.h - Manages undo/redo of file operations
+ *
+ * Copyright (C) 2007-2010 Amos Brocco
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Amos Brocco <amos brocco unifr ch>
+ */
+#include <config.h>
+
+#include "nautilus-undostack-manager.h"
+#include "nautilus-file-operations.h"
+#include "nautilus-file.h"
+#include <gio/gio.h>
+#include <glib/gprintf.h>
+#include <glib-object.h>
+#include <glib/gi18n.h>
+#include <locale.h>
+#include <gdk/gdk.h>
+#include <eel/eel-glib-extensions.h>
+
+/* Default depth of the undo/redo stack. */
+#define DEFAULT_UNDO_DEPTH 32
+
+struct _NautilusUndoStackActionData
+{
+ /* Common stuff */
+ NautilusUndoStackActionType type;
+ NautilusUndoStackManager *manager;
+ eel_boolean_bit is_valid : 1;
+ eel_boolean_bit locked : 1; /* True if the action is being undone/redone */
+ eel_boolean_bit freed : 1; /* True if the action must be freed after undo/redo */
+ guint count; /* Number of items */
+
+ /* Cached labels/descriptions */
+ char *undo_label;
+ char *undo_description;
+ char *redo_label;
+ char *redo_description;
+
+ /* Copy / Move stuff */
+ GFile *src_dir;
+ GFile *dest_dir;
+ GList *sources; /* Relative to src_dir */
+ GList *destinations; /* Relative to dest_dir */
+
+ /* Create new file/folder stuff/set permissions */
+ char *template;
+ GFile *target_file;
+
+ /* Rename stuff */
+ GFile *old_file;
+ GFile *new_file;
+
+ /* Trash stuff */
+ GHashTable *trashed;
+
+ /* Recursive change permissions stuff */
+ GHashTable *original_permissions;
+ guint32 dir_mask;
+ guint32 dir_permissions;
+ guint32 file_mask;
+ guint32 file_permissions;
+
+ /* Single file change permissions stuff */
+ guint32 current_permissions;
+ guint32 new_permissions;
+
+ /* Group */
+ char *original_group_name;
+ char *new_group_name;
+
+ /* Owner */
+ char *original_user_name;
+ char *new_user_name;
+
+};
+
+struct _NautilusUndoStackManagerPrivate
+{
+ GQueue *stack;
+
+ /* Used to protect access to stack (because of async file ops) */
+ GMutex *mutex;
+
+ guint undo_levels;
+ guint index;
+ eel_boolean_bit dispose_has_run : 1;
+ eel_boolean_bit undo_redo_flag : 1;
+ eel_boolean_bit confirm_delete : 1;
+};
+
+#define NAUTILUS_UNDO_STACK_MANAGER_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), NAUTILUS_TYPE_UNDO_STACK_MANAGER, NautilusUndoStackManagerPrivate))
+
+enum
+{
+ PROP_UNDOSTACK_MANAGER_0,
+ PROP_UNDO_LEVELS,
+ PROP_CONFIRM_DELETE
+};
+
+static void nautilus_undo_stack_manager_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec);
+static void nautilus_undo_stack_manager_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec);
+static void nautilus_undo_stack_manager_finalize (GObject * object);
+static void nautilus_undo_stack_manager_dispose (GObject * object);
+G_DEFINE_TYPE (NautilusUndoStackManager, nautilus_undo_stack_manager, G_TYPE_OBJECT)
+
+static void stack_clear_n_oldest (GQueue * stack, guint n);
+static void stack_fix_size (NautilusUndoStackManagerPrivate *priv);
+static gboolean can_undo (NautilusUndoStackManagerPrivate *priv);
+static gboolean can_redo (NautilusUndoStackManagerPrivate *priv);
+static void stack_push_action (NautilusUndoStackManagerPrivate *priv, NautilusUndoStackActionData *action);
+static NautilusUndoStackActionData * stack_scroll_left (NautilusUndoStackManagerPrivate *priv);
+static NautilusUndoStackActionData * stack_scroll_right (NautilusUndoStackManagerPrivate *priv);
+static NautilusUndoStackActionData * get_next_redo_action (NautilusUndoStackManagerPrivate *priv);
+static NautilusUndoStackActionData * get_next_undo_action (NautilusUndoStackManagerPrivate *priv);
+
+static char *get_undo_label (NautilusUndoStackActionData *action);
+static char *get_undo_description (NautilusUndoStackActionData *action);
+static char *get_redo_label (NautilusUndoStackActionData *action);
+static char *get_redo_description (NautilusUndoStackActionData *action);
+
+static void do_menu_update (NautilusUndoStackManager * manager);
+static void free_undo_stack_action_data (NautilusUndoStackActionData *data);
+static void undostack_dispose_all (GQueue * queue);
+static void undo_redo_done_transfer_callback (GHashTable * debuting_uris, gpointer data);
+static void undo_redo_op_callback (gpointer callback_data);
+static void undo_redo_done_rename_callback (NautilusFile * file, GFile * result_location, GError * error, gpointer callback_data);
+static void undo_redo_done_delete_callback (GHashTable * debuting_uris, gboolean user_cancel, gpointer callback_data);
+static void undo_redo_done_create_callback (GFile * new_file, gpointer callback_data);
+static void clear_redo_actions (NautilusUndoStackManagerPrivate *priv);
+static char *get_first_target_short_name (NautilusUndoStackActionData *action);
+static GList *construct_gfile_list (const GList * urilist, GFile * parent);
+static GList *construct_gfile_list_from_uri (char *uri);
+static GList *uri_list_to_gfile_list (GList * urilist);
+static GHashTable *retrieve_files_to_restore (GHashTable * trashed);
+
+static void
+nautilus_undo_stack_manager_class_init (NautilusUndoStackManagerClass * klass)
+{
+ GParamSpec *undo_levels;
+ GParamSpec *confirm_delete;
+ GObjectClass *g_object_class;
+
+ /* Add private structure */
+ g_type_class_add_private (klass, sizeof (NautilusUndoStackManagerPrivate));
+
+ /* Create properties */
+ undo_levels = g_param_spec_uint ("undo-levels",
+ "undo levels",
+ "Number of undo levels to be stored",
+ 1, UINT_MAX, DEFAULT_UNDO_DEPTH,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+
+ confirm_delete = g_param_spec_boolean ("confirm-delete",
+ "confirm delete",
+ "Always confirm file deletion",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT);
+
+ /* Set properties get/set methods */
+ g_object_class = G_OBJECT_CLASS (klass);
+
+ g_object_class->set_property = nautilus_undo_stack_manager_set_property;
+ g_object_class->get_property = nautilus_undo_stack_manager_get_property;
+
+ /* Install properties */
+ g_object_class_install_property (g_object_class, PROP_UNDO_LEVELS, undo_levels);
+
+ g_object_class_install_property (g_object_class, PROP_CONFIRM_DELETE, confirm_delete);
+
+ /* The UI menu needs to update its status */
+ g_signal_new ("request-menu-update",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
+ 0, NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER);
+
+ /* Hook deconstructors */
+ g_object_class->dispose = nautilus_undo_stack_manager_dispose;
+ g_object_class->finalize = nautilus_undo_stack_manager_finalize;
+}
+
+static void
+nautilus_undo_stack_manager_init (NautilusUndoStackManager * self)
+{
+ NautilusUndoStackManagerPrivate *priv;
+
+ priv = NAUTILUS_UNDO_STACK_MANAGER_GET_PRIVATE (self);
+
+ self->priv = priv;
+
+ /* Initialize private fields */
+ priv->stack = g_queue_new ();
+ priv->mutex = g_mutex_new ();
+ priv->index = 0;
+ priv->dispose_has_run = FALSE;
+ priv->undo_redo_flag = FALSE;
+ priv->confirm_delete = FALSE;
+}
+
+static void
+nautilus_undo_stack_manager_dispose (GObject * object)
+{
+ NautilusUndoStackManager *self;
+ NautilusUndoStackManagerPrivate *priv;
+
+ self = NAUTILUS_UNDO_STACK_MANAGER (object);
+ priv = self->priv;
+
+ if (priv->dispose_has_run)
+ return;
+
+ g_mutex_lock (priv->mutex);
+
+ /* Free each undoable action in the stack and the stack itself */
+ undostack_dispose_all (priv->stack);
+ g_queue_free (priv->stack);
+
+ g_mutex_unlock (priv->mutex);
+
+ g_mutex_free (priv->mutex);
+
+ priv->dispose_has_run = TRUE;
+
+ G_OBJECT_CLASS (nautilus_undo_stack_manager_parent_class)->dispose (object);
+}
+
+static void
+nautilus_undo_stack_manager_finalize (GObject * object)
+{
+ G_OBJECT_CLASS (nautilus_undo_stack_manager_parent_class)->finalize (object);
+}
+
+static void
+nautilus_undo_stack_manager_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec)
+{
+ NautilusUndoStackManager *manager;
+ NautilusUndoStackManagerPrivate *priv;
+ guint new_undo_levels;
+
+ g_return_if_fail (NAUTILUS_IS_UNDO_STACK_MANAGER (object));
+
+ manager = NAUTILUS_UNDO_STACK_MANAGER (object);
+ priv = manager->priv;
+
+ switch (prop_id) {
+ case PROP_UNDO_LEVELS:
+ new_undo_levels = g_value_get_uint (value);
+ if (new_undo_levels > 0 && (priv->undo_levels != new_undo_levels)) {
+ priv->undo_levels = new_undo_levels;
+ g_mutex_lock (priv->mutex);
+ stack_fix_size (priv);
+ g_mutex_unlock (priv->mutex);
+ do_menu_update (manager);
+ }
+ break;
+ case PROP_CONFIRM_DELETE:
+ priv->confirm_delete = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+nautilus_undo_stack_manager_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec)
+{
+ NautilusUndoStackManager *manager;
+ NautilusUndoStackManagerPrivate *priv;
+
+ g_return_if_fail (NAUTILUS_IS_UNDO_STACK_MANAGER (object));
+
+ manager = NAUTILUS_UNDO_STACK_MANAGER (object);
+ priv = manager->priv;
+
+ switch (prop_id) {
+ case PROP_UNDO_LEVELS:
+ g_value_set_uint (value, priv->undo_levels);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+/**
+ * nautilus_undo_stack_manager_get:
+ *
+ * Gets the undo manager singleton.
+ *
+ * Returns: The #NautilusUndoStackManager singleton.
+ */
+NautilusUndoStackManager *
+nautilus_undo_stack_manager_get (void)
+{
+ static NautilusUndoStackManager *manager = NULL;
+
+ if (manager == NULL) {
+ manager = g_object_new (NAUTILUS_TYPE_UNDO_STACK_MANAGER, "undo-levels", DEFAULT_UNDO_DEPTH, NULL);
+ }
+
+ return manager;
+}
+
+/**
+ * nautilus_undo_stack_manager_is_undo_redo:
+ * @manager:
+ *
+ * Returns: %TRUE if undoing or redoing. TODO: Why does this clear the flag?
+ */
+gboolean
+nautilus_undo_stack_manager_is_undo_redo (NautilusUndoStackManager *manager)
+{
+ NautilusUndoStackManagerPrivate *priv;
+
+ priv = manager->priv;
+
+ if (priv->undo_redo_flag) {
+ priv->undo_redo_flag = FALSE;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * nautilus_undo_stack_manager_request_menu_update:
+ * @manager:
+ *
+ *
+ */
+void
+nautilus_undo_stack_manager_request_menu_update (NautilusUndoStackManager *manager)
+{
+ do_menu_update (manager);
+}
+
+/**
+ * nautilus_undo_stack_manager_redo:
+ * @manager:
+ * @cb:
+ * @user_data:
+ *
+ * Redoes the last file operation.
+ */
+void
+nautilus_undo_stack_manager_redo (NautilusUndoStackManager *manager,
+ NautilusUndoStackFinishCallback callback,
+ gpointer user_data)
+{
+ NautilusUndoStackManagerPrivate *priv;
+ NautilusUndoStackActionData *action;
+ NautilusFile *file;
+ GList *uris;
+ char *new_name;
+ char *parent_uri;
+
+ priv = manager->priv;
+
+ /* Update the menus invalidating undo/redo while an operation is already underway */
+ g_mutex_lock (priv->mutex);
+ action = stack_scroll_left (priv);
+ /* Action will be NULL if redo is not possible */
+ if (action != NULL) {
+ action->locked = TRUE;
+ }
+ g_mutex_unlock (priv->mutex);
+ do_menu_update (manager);
+
+ uris = NULL;
+ if (action != NULL) {
+ action->locked = TRUE; /* Remember to unlock when redo is finished */
+ priv->undo_redo_flag = TRUE;
+ switch (action->type) {
+ case NAUTILUS_UNDO_STACK_COPY:
+ uris = construct_gfile_list (action->sources, action->src_dir);
+ nautilus_file_operations_copy (uris, NULL, action->dest_dir, NULL, undo_redo_done_transfer_callback, action);
+ eel_g_object_list_free (uris);
+ break;
+ case NAUTILUS_UNDO_STACK_CREATE_FILE_FROM_TEMPLATE:
+ {
+ GFile *parent;
+ parent = g_file_get_parent (action->target_file);
+ parent_uri = g_file_get_uri (parent);
+ new_name = g_file_get_parse_name (action->target_file);
+ nautilus_file_operations_new_file_from_template (NULL,
+ NULL,
+ parent_uri,
+ new_name, action->template, undo_redo_done_create_callback, action);
+ g_free (parent_uri);
+ g_free (new_name);
+ g_object_unref (parent);
+ }
+ break;
+ case NAUTILUS_UNDO_STACK_DUPLICATE:
+ uris = construct_gfile_list (action->sources, action->src_dir);
+ nautilus_file_operations_duplicate (uris, NULL, NULL, undo_redo_done_transfer_callback, action);
+ eel_g_object_list_free (uris);
+ break;
+ case NAUTILUS_UNDO_STACK_RESTORE_FROM_TRASH:
+ case NAUTILUS_UNDO_STACK_MOVE:
+ uris = construct_gfile_list (action->sources, action->src_dir);
+ nautilus_file_operations_move (uris, NULL, action->dest_dir, NULL, undo_redo_done_transfer_callback, action);
+ eel_g_object_list_free (uris);
+ break;
+ case NAUTILUS_UNDO_STACK_RENAME:
+ new_name = g_file_get_basename (action->new_file);
+ file = nautilus_file_get (action->old_file);
+ nautilus_file_rename (file, new_name, undo_redo_done_rename_callback, action);
+ g_object_unref (file);
+ g_free (new_name);
+ break;
+ case NAUTILUS_UNDO_STACK_CREATE_EMPTY_FILE:
+ {
+ GFile *parent;
+ parent = g_file_get_parent (action->target_file);
+ parent_uri = g_file_get_uri (parent);
+ new_name = g_file_get_parse_name (action->target_file);
+ nautilus_file_operations_new_file (NULL, NULL, parent_uri,
+ new_name,
+ action->template,
+ action->count, undo_redo_done_create_callback, action);
+ g_free (parent_uri);
+ g_free (new_name);
+ }
+ break;
+ case NAUTILUS_UNDO_STACK_CREATE_FOLDER:
+ {
+ GFile *parent;
+ parent = g_file_get_parent (action->target_file);
+ parent_uri = g_file_get_uri (parent);
+ nautilus_file_operations_new_folder (NULL, NULL, parent_uri, undo_redo_done_create_callback, action);
+ g_free (parent_uri);
+ }
+ break;
+ case NAUTILUS_UNDO_STACK_MOVE_TO_TRASH:
+ if (g_hash_table_size (action->trashed) > 0) {
+ GList *uri_to_trash;
+ uri_to_trash = g_hash_table_get_keys (action->trashed);
+ uris = uri_list_to_gfile_list (uri_to_trash);
+ nautilus_file_operations_trash_or_delete (uris, NULL, undo_redo_done_delete_callback, action);
+ g_list_free (uri_to_trash);
+ eel_g_object_list_free (uris);
+ }
+ break;
+ case NAUTILUS_UNDO_STACK_CREATE_LINK:
+ uris = construct_gfile_list (action->sources, action->src_dir);
+ nautilus_file_operations_link (uris, NULL, action->dest_dir, NULL, undo_redo_done_transfer_callback, action);
+ eel_g_object_list_free (uris);
+ break;
+ case NAUTILUS_UNDO_STACK_SET_PERMISSIONS:
+ file = nautilus_file_get (action->target_file);
+ nautilus_file_set_permissions (file, action->new_permissions, undo_redo_done_rename_callback, action);
+ g_object_unref (file);
+ break;
+ case NAUTILUS_UNDO_STACK_RECURSIVE_SET_PERMISSIONS:
+ parent_uri = g_file_get_uri (action->dest_dir);
+ nautilus_file_set_permissions_recursive (parent_uri,
+ action->file_permissions,
+ action->file_mask,
+ action->dir_permissions,
+ action->dir_mask,
+ undo_redo_op_callback,
+ action);
+ g_free (parent_uri);
+ break;
+ case NAUTILUS_UNDO_STACK_CHANGE_GROUP:
+ file = nautilus_file_get (action->target_file);
+ nautilus_file_set_group (file,
+ action->new_group_name,
+ undo_redo_done_rename_callback,
+ action);
+ g_object_unref (file);
+ break;
+ case NAUTILUS_UNDO_STACK_CHANGE_OWNER:
+ file = nautilus_file_get (action->target_file);
+ nautilus_file_set_owner (file,
+ action->new_user_name,
+ undo_redo_done_rename_callback,
+ action);
+ g_object_unref (file);
+ break;
+ default:
+ priv->undo_redo_flag = FALSE;
+ break; /* We shouldn't be here */
+ }
+ }
+
+ if (callback != NULL) {
+ callback (user_data);
+ }
+}
+
+/**
+ * nautilus_undo_stack_manager_undo:
+ * @manager:
+ * @user_data:
+ * @callback:
+ *
+ * Undoes the operation on the top of the undo stack, if there is one.
+ * When the operation is finished, @callback will be called with
+ * @user_data.
+ */
+void
+nautilus_undo_stack_manager_undo (NautilusUndoStackManager *manager,
+ NautilusUndoStackFinishCallback callback,
+ gpointer user_data)
+{
+ NautilusUndoStackManagerPrivate *priv;
+ NautilusUndoStackActionData *action;
+ NautilusFile *file;
+ GList *uris;
+ GHashTable *files_to_restore;
+ char *new_name;
+
+ priv = manager->priv;
+
+ /* Update the menus invalidating undo/redo while an operation is already underway */
+ g_mutex_lock (priv->mutex);
+ action = stack_scroll_right (priv);
+ if (action != NULL) {
+ action->locked = TRUE;
+ }
+ g_mutex_unlock (priv->mutex);
+ do_menu_update (manager);
+
+ uris = NULL;
+ if (action != NULL) {
+ /* Note: Internal managed ops have to call nautilus_undo_stack_manager_is_undo_redo (manager);
+ * TODO: AW - this is really awkward. */
+ priv->undo_redo_flag = TRUE;
+ switch (action->type) {
+ case NAUTILUS_UNDO_STACK_CREATE_EMPTY_FILE:
+ case NAUTILUS_UNDO_STACK_CREATE_FILE_FROM_TEMPLATE:
+ case NAUTILUS_UNDO_STACK_CREATE_FOLDER:
+ uris = construct_gfile_list_from_uri (g_file_get_uri (action->target_file));
+ case NAUTILUS_UNDO_STACK_COPY:
+ case NAUTILUS_UNDO_STACK_DUPLICATE:
+ case NAUTILUS_UNDO_STACK_CREATE_LINK:
+ if (!uris) {
+ uris = construct_gfile_list (action->destinations, action->dest_dir);
+ uris = g_list_reverse (uris); /* Deleting must be done in reverse */
+ }
+ if (priv->confirm_delete) {
+ nautilus_file_operations_delete (uris, NULL, undo_redo_done_delete_callback, action);
+ eel_g_object_list_free (uris);
+ } else {
+ priv->undo_redo_flag = FALSE;
+ /* We skip the confirmation message */
+ GList *f;
+ for (f = uris; f != NULL; f = f->next) {
+ g_file_delete (f->data, NULL, NULL);
+ g_object_unref (f->data);
+ }
+ g_list_free (uris);
+ /* Here we must do what's necessary for the callback */
+ undo_redo_done_transfer_callback (NULL, action);
+ }
+ break;
+ case NAUTILUS_UNDO_STACK_RESTORE_FROM_TRASH:
+ uris = construct_gfile_list (action->destinations, action->dest_dir);
+ nautilus_file_operations_trash_or_delete (uris, NULL, undo_redo_done_delete_callback, action);
+ eel_g_object_list_free (uris);
+ break;
+ case NAUTILUS_UNDO_STACK_MOVE_TO_TRASH:
+ /* Internally managed op, clear the undo_redo_flag.
+ * Same as calling nautilus_undo_stack_manager_is_undo_redo()
+ * minus the function call and unused return val.
+ */
+ priv->undo_redo_flag = FALSE;
+ files_to_restore = retrieve_files_to_restore (action->trashed);
+ if (g_hash_table_size (files_to_restore) > 0) {
+ GList *gfiles_in_trash, *l;
+ GFile *item;
+ GFile *dest;
+ char *value;
+
+ gfiles_in_trash = g_hash_table_get_keys (files_to_restore);
+ for (l = gfiles_in_trash; l != NULL; l = l->next) {
+ item = l->data;
+ value = g_hash_table_lookup (files_to_restore, item);
+ dest = g_file_new_for_uri (value);
+ g_file_move (item, dest, G_FILE_COPY_NOFOLLOW_SYMLINKS, NULL, NULL, NULL, NULL);
+ g_object_unref (dest);
+ }
+
+ g_list_free (gfiles_in_trash);
+ }
+ g_hash_table_destroy (files_to_restore);
+ /* Here we must do what's necessary for the callback */
+ undo_redo_done_transfer_callback (NULL, action);
+ break;
+ case NAUTILUS_UNDO_STACK_MOVE:
+ uris = construct_gfile_list (action->destinations, action->dest_dir);
+ nautilus_file_operations_move (uris, NULL, action->src_dir, NULL, undo_redo_done_transfer_callback, action);
+ eel_g_object_list_free (uris);
+ break;
+ case NAUTILUS_UNDO_STACK_RENAME:
+ new_name = g_file_get_basename (action->old_file);
+ file = nautilus_file_get (action->new_file);
+ nautilus_file_rename (file, new_name, undo_redo_done_rename_callback, action);
+ nautilus_file_unref (file);
+ g_free (new_name);
+ break;
+ case NAUTILUS_UNDO_STACK_SET_PERMISSIONS:
+ file = nautilus_file_get (action->target_file);
+ nautilus_file_set_permissions (file,
+ action->current_permissions,
+ undo_redo_done_rename_callback, action);
+ nautilus_file_unref (file);
+ break;
+ case NAUTILUS_UNDO_STACK_RECURSIVE_SET_PERMISSIONS:
+ /* Internally managed op, clear the undo_redo_flag. */
+ priv->undo_redo_flag = FALSE;
+ if (g_hash_table_size (action->original_permissions) > 0) {
+ GList *gfiles_list;
+ guint32 *perm;
+ GList *l;
+ GFile *dest;
+ char *item;
+
+ gfiles_list = g_hash_table_get_keys (action->original_permissions);
+ for (l = gfiles_list; l != NULL; l = l->next) {
+ item = l->data;
+ perm = g_hash_table_lookup (action->original_permissions, item);
+ dest = g_file_new_for_uri (item);
+ g_file_set_attribute_uint32 (dest,
+ G_FILE_ATTRIBUTE_UNIX_MODE,
+ *perm, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, NULL);
+ g_object_unref (dest);
+ }
+
+ g_list_free (gfiles_list);
+ /* Here we must do what's necessary for the callback */
+ undo_redo_done_transfer_callback (NULL, action);
+ }
+ break;
+ case NAUTILUS_UNDO_STACK_CHANGE_GROUP:
+ file = nautilus_file_get (action->target_file);
+ nautilus_file_set_group (file,
+ action->original_group_name,
+ undo_redo_done_rename_callback, action);
+ nautilus_file_unref (file);
+ break;
+ case NAUTILUS_UNDO_STACK_CHANGE_OWNER:
+ file = nautilus_file_get (action->target_file);
+ nautilus_file_set_owner (file,
+ action->original_user_name,
+ undo_redo_done_rename_callback, action);
+ nautilus_file_unref (file);
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+ }
+
+ if (callback != NULL) {
+ callback (user_data);
+ }
+}
+
+/**
+ * nautilus_undo_stack_manager_add_action:
+ * @manager:
+ * @action:
+ *
+ * Pushes an undo action onto the top of the stack.
+ */
+void
+nautilus_undo_stack_manager_add_action (NautilusUndoStackManager *manager,
+ NautilusUndoStackActionData *action)
+{
+ NautilusUndoStackManagerPrivate *priv;
+
+ g_return_if_fail (manager != NULL);
+ g_return_if_fail (action != NULL);
+
+ priv = manager->priv;
+
+ if (!(action && action->is_valid)) {
+ free_undo_stack_action_data (action);
+ return;
+ }
+
+ action->manager = manager;
+
+ g_mutex_lock (priv->mutex);
+ stack_push_action (priv, action);
+ g_mutex_unlock (priv->mutex);
+
+ do_menu_update (manager);
+}
+
+/**
+ * nautilus_undo_stack_manager_trash_has_emptied:
+ * @manager:
+ *
+ * Callback after emptying the trash
+ */
+void
+nautilus_undo_stack_manager_trash_has_emptied (NautilusUndoStackManager *manager)
+{
+ NautilusUndoStackManagerPrivate *priv;
+ NautilusUndoStackActionData *action;
+ guint newest_move_to_trash_position;
+ guint i, length;
+
+ priv = manager->priv;
+
+ g_mutex_lock (priv->mutex);
+
+ /* Clear actions from the oldest to the newest move to trash */
+ clear_redo_actions (priv);
+
+ /* Search newest move to trash */
+ length = g_queue_get_length (priv->stack);
+ newest_move_to_trash_position = -1;
+ action = NULL;
+
+ for (i = 0; i < length; i++) {
+ action = (NautilusUndoStackActionData *)g_queue_peek_nth (priv->stack, i);
+ if (action->type == NAUTILUS_UNDO_STACK_MOVE_TO_TRASH) {
+ newest_move_to_trash_position = i;
+ break;
+ }
+ }
+
+ if (newest_move_to_trash_position >= 0) {
+ guint to_clear;
+ to_clear = length - newest_move_to_trash_position;
+ stack_clear_n_oldest (priv->stack, to_clear);
+ }
+
+ g_mutex_unlock (priv->mutex);
+}
+
+/**
+ * nautilus_undo_stack_manager_request_menu_update:
+ * @manager:
+ *
+ * Returns the modification time for the given file (used for undo trash)
+ */
+guint64
+nautilus_undo_stack_manager_get_file_modification_time (GFile * file)
+{
+ GFileInfo *info;
+ guint64 mtime;
+
+ /* TODO: Synch-I/O, Error checking. */
+ info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, FALSE, NULL);
+ if (info == NULL) {
+ return -1;
+ }
+
+ mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
+
+ g_object_unref (info);
+
+ return mtime;
+}
+
+/**
+ * nautilus_undo_stack_action_data_new:
+ * @manager:
+ * @items_count:
+ *
+ * Returns: a new undo data container.
+ */
+NautilusUndoStackActionData *
+nautilus_undo_stack_action_data_new (NautilusUndoStackActionType type,
+ gint items_count)
+{
+ NautilusUndoStackActionData *data;
+
+ data = g_slice_new0 (NautilusUndoStackActionData);
+ data->type = type;
+ data->count = items_count;
+
+ if (type == NAUTILUS_UNDO_STACK_MOVE_TO_TRASH) {
+ data->trashed = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+ } else if (type == NAUTILUS_UNDO_STACK_RECURSIVE_SET_PERMISSIONS) {
+ data->original_permissions = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+ }
+
+ return data;
+}
+
+/**
+ * nautilus_undo_stack_action_data_set_src_dir:
+ * @action_data:
+ * @src:
+ *
+ * Sets the source directory.
+ */
+void
+nautilus_undo_stack_action_data_set_src_dir (NautilusUndoStackActionData *action_data,
+ GFile *src)
+{
+ g_return_if_fail (action_data != NULL);
+
+ action_data->src_dir = src;
+}
+
+/**
+ * nautilus_undo_stack_action_data_set_dest_dir:
+ * @action_data:
+ * @dest:
+ *
+ * Sets the destination directory
+ */
+void
+nautilus_undo_stack_action_data_set_dest_dir (NautilusUndoStackActionData *action_data,
+ GFile *dest)
+{
+ g_return_if_fail (action_data != NULL);
+
+ action_data->dest_dir = dest;
+}
+
+/**
+ * nautilus_undo_stack_action_data_add_origin_pair_target:
+ * @action_data:
+ * @origin:
+ * @target:
+ *
+ * Pushes an origin, target pair in an existing undo data container
+ */
+void
+nautilus_undo_stack_action_data_add_origin_target_pair (NautilusUndoStackActionData *action_data,
+ GFile *origin,
+ GFile *target)
+{
+ char *src_relative;
+ char *dest_relative;
+
+ g_return_if_fail (action_data != NULL);
+
+ src_relative = g_file_get_relative_path (action_data->src_dir, origin);
+ action_data->sources = g_list_append (action_data->sources, src_relative);
+ dest_relative = g_file_get_relative_path (action_data->dest_dir, target);
+ action_data->destinations = g_list_append (action_data->destinations, dest_relative);
+
+ action_data->is_valid = TRUE;
+}
+
+/**
+ * nautilus_undo_stack_action_data_add_trashed_file:
+ * @action_data:
+ * @file:
+ * @mtime:
+ *
+ * Pushes an trashed file with modification time in an existing undo data container.
+ */
+void
+nautilus_undo_stack_action_data_add_trashed_file (NautilusUndoStackActionData *action_data,
+ GFile *file,
+ guint64 mtime)
+{
+ guint64 *modification_time;
+ char *original_uri;
+
+ g_return_if_fail (action_data != NULL);
+
+ modification_time = g_new (guint64, 1);
+ *modification_time = mtime;
+
+ original_uri = g_file_get_uri (file);
+
+ g_hash_table_insert (action_data->trashed, original_uri, modification_time);
+
+ action_data->is_valid = TRUE;
+}
+
+/**
+ * nautilus_undo_stack_action_data_add_file_permissions:
+ * @action_data:
+ * @file:
+ * @permission:
+ *
+ * Pushes a recursive permission change data in an existing undo data container
+ */
+void
+nautilus_undo_stack_action_data_add_file_permissions (NautilusUndoStackActionData *action_data,
+ GFile *file,
+ guint32 permission)
+{
+ guint32 *current_permissions;
+ char *original_uri;
+
+ g_return_if_fail (action_data != NULL);
+
+ current_permissions = g_new (guint32, 1);
+ *current_permissions = permission;
+
+ original_uri = g_file_get_uri (file);
+
+ g_hash_table_insert (action_data->original_permissions, original_uri, current_permissions);
+
+ action_data->is_valid = TRUE;
+}
+
+/**
+ * nautilus_undo_stack_action_data_set_file_permissions:
+ * @action_data:
+ * @uri:
+ * @current_permissions:
+ * @new_permissions:
+ *
+ * Sets the original file permission in an existing undo data container
+ */
+void
+nautilus_undo_stack_action_data_set_file_permissions (NautilusUndoStackActionData *action_data,
+ GFile *file,
+ guint32 current_permissions,
+ guint32 new_permissions)
+{
+ g_return_if_fail (action_data != NULL);
+ g_return_if_fail (G_IS_FILE (file));
+
+ action_data->target_file = g_object_ref (file);
+ action_data->current_permissions = current_permissions;
+ action_data->new_permissions = new_permissions;
+
+ action_data->is_valid = TRUE;
+}
+
+/**
+ * nautilus_undo_stack_action_data_set_owner_change_information:
+ * @action_data:
+ * @uri:
+ * @current_user:
+ * @new_user:
+ *
+ * Sets the change owner information in an existing undo data container
+ */
+void
+nautilus_undo_stack_action_data_set_owner_change_information (NautilusUndoStackActionData *action_data,
+ GFile *file,
+ const char *current_user,
+ const char *new_user)
+{
+ g_return_if_fail (action_data != NULL);
+ g_return_if_fail (G_IS_FILE (file));
+
+ action_data->target_file = g_object_ref (file);
+ action_data->original_user_name = g_strdup (current_user);
+ action_data->new_user_name = g_strdup (new_user);
+
+ action_data->is_valid = TRUE;
+}
+
+/**
+ * nautilus_undo_stack_action_data_set_group_change_information:
+ * @action_data:
+ * @uri:
+ * @current_group:
+ * @new_group:
+ *
+ * Sets the change group information in an existing undo data container
+ */
+void
+nautilus_undo_stack_action_data_set_group_change_information (NautilusUndoStackActionData *action_data,
+ GFile *file,
+ const char *current_group,
+ const char *new_group)
+{
+ g_return_if_fail (action_data != NULL);
+ g_return_if_fail (G_IS_FILE (file));
+
+ action_data->target_file = g_object_ref (file);
+ action_data->original_group_name = g_strdup (current_group);
+ action_data->new_group_name = g_strdup (new_group);
+
+ action_data->is_valid = TRUE;
+}
+
+/**
+ * nautilus_undo_stack_action_data_set_recursive_permissions:
+ * @data:
+ * @file_permissions:
+ * @file_mask:
+ * @dir_permissions:
+ * @dir_mask:
+ *
+ * Sets the permission change mask
+ */
+void
+nautilus_undo_stack_action_data_set_recursive_permissions (NautilusUndoStackActionData *action_data,
+ guint32 file_permissions,
+ guint32 file_mask,
+ guint32 dir_permissions,
+ guint32 dir_mask)
+{
+ g_return_if_fail (action_data != NULL);
+
+ action_data->file_permissions = file_permissions;
+ action_data->file_mask = file_mask;
+ action_data->dir_permissions = dir_permissions;
+ action_data->dir_mask = dir_mask;
+
+ action_data->is_valid = TRUE;
+}
+
+/**
+ * nautilus_undo_stack_action_data_set_create_data:
+ * @action_data:
+ * @file:
+ * @template:
+ *
+ * Sets create file information
+ */
+void
+nautilus_undo_stack_action_data_set_create_data (NautilusUndoStackActionData *action_data,
+ GFile *file,
+ const char *template)
+{
+ g_return_if_fail (action_data != NULL);
+ g_return_if_fail (G_IS_FILE (file));
+
+ action_data->target_file = g_object_ref (file);
+ action_data->template = g_strdup (template);
+
+ action_data->is_valid = TRUE;
+}
+
+/**
+ * nautilus_undo_stack_action_data_set_rename_information:
+ * @action_data: the undo action data instance
+ * @old_file: a #GFile containing the file's old location
+ * @new_file: a #GFile containing the file's new location
+ *
+ * Sets rename information.
+ */
+void
+nautilus_undo_stack_action_data_set_rename_information (NautilusUndoStackActionData *action_data,
+ GFile *old_file,
+ GFile *new_file)
+{
+ g_return_if_fail (action_data != NULL);
+
+ action_data->old_file = g_object_ref (old_file);
+ action_data->new_file = g_object_ref (new_file);
+
+ action_data->is_valid = TRUE;
+}
+
+static NautilusUndoStackActionData *
+stack_scroll_right (NautilusUndoStackManagerPrivate *priv)
+{
+ NautilusUndoStackActionData *data;
+
+ if (!can_undo (priv))
+ return NULL;
+
+ data = (NautilusUndoStackActionData *) g_queue_peek_nth (priv->stack, priv->index);
+ if (priv->index < g_queue_get_length (priv->stack)) {
+ priv->index++;
+ }
+
+ return data;
+}
+
+static NautilusUndoStackActionData *
+stack_scroll_left (NautilusUndoStackManagerPrivate *priv)
+{
+ NautilusUndoStackActionData *data;
+
+ if (!can_redo (priv))
+ return NULL;
+
+ priv->index--;
+ data = (NautilusUndoStackActionData *) g_queue_peek_nth (priv->stack, priv->index);
+
+ return data;
+}
+
+static void
+stack_clear_n_oldest (GQueue * stack, guint n)
+{
+ NautilusUndoStackActionData *action;
+ guint i;
+
+ for (i = 0; i < n; i++) {
+ action = (NautilusUndoStackActionData *) g_queue_pop_tail (stack);
+ if (action->locked) {
+ action->freed = TRUE;
+ } else {
+ free_undo_stack_action_data (action);
+ }
+ }
+}
+
+static void
+stack_fix_size (NautilusUndoStackManagerPrivate *priv)
+{
+ guint length;
+
+ length = g_queue_get_length (priv->stack);
+
+ if (length > priv->undo_levels) {
+ if (priv->index > (priv->undo_levels + 1)) {
+ /* If the index will fall off the stack
+ * move it back to the maximum position */
+ priv->index = priv->undo_levels + 1;
+ }
+ stack_clear_n_oldest (priv->stack, length - (priv->undo_levels));
+ }
+}
+
+static void
+clear_redo_actions (NautilusUndoStackManagerPrivate *priv)
+{
+ while (priv->index > 0) {
+ NautilusUndoStackActionData *head;
+ head = (NautilusUndoStackActionData *)g_queue_pop_head (priv->stack);
+ free_undo_stack_action_data (head);
+ priv->index--;
+ }
+}
+
+static void
+stack_push_action (NautilusUndoStackManagerPrivate *priv,
+ NautilusUndoStackActionData *action)
+{
+ guint length;
+
+ clear_redo_actions (priv);
+
+ g_queue_push_head (priv->stack, (gpointer) action);
+ length = g_queue_get_length (priv->stack);
+
+ if (length > priv->undo_levels) {
+ stack_fix_size (priv);
+ }
+}
+
+static char *
+get_first_target_short_name (NautilusUndoStackActionData *action)
+{
+ GList *targets_first;
+ char *file_name;
+
+ g_assert (action != NULL);
+
+ targets_first = g_list_first (action->destinations);
+ file_name = g_strdup (targets_first->data);
+
+ return file_name;
+}
+
+static char *
+get_undo_description (NautilusUndoStackActionData *action)
+{
+ char *description;
+ char *source;
+ guint count;
+
+ g_return_val_if_fail (action != NULL, NULL);
+
+ description = NULL;
+ source = NULL;
+
+ if (action->undo_description != NULL)
+ return action->undo_description;
+
+ if (action->src_dir) {
+ source = g_file_get_path (action->src_dir);
+ }
+ count = action->count;
+ switch (action->type) {
+ case NAUTILUS_UNDO_STACK_COPY:
+ if (count != 1) {
+ description = g_strdup_printf (_("Delete %d copied items"), count);
+ } else {
+ char *name;
+ name = get_first_target_short_name (action);
+ description = g_strdup_printf (_("Delete '%s'"), name);
+ g_free (name);
+ }
+ break;
+ case NAUTILUS_UNDO_STACK_DUPLICATE:
+ if (count != 1) {
+ description = g_strdup_printf (_("Delete %d duplicated items"), count);
+ } else {
+ char *name;
+ name = get_first_target_short_name (action);
+ description = g_strdup_printf (_("Delete '%s'"), name);
+ g_free (name);
+ }
+ break;
+ case NAUTILUS_UNDO_STACK_MOVE:
+ if (count != 1) {
+ description = g_strdup_printf (_("Move %d items back to '%s'"), count, source);
+ } else {
+ char *name;
+ name = get_first_target_short_name (action);
+ description = g_strdup_printf (_("Move '%s' back to '%s'"), name, source);
+ g_free (name);
+ }
+ break;
+ case NAUTILUS_UNDO_STACK_RENAME:
+ {
+ char *from_name;
+ char *to_name;
+ from_name = g_file_get_parse_name (action->new_file);
+ to_name = g_file_get_parse_name (action->old_file);
+ description = g_strdup_printf (_("Rename '%s' as '%s'"), from_name, to_name);
+ g_free (from_name);
+ g_free (to_name);
+ }
+ break;
+ case NAUTILUS_UNDO_STACK_CREATE_FILE_FROM_TEMPLATE:
+ case NAUTILUS_UNDO_STACK_CREATE_EMPTY_FILE:
+ case NAUTILUS_UNDO_STACK_CREATE_FOLDER:
+ {
+ char *name;
+ name = g_file_get_parse_name (action->target_file);
+ description = g_strdup_printf (_("Delete '%s'"), name);
+ g_free (name);
+ }
+ break;
+ case NAUTILUS_UNDO_STACK_MOVE_TO_TRASH:
+ {
+ count = g_hash_table_size (action->trashed);
+ if (count != 1) {
+ description = g_strdup_printf (_("Restore %d items from trash"), count);
+ } else {
+ GList *keys, *first;
+ char *item, *name, *orig_path;
+ GFile *file;
+
+ keys = g_hash_table_get_keys (action->trashed);
+ first = g_list_first (keys);
+ item = (char *) first->data;
+ file = g_file_new_for_commandline_arg (item);
+ name = g_file_get_basename (file);
+ orig_path = g_file_get_path (file);
+ description = g_strdup_printf (_("Restore '%s' to '%s'"), name, orig_path);
+ g_free (name);
+ g_free (orig_path);
+ g_list_free (keys);
+ g_object_unref (file);
+ }
+ }
+ break;
+ case NAUTILUS_UNDO_STACK_RESTORE_FROM_TRASH:
+ {
+ if (count != 1) {
+ description = g_strdup_printf (_("Move %d items back to trash"), count);
+ } else {
+ char *name;
+ name = get_first_target_short_name (action);
+ description = g_strdup_printf (_("Move '%s' back to trash"), name);
+ g_free (name);
+ }
+ }
+ break;
+ case NAUTILUS_UNDO_STACK_CREATE_LINK:
+ {
+ if (count != 1) {
+ description = g_strdup_printf (_("Delete links to %d items"), count);
+ } else {
+ char *name;
+ name = get_first_target_short_name (action);
+ description = g_strdup_printf (_("Delete link to '%s'"), name);
+ g_free (name);
+ }
+ }
+ break;
+ case NAUTILUS_UNDO_STACK_RECURSIVE_SET_PERMISSIONS:
+ {
+ char *name;
+ name = g_file_get_path (action->dest_dir);
+ description = g_strdup_printf (_("Restore original permissions of items enclosed in '%s'"), name);
+ g_free (name);
+ }
+ break;
+ case NAUTILUS_UNDO_STACK_SET_PERMISSIONS:
+ {
+ char *name;
+ name = g_file_get_parse_name (action->target_file);
+ description = g_strdup_printf (_("Restore original permissions of '%s'"), name);
+ g_free (name);
+ }
+ break;
+ case NAUTILUS_UNDO_STACK_CHANGE_GROUP:
+ {
+ char *name;
+ name = g_file_get_parse_name (action->target_file);
+ description = g_strdup_printf (_("Restore group of '%s' to '%s'"),
+ name, action->original_group_name);
+ g_free (name);
+ }
+ break;
+ case NAUTILUS_UNDO_STACK_CHANGE_OWNER:
+ {
+ char *name;
+ name = g_file_get_parse_name (action->target_file);
+ description = g_strdup_printf (_("Restore owner of '%s' to '%s'"),
+ name, action->original_user_name);
+ g_free (name);
+ }
+ break;
+ default:
+ break;
+ }
+ if (source) {
+ g_free (source);
+ }
+ action->undo_description = description;
+
+ return description;
+}
+
+static char *
+get_redo_description (NautilusUndoStackActionData *action)
+{
+ char *description;
+ char *destination;
+ guint count;
+
+ description = NULL;
+ destination = NULL;
+
+ g_return_val_if_fail (action != NULL, NULL);
+
+ if (action->redo_description != NULL) {
+ return action->redo_description;
+ }
+
+ if (action->dest_dir) {
+ destination = g_file_get_path (action->dest_dir);
+ }
+
+ count = action->count;
+ switch (action->type) {
+ case NAUTILUS_UNDO_STACK_COPY:
+ if (count != 1) {
+ description = g_strdup_printf (_("Copy %d items to '%s'"), count, destination);
+ } else {
+ char *name;
+ name= get_first_target_short_name (action);
+ description = g_strdup_printf (_("Copy '%s' to '%s'"), name, destination);
+ g_free (name);
+ }
+ break;
+ case NAUTILUS_UNDO_STACK_DUPLICATE:
+ if (count != 1) {
+ description = g_strdup_printf (_("Duplicate of %d items in '%s'"), count, destination);
+ } else {
+ char *name;
+ name = get_first_target_short_name (action);
+ description = g_strdup_printf (_("Duplicate '%s' in '%s'"), name, destination);
+ g_free (name);
+ }
+ break;
+ case NAUTILUS_UNDO_STACK_MOVE:
+ if (count != 1) {
+ description = g_strdup_printf (_("Move %d items to '%s'"), count, destination);
+ } else {
+ char *name;
+ name = get_first_target_short_name (action);
+ description = g_strdup_printf (_("Move '%s' to '%s'"), name, destination);
+ g_free (name);
+ }
+ break;
+ case NAUTILUS_UNDO_STACK_RENAME:
+ {
+ char *from_name;
+ char *to_name;
+ from_name = g_file_get_parse_name (action->old_file);
+ to_name = g_file_get_parse_name (action->new_file);
+ description = g_strdup_printf (_("Rename '%s' as '%s'"), from_name, to_name);
+ g_free (from_name);
+ g_free (to_name);
+ }
+ break;
+ case NAUTILUS_UNDO_STACK_CREATE_FILE_FROM_TEMPLATE:
+ {
+ char *name;
+ name = g_file_get_parse_name (action->target_file);
+ description = g_strdup_printf (_("Create new file '%s' from template "), name);
+ g_free (name);
+ }
+ break;
+ case NAUTILUS_UNDO_STACK_CREATE_EMPTY_FILE:
+ {
+ char *name;
+ name = g_file_get_parse_name (action->target_file);
+ description = g_strdup_printf (_("Create an empty file '%s'"), name);
+ g_free (name);
+ }
+ break;
+ case NAUTILUS_UNDO_STACK_CREATE_FOLDER:
+ {
+ char *name;
+ name = g_file_get_parse_name (action->target_file);
+ description = g_strdup_printf (_("Create a new folder '%s'"), name);
+ g_free (name);
+ }
+ break;
+ case NAUTILUS_UNDO_STACK_MOVE_TO_TRASH:
+ {
+ count = g_hash_table_size (action->trashed);
+ if (count != 1) {
+ description = g_strdup_printf (_("Move %d items to trash"), count);
+ } else {
+ GList *keys;
+ GList *first;
+ char *item;
+ char *name;
+
+ keys = g_hash_table_get_keys (action->trashed);
+ first = g_list_first (keys);
+ item = (char *) first->data;
+ name = g_file_get_parse_name (action->target_file);
+ description = g_strdup_printf (_("Move '%s' to trash"), name);
+ g_free (name);
+ g_list_free (keys);
+ }
+ }
+ break;
+ case NAUTILUS_UNDO_STACK_RESTORE_FROM_TRASH:
+ {
+ if (count != 1) {
+ description = g_strdup_printf (_("Restore %d items from trash"), count);
+ } else {
+ char *name;
+ name = get_first_target_short_name (action);
+ description = g_strdup_printf (_("Restore '%s' from trash"), name);
+ g_free (name);
+ }
+ }
+ break;
+ case NAUTILUS_UNDO_STACK_CREATE_LINK:
+ {
+ if (count != 1) {
+ description = g_strdup_printf (_("Create links to %d items"), count);
+ } else {
+ char *name;
+ name = get_first_target_short_name (action);
+ description = g_strdup_printf (_("Create link to '%s'"), name);
+ g_free (name);
+ }
+ }
+ break;
+ case NAUTILUS_UNDO_STACK_RECURSIVE_SET_PERMISSIONS:
+ {
+ char *name;
+ name = g_file_get_path (action->dest_dir);
+ description = g_strdup_printf (_("Set permissions of items enclosed in '%s'"), name);
+ g_free (name);
+ }
+ break;
+ case NAUTILUS_UNDO_STACK_SET_PERMISSIONS:
+ {
+ char *name;
+ name = g_file_get_parse_name (action->target_file);
+ description = g_strdup_printf (_("Set permissions of '%s'"), name);
+ g_free (name);
+ }
+ break;
+ case NAUTILUS_UNDO_STACK_CHANGE_GROUP:
+ {
+ char *name;
+ name = g_file_get_parse_name (action->target_file);
+ description = g_strdup_printf (_("Set group of '%s' to '%s'"), name, action->new_group_name);
+ g_free (name);
+ }
+ break;
+ case NAUTILUS_UNDO_STACK_CHANGE_OWNER:
+ {
+ char *name;
+ name = g_file_get_parse_name (action->target_file);
+ description = g_strdup_printf (_("Set owner of '%s' to '%s'"), name, action->new_user_name);
+ g_free (name);
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (destination) {
+ g_free (destination);
+ }
+
+ action->redo_description = description;
+
+ return description;
+}
+
+static char *
+get_undo_label (NautilusUndoStackActionData *action)
+{
+ char *label;
+ guint count;
+
+ g_return_val_if_fail (action != NULL, NULL);
+
+ if (action->redo_label != NULL) {
+ return action->redo_label;
+ }
+
+ label = NULL;
+ count = action->count;
+ switch (action->type) {
+ case NAUTILUS_UNDO_STACK_COPY:
+ label = g_strdup_printf (ngettext ("_Undo copy of %d item",
+ "_Undo copy of %d items", count), count);
+ break;
+ case NAUTILUS_UNDO_STACK_DUPLICATE:
+ label = g_strdup_printf (ngettext ("_Undo duplicate of %d item",
+ "_Undo duplicate of %d items", count), count);
+ break;
+ case NAUTILUS_UNDO_STACK_MOVE:
+ label = g_strdup_printf (ngettext ("_Undo move of %d item",
+ "_Undo move of %d items", count), count);
+ break;
+ case NAUTILUS_UNDO_STACK_RENAME:
+ label = g_strdup_printf (ngettext ("_Undo rename of %d item",
+ "_Undo rename of %d items", count), count);
+ break;
+ case NAUTILUS_UNDO_STACK_CREATE_EMPTY_FILE:
+ label = g_strdup_printf (_("_Undo creation of an empty file"));
+ break;
+ case NAUTILUS_UNDO_STACK_CREATE_FILE_FROM_TEMPLATE:
+ label = g_strdup_printf (_("_Undo creation of a file from template"));
+ break;
+ case NAUTILUS_UNDO_STACK_CREATE_FOLDER:
+ label = g_strdup_printf (ngettext ("_Undo creation of %d folder",
+ "_Undo creation of %d folders", count), count);
+ break;
+ case NAUTILUS_UNDO_STACK_MOVE_TO_TRASH:
+ label = g_strdup_printf (ngettext ("_Undo move to trash of %d item",
+ "_Undo move to trash of %d items", count), count);
+ break;
+ case NAUTILUS_UNDO_STACK_RESTORE_FROM_TRASH:
+ label = g_strdup_printf (ngettext ("_Undo restore from trash of %d item",
+ "_Undo restore from trash of %d items", count), count);
+ break;
+ case NAUTILUS_UNDO_STACK_CREATE_LINK:
+ label = g_strdup_printf (ngettext ("_Undo create link to %d item",
+ "_Undo create link to %d items", count), count);
+ break;
+ case NAUTILUS_UNDO_STACK_RECURSIVE_SET_PERMISSIONS:
+ label = g_strdup_printf (ngettext ("Undo recursive change permissions of %d item",
+ "Undo recursive change permissions of %d items",
+ count), count);
+ break;
+ case NAUTILUS_UNDO_STACK_SET_PERMISSIONS:
+ label = g_strdup_printf (ngettext ("Undo change permissions of %d item",
+ "Undo change permissions of %d items", count), count);
+ break;
+ case NAUTILUS_UNDO_STACK_CHANGE_GROUP:
+ label = g_strdup_printf (ngettext ("Undo change group of %d item",
+ "Undo change group of %d items", count), count);
+ break;
+ case NAUTILUS_UNDO_STACK_CHANGE_OWNER:
+ label = g_strdup_printf (ngettext ("Undo change owner of %d item",
+ "Undo change owner of %d items", count), count);
+ break;
+ default:
+ break;
+ }
+ action->undo_label = label;
+
+ return label;
+}
+
+static char *
+get_redo_label (NautilusUndoStackActionData *action)
+{
+ char *label;
+ guint count;
+
+ g_return_val_if_fail (action != NULL, NULL);
+
+ if (action->redo_label != NULL) {
+ return action->redo_label;
+ }
+
+ label = NULL;
+ count = action->count;
+ switch (action->type) {
+ case NAUTILUS_UNDO_STACK_COPY:
+ label = g_strdup_printf (ngettext ("_Redo copy of %d item",
+ "_Redo copy of %d items", count), count);
+ break;
+ case NAUTILUS_UNDO_STACK_DUPLICATE:
+ label = g_strdup_printf (ngettext ("_Redo duplicate of %d item",
+ "_Redo duplicate of %d items", count), count);
+ break;
+ case NAUTILUS_UNDO_STACK_MOVE:
+ label = g_strdup_printf (ngettext ("_Redo move of %d item",
+ "_Redo move of %d items", count), count);
+ break;
+ case NAUTILUS_UNDO_STACK_RENAME:
+ label = g_strdup_printf (ngettext ("_Redo rename of %d item",
+ "_Redo rename of %d items", count), count);
+ break;
+ case NAUTILUS_UNDO_STACK_CREATE_EMPTY_FILE:
+ label = g_strdup_printf (_("_Redo creation of an empty file"));
+ break;
+ case NAUTILUS_UNDO_STACK_CREATE_FILE_FROM_TEMPLATE:
+ label = g_strdup_printf (_("_Redo creation of a file from template"));
+ break;
+ case NAUTILUS_UNDO_STACK_CREATE_FOLDER:
+ label = g_strdup_printf (ngettext ("_Redo creation of %d folder",
+ "_Redo creation of %d folders", count), count);
+ break;
+ case NAUTILUS_UNDO_STACK_MOVE_TO_TRASH:
+ label = g_strdup_printf (ngettext ("_Redo move to trash of %d item",
+ "_Redo move to trash of %d items", count), count);
+ break;
+ case NAUTILUS_UNDO_STACK_RESTORE_FROM_TRASH:
+ label = g_strdup_printf (ngettext ("_Redo restore from trash of %d item",
+ "_Redo restore from trash of %d items", count), count);
+ break;
+ case NAUTILUS_UNDO_STACK_CREATE_LINK:
+ label = g_strdup_printf (ngettext ("_Redo create link to %d item",
+ "_Redo create link to %d items", count), count);
+ break;
+ case NAUTILUS_UNDO_STACK_RECURSIVE_SET_PERMISSIONS:
+ label = g_strdup_printf (ngettext ("Redo recursive change permissions of %d item",
+ "Redo recursive change permissions of %d items",
+ count), count);
+ break;
+ case NAUTILUS_UNDO_STACK_SET_PERMISSIONS:
+ label = g_strdup_printf (ngettext ("Redo change permissions of %d item",
+ "Redo change permissions of %d items", count), count);
+ break;
+ case NAUTILUS_UNDO_STACK_CHANGE_GROUP:
+ label = g_strdup_printf (ngettext ("Redo change group of %d item",
+ "Redo change group of %d items", count), count);
+ break;
+ case NAUTILUS_UNDO_STACK_CHANGE_OWNER:
+ label = g_strdup_printf (ngettext ("Redo change owner of %d item",
+ "Redo change owner of %d items", count), count);
+ break;
+ default:
+ break;
+ }
+
+ action->redo_label = label;
+
+ return label;
+}
+
+static void
+undo_redo_done_transfer_callback (GHashTable * debuting_uris, gpointer data)
+{
+ NautilusUndoStackActionData *action;
+
+ action = (NautilusUndoStackActionData *) data;
+
+ /* If the action needed to be freed but was locked, free now */
+ if (action->freed) {
+ free_undo_stack_action_data (action);
+ } else {
+ action->locked = FALSE;
+ }
+
+ /* Update menus */
+ do_menu_update (action->manager);
+}
+
+static void
+undo_redo_done_delete_callback (GHashTable *debuting_uris, gboolean user_cancel, gpointer callback_data)
+{
+ undo_redo_done_transfer_callback (debuting_uris, callback_data);
+}
+
+static void
+undo_redo_done_create_callback (GFile * new_file, gpointer callback_data)
+{
+ undo_redo_done_transfer_callback (NULL, callback_data);
+}
+
+static void
+undo_redo_op_callback (gpointer callback_data)
+{
+ undo_redo_done_transfer_callback (NULL, callback_data);
+}
+
+static void
+undo_redo_done_rename_callback (NautilusFile * file, GFile * result_location, GError * error, gpointer callback_data)
+{
+ undo_redo_done_transfer_callback (NULL, callback_data);
+}
+
+static void
+free_undo_stack_action_data (NautilusUndoStackActionData *action)
+{
+ g_return_if_fail (action != NULL);
+
+ g_free (action->template);
+
+ g_free (action->undo_label);
+ g_free (action->undo_description);
+ g_free (action->redo_label);
+ g_free (action->redo_description);
+
+ g_free (action->original_group_name);
+ g_free (action->original_user_name);
+ g_free (action->new_group_name);
+ g_free (action->new_user_name);
+
+ if (action->sources) {
+ g_list_foreach (action->sources, (GFunc) g_free, NULL);
+ g_list_free (action->sources);
+ }
+ if (action->destinations) {
+ g_list_foreach (action->destinations, (GFunc) g_free, NULL);
+ g_list_free (action->destinations);
+ }
+
+ if (action->trashed) {
+ g_hash_table_destroy (action->trashed);
+ }
+
+ if (action->original_permissions) {
+ g_hash_table_destroy (action->original_permissions);
+ }
+
+ if (action->old_file)
+ g_object_unref (action->old_file);
+ if (action->new_file)
+ g_free (action->new_file);
+
+ if (action->target_file)
+ g_object_unref (action->target_file);
+ if (action->src_dir)
+ g_object_unref (action->src_dir);
+ if (action->dest_dir)
+ g_object_unref (action->dest_dir);
+
+ g_slice_free (NautilusUndoStackActionData, action);
+}
+
+static void
+undostack_dispose_all (GQueue * queue)
+{
+ g_queue_foreach (queue, (GFunc)free_undo_stack_action_data, NULL);
+}
+
+static gboolean
+can_undo (NautilusUndoStackManagerPrivate *priv)
+{
+ return (get_next_undo_action (priv) != NULL);
+}
+
+static gboolean
+can_redo (NautilusUndoStackManagerPrivate *priv)
+{
+ return (get_next_redo_action (priv) != NULL);
+}
+
+static NautilusUndoStackActionData *
+get_next_redo_action (NautilusUndoStackManagerPrivate *priv)
+{
+ NautilusUndoStackActionData *action;
+
+ if (g_queue_is_empty (priv->stack)) {
+ return NULL;
+ }
+
+ if (priv->index == 0) {
+ /* ... no redo actions */
+ return NULL;
+ }
+
+ action = (NautilusUndoStackActionData *) g_queue_peek_nth (priv->stack, priv->index - 1);
+
+ if (action->locked) {
+ return NULL;
+ } else {
+ return action;
+ }
+}
+
+static NautilusUndoStackActionData *
+get_next_undo_action (NautilusUndoStackManagerPrivate *priv)
+{
+ NautilusUndoStackActionData *action;
+ guint stack_size;
+
+ if (g_queue_is_empty (priv->stack)) {
+ return NULL;
+ }
+
+ stack_size = g_queue_get_length (priv->stack);
+
+ if (priv->index == stack_size) {
+ return NULL;
+ }
+
+ action = (NautilusUndoStackActionData *) g_queue_peek_nth (priv->stack, priv->index);
+
+ if (action->locked) {
+ return NULL;
+ } else {
+ return action;
+ }
+}
+
+static void
+do_menu_update (NautilusUndoStackManager * manager)
+{
+ NautilusUndoStackActionData *action;
+ NautilusUndoStackManagerPrivate *priv;
+ NautilusUndoStackMenuData *data;
+
+ g_return_if_fail (manager != NULL);
+
+ priv = manager->priv;
+ data = g_slice_new0 (NautilusUndoStackMenuData);
+
+ g_mutex_lock (priv->mutex);
+
+ action = get_next_undo_action (priv);
+ data->undo_label = get_undo_label (action);
+ data->undo_description = get_undo_description (action);
+
+ action = get_next_redo_action (priv);
+ data->redo_label = get_redo_label (action);
+ data->redo_description = get_redo_description (action);
+
+ g_mutex_unlock (priv->mutex);
+
+ /* Update menus */
+ g_signal_emit_by_name (manager, "request-menu-update", data);
+
+ /* Free the signal data */
+ /* Note: we do not own labels and descriptions, they are part of the action. */
+ g_slice_free (NautilusUndoStackMenuData, data);
+}
+
+static GList *
+construct_gfile_list (const GList * urilist, GFile * parent)
+{
+ const GList *l;
+ GList *file_list = NULL;
+ GFile *file;
+
+ for (l = urilist; l != NULL; l = l->next) {
+ file = g_file_get_child (parent, l->data);
+ file_list = g_list_append (file_list, file);
+ }
+
+ return file_list;
+}
+
+static GList *
+construct_gfile_list_from_uri (char *uri)
+{
+ GList *file_list = NULL;
+ GFile *file;
+
+ file = g_file_new_for_uri (uri);
+ file_list = g_list_append (file_list, file);
+
+ return file_list;
+}
+
+static GList *
+uri_list_to_gfile_list (GList * urilist)
+{
+ const GList *l;
+ GList *file_list = NULL;
+ GFile *file;
+
+ for (l = urilist; l != NULL; l = l->next) {
+ file = g_file_new_for_uri (l->data);
+ file_list = g_list_append (file_list, file);
+ }
+
+ return file_list;
+}
+
+/* TODO: Synch-I/O, error handling */
+static GHashTable *
+retrieve_files_to_restore (GHashTable * trashed)
+{
+ GFileEnumerator *enumerator;
+ GHashTable *to_restore;
+ GFile *trash;
+
+ to_restore = g_hash_table_new_full (g_direct_hash, g_direct_equal, g_object_unref, g_free);
+
+ trash = g_file_new_for_uri ("trash:///");
+
+ enumerator = g_file_enumerate_children (trash,
+ G_FILE_ATTRIBUTE_STANDARD_NAME","
+ G_FILE_ATTRIBUTE_TIME_MODIFIED","
+ G_FILE_ATTRIBUTE_TRASH_ORIG_PATH,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ NULL, NULL);
+
+ if (enumerator) {
+ GFileInfo *info;
+ guint64 *mtime;
+ gpointer lookupvalue;
+ GFile *item;
+ guint64 mtime_item;
+ char *origpath;
+ GFile *origfile;
+ char *origuri;
+
+ mtime = 0;
+ while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL)) != NULL) {
+ /* Retrieve the original file uri */
+ origpath = g_file_info_get_attribute_as_string (info, G_FILE_ATTRIBUTE_TRASH_ORIG_PATH);
+ origfile = g_file_new_for_path (origpath);
+ origuri = g_file_get_uri (origfile);
+ g_object_unref (origfile);
+ g_free (origpath);
+
+ lookupvalue = g_hash_table_lookup (trashed, origuri);
+
+ if (lookupvalue) {
+ mtime = (guint64 *)lookupvalue;
+ mtime_item = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
+ if (*mtime == mtime_item) {
+ /* File in the trash */
+ item = g_file_get_child (trash, g_file_info_get_name (info));
+ g_hash_table_insert (to_restore, item, origuri);
+ }
+ } else {
+ g_free (origuri);
+ }
+
+ }
+ g_file_enumerator_close (enumerator, FALSE, NULL);
+ g_object_unref (enumerator);
+ }
+ g_object_unref (trash);
+
+ return to_restore;
+}
diff --git a/libnautilus-private/nautilus-undostack-manager.h b/libnautilus-private/nautilus-undostack-manager.h
new file mode 100644
index 0000000..2bfa47a
--- /dev/null
+++ b/libnautilus-private/nautilus-undostack-manager.h
@@ -0,0 +1,152 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* nautilus-undostack-manager.h - Manages undo/redo of file operations
+ *
+ * Copyright (C) 2007-2010 Amos Brocco
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: Amos Brocco <amos brocco unifr ch>
+ */
+
+#ifndef __NAUTILUS_UNDO_STACK_MANAGER_H__
+#define __NAUTILUS_UNDO_STACK_MANAGER_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+
+typedef struct _NautilusUndoStackManager NautilusUndoStackManager;
+typedef struct _NautilusUndoStackManagerClass NautilusUndoStackManagerClass;
+typedef struct _NautilusUndoStackManagerPrivate NautilusUndoStackManagerPrivate;
+
+#define NAUTILUS_TYPE_UNDO_STACK_MANAGER\
+ (nautilus_undo_stack_manager_get_type())
+#define NAUTILUS_UNDO_STACK_MANAGER(object)\
+ (G_TYPE_CHECK_INSTANCE_CAST((object), NAUTILUS_TYPE_UNDO_STACK_MANAGER,\
+ NautilusUndoStackManager))
+#define NAUTILUS_UNDO_STACK_MANAGER_CLASS(klass)\
+ (G_TYPE_CHECK_CLASS_CAST((klass), NAUTILUS_TYPE_UNDO_STACK_MANAGER,\
+ NautilusUndoStackManagerClass))
+#define NAUTILUS_IS_UNDO_STACK_MANAGER(object)\
+ (G_TYPE_CHECK_INSTANCE_TYPE((object), NAUTILUS_TYPE_UNDO_STACK_MANAGER))
+#define NAUTILUS_IS_UNDO_STACK_MANAGER_CLASS(klass)\
+ (G_TYPE_CHECK_CLASS_TYPE((klass), NAUTILUS_TYPE_UNDO_STACK_MANAGER))
+#define NAUTILUS_UNDO_STACK_MANAGER_GET_CLASS(object)\
+ (G_TYPE_INSTANCE_GET_CLASS((object), NAUTILUS_TYPE_UNDO_STACK_MANAGER,\
+ NautilusUndoStackManagerClass))
+
+typedef enum {
+ NAUTILUS_UNDO_STACK_INVALID,
+ NAUTILUS_UNDO_STACK_COPY,
+ NAUTILUS_UNDO_STACK_DUPLICATE,
+ NAUTILUS_UNDO_STACK_MOVE,
+ NAUTILUS_UNDO_STACK_RENAME,
+ NAUTILUS_UNDO_STACK_CREATE_EMPTY_FILE,
+ NAUTILUS_UNDO_STACK_CREATE_FILE_FROM_TEMPLATE,
+ NAUTILUS_UNDO_STACK_CREATE_FOLDER,
+ NAUTILUS_UNDO_STACK_MOVE_TO_TRASH,
+ NAUTILUS_UNDO_STACK_CREATE_LINK,
+ NAUTILUS_UNDO_STACK_RESTORE_FROM_TRASH,
+ NAUTILUS_UNDO_STACK_SET_PERMISSIONS,
+ NAUTILUS_UNDO_STACK_RECURSIVE_SET_PERMISSIONS,
+ NAUTILUS_UNDO_STACK_CHANGE_OWNER,
+ NAUTILUS_UNDO_STACK_CHANGE_GROUP
+} NautilusUndoStackActionType;
+
+typedef struct _NautilusUndoStackActionData NautilusUndoStackActionData;
+
+typedef struct _NautilusUndoStackMenuData NautilusUndoStackMenuData;
+struct _NautilusUndoStackMenuData {
+ char *undo_label;
+ char *undo_description;
+ char *redo_label;
+ char *redo_description;
+};
+
+typedef void (*NautilusUndoStackFinishCallback) (gpointer data);
+
+struct _NautilusUndoStackManager {
+ GObject parent_instance;
+
+ /* < private > */
+ NautilusUndoStackManagerPrivate* priv;
+};
+
+struct _NautilusUndoStackManagerClass {
+ GObjectClass parent_class;
+};
+
+
+GType nautilus_undo_stack_manager_get_type (void) G_GNUC_CONST;
+
+NautilusUndoStackManager * nautilus_undo_stack_manager_get (void);
+
+void nautilus_undo_stack_manager_add_action (NautilusUndoStackManager *manager,
+ NautilusUndoStackActionData *action);
+void nautilus_undo_stack_manager_undo (NautilusUndoStackManager *manager,
+ NautilusUndoStackFinishCallback callback,
+ gpointer user_data);
+void nautilus_undo_stack_manager_redo (NautilusUndoStackManager *manager,
+ NautilusUndoStackFinishCallback callback,
+ gpointer user_data);
+
+gboolean nautilus_undo_stack_manager_is_undo_redo (NautilusUndoStackManager *manager);
+void nautilus_undo_stack_manager_trash_has_emptied (NautilusUndoStackManager *manager);
+void nautilus_undo_stack_manager_request_menu_update (NautilusUndoStackManager *manager);
+guint64 nautilus_undo_stack_manager_get_file_modification_time (GFile *file);
+
+NautilusUndoStackActionData * nautilus_undo_stack_action_data_new (NautilusUndoStackActionType type,
+ gint items_count);
+
+void nautilus_undo_stack_action_data_set_src_dir (NautilusUndoStackActionData *data,
+ GFile *src);
+void nautilus_undo_stack_action_data_set_dest_dir (NautilusUndoStackActionData *data,
+ GFile *dest);
+void nautilus_undo_stack_action_data_add_origin_target_pair (NautilusUndoStackActionData *data,
+ GFile *origin,
+ GFile *target);
+void nautilus_undo_stack_action_data_set_create_data (NautilusUndoStackActionData *data,
+ GFile *target_file,
+ const char *template_uri);
+void nautilus_undo_stack_action_data_set_rename_information (NautilusUndoStackActionData *data,
+ GFile *old_file,
+ GFile *new_file);
+void nautilus_undo_stack_action_data_add_trashed_file (NautilusUndoStackActionData *data,
+ GFile *file,
+ guint64 mtime);
+void nautilus_undo_stack_action_data_add_file_permissions (NautilusUndoStackActionData *data,
+ GFile *file,
+ guint32 permission);
+void nautilus_undo_stack_action_data_set_recursive_permissions (NautilusUndoStackActionData *data,
+ guint32 file_permissions,
+ guint32 file_mask,
+ guint32 dir_permissions,
+ guint32 dir_mask);
+void nautilus_undo_stack_action_data_set_file_permissions (NautilusUndoStackActionData *data,
+ GFile *file,
+ guint32 current_permissions,
+ guint32 new_permissions);
+void nautilus_undo_stack_action_data_set_owner_change_information (NautilusUndoStackActionData *data,
+ GFile *file,
+ const char *current_user,
+ const char *new_user);
+void nautilus_undo_stack_action_data_set_group_change_information (NautilusUndoStackActionData *data,
+ GFile *file,
+ const char *current_group,
+ const char *new_group);
+
+#endif /* __NAUTILUS_UNDO_STACK_MANAGER_H__ */
diff --git a/src/file-manager/fm-actions.h b/src/file-manager/fm-actions.h
index 077d82c..882f4b8 100644
--- a/src/file-manager/fm-actions.h
+++ b/src/file-manager/fm-actions.h
@@ -106,5 +106,7 @@
#define FM_ACTION_UNSTRETCH "Unstretch"
#define FM_ACTION_ZOOM_ITEMS "Zoom Items"
#define FM_ACTION_SORT_TRASH_TIME "Sort by Trash Time"
+#define FM_ACTION_UNDO "Undo"
+#define FM_ACTION_REDO "Redo"
#endif /* FM_ACTIONS_H */
diff --git a/src/file-manager/fm-directory-view.c b/src/file-manager/fm-directory-view.c
index 8f497c8..5cf0aca 100644
--- a/src/file-manager/fm-directory-view.c
+++ b/src/file-manager/fm-directory-view.c
@@ -77,6 +77,7 @@
#include <libnautilus-private/nautilus-ui-utilities.h>
#include <libnautilus-private/nautilus-signaller.h>
#include <libnautilus-private/nautilus-icon-names.h>
+#include <libnautilus-private/nautilus-undostack-manager.h>
/* Minimum starting update inverval */
#define UPDATE_INTERVAL_MIN 100
@@ -248,6 +249,13 @@ struct FMDirectoryViewDetails
gboolean allow_moves;
GdkPoint context_menu_position;
+
+ gboolean undo_active;
+ gboolean redo_active;
+ gchar* undo_action_description;
+ gchar* undo_action_label;
+ gchar* redo_action_description;
+ gchar* redo_action_label;
};
typedef struct {
@@ -378,6 +386,18 @@ static inline void fm_directory_view_widget_to_file_operation_position (FMDirect
static void fm_directory_view_widget_to_file_operation_position_xy (FMDirectoryView *view,
int *x, int *y);
+/* undo-related actions */
+static void undo_redo_menu_update_callback (NautilusUndoStackManager *manager,
+ gpointer arg1,
+ gpointer data);
+static void undo_update_menu (FMDirectoryView *view);
+static void real_action_undo (FMDirectoryView *view);
+static void real_action_redo (FMDirectoryView *view);
+static void action_undo_callback (GtkAction *action,
+ gpointer callback_data);
+static void action_redo_callback (GtkAction *action,
+ gpointer callback_data);
+
EEL_CLASS_BOILERPLATE (FMDirectoryView, fm_directory_view, GTK_TYPE_SCROLLED_WINDOW)
EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, add_file)
@@ -1843,6 +1863,78 @@ file_list_from_location_list (const GList *uri_list)
return g_list_reverse (file_list);
}
+
+static void
+undo_redo_menu_update_callback (NautilusUndoStackManager* manager, gpointer arg, gpointer data)
+{
+ FMDirectoryView *view;
+ NautilusUndoStackMenuData *menudata;
+
+ view = FM_DIRECTORY_VIEW (data);
+
+ menudata = (NautilusUndoStackMenuData*) arg;
+
+ g_free (view->details->undo_action_label);
+ g_free (view->details->undo_action_description);
+ g_free (view->details->redo_action_label);
+ g_free (view->details->redo_action_description);
+
+ view->details->undo_active = menudata->undo_label ? TRUE : FALSE;
+ view->details->redo_active = menudata->redo_label ? TRUE : FALSE;
+
+ view->details->undo_action_label = g_strdup (menudata->undo_label);
+ view->details->undo_action_description = g_strdup (menudata->undo_description);
+ view->details->redo_action_label = g_strdup (menudata->redo_label);
+ view->details->redo_action_description = g_strdup (menudata->redo_description);
+
+ schedule_update_menus (view);
+}
+
+static void
+undo_update_menu (FMDirectoryView *view)
+{
+ GtkAction *action;
+ gchar *label;
+ gchar *tooltip;
+ gboolean available;
+
+ /* Update undo entry */
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_UNDO);
+ available = view->details->undo_active;
+ if (available) {
+ label = view->details->undo_action_label;
+ tooltip = view->details->undo_action_description;
+ } else {
+ /* Reset to default info */
+ label = _("Undo");
+ tooltip = _("Undo the last action");
+ }
+ g_object_set (action,
+ "label", label,
+ "tooltip", tooltip,
+ NULL);
+ gtk_action_set_sensitive (action, available);
+
+ /* Update redo entry */
+ action = gtk_action_group_get_action (view->details->dir_action_group,
+ FM_ACTION_REDO);
+ available = view->details->redo_active;
+ if (available) {
+ label = view->details->redo_action_label;
+ tooltip = view->details->redo_action_description;
+ } else {
+ /* Reset to default info */
+ label = _("Redo");
+ tooltip = _("Redo the last undone action");
+ }
+ g_object_set (action,
+ "label", label,
+ "tooltip", tooltip,
+ NULL);
+ gtk_action_set_sensitive (action, available);
+}
+
static void
fm_directory_view_set_selection_locations (NautilusView *nautilus_view,
GList *selection_locations)
@@ -1998,6 +2090,21 @@ fm_directory_view_init (FMDirectoryView *view)
g_signal_connect_swapped (gnome_lockdown_preferences,
"changed::" NAUTILUS_PREFERENCES_LOCKDOWN_COMMAND_LINE,
G_CALLBACK (schedule_update_menus), view);
+
+ /* Update undo actions stuff and connect signals from the undostack manager */
+ view->details->undo_active = FALSE;
+ view->details->redo_active = FALSE;
+ view->details->undo_action_description = NULL;
+ view->details->undo_action_label = NULL;
+ view->details->redo_action_description = NULL;
+ view->details->redo_action_label = NULL;
+
+ NautilusUndoStackManager* manager = nautilus_undo_stack_manager_get ();
+
+ g_signal_connect_object (G_OBJECT(manager), "request-menu-update",
+ G_CALLBACK(undo_redo_menu_update_callback), view, 0);
+
+ nautilus_undo_stack_manager_request_menu_update (nautilus_undo_stack_manager_get());
}
static void
@@ -2675,6 +2782,8 @@ copy_move_done_callback (GHashTable *debuting_files, gpointer data)
(GClosureNotify) debuting_files_data_free,
G_CONNECT_AFTER);
}
+ /* Schedule menu update for undo items */
+ schedule_update_menus (directory_view);
}
copy_move_done_data_free (copy_move_done_data);
@@ -3132,6 +3241,20 @@ schedule_changes (FMDirectoryView *view)
}
static void
+action_undo_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ real_action_undo (FM_DIRECTORY_VIEW (callback_data));
+}
+
+static void
+action_redo_callback (GtkAction *action,
+ gpointer callback_data)
+{
+ real_action_redo (FM_DIRECTORY_VIEW (callback_data));
+}
+
+static void
files_added_callback (NautilusDirectory *directory,
GList *files,
gpointer callback_data)
@@ -6106,6 +6229,36 @@ invoke_external_bulk_rename_utility (FMDirectoryView *view,
}
static void
+real_action_undo (FMDirectoryView *view)
+{
+ NautilusUndoStackManager *manager;
+
+ manager = nautilus_undo_stack_manager_get ();
+
+ /* Disable menus because they are in an untrustworthy status */
+ view->details->undo_active = FALSE;
+ view->details->redo_active = FALSE;
+ fm_directory_view_update_menus (view);
+
+ nautilus_undo_stack_manager_undo (manager, NULL, NULL);
+}
+
+static void
+real_action_redo (FMDirectoryView *view)
+{
+ NautilusUndoStackManager *manager;
+
+ manager = nautilus_undo_stack_manager_get ();
+
+ /* Disable menus because they are in an untrustworthy status */
+ view->details->undo_active = FALSE;
+ view->details->redo_active = FALSE;
+ fm_directory_view_update_menus (view);
+
+ nautilus_undo_stack_manager_redo (manager, NULL, NULL);
+}
+
+static void
real_action_rename (FMDirectoryView *view,
gboolean select_all)
{
@@ -7152,6 +7305,14 @@ static const GtkActionEntry directory_view_entries[] = {
/* label, accelerator */ N_("_Restore"), NULL,
NULL,
G_CALLBACK (action_restore_from_trash_callback) },
+ /* name, stock id */ { FM_ACTION_UNDO, GTK_STOCK_UNDO,
+ /* label, accelerator */ N_("_Undo"), "<control>Z",
+ /* tooltip */ N_("Undo the last action"),
+ G_CALLBACK (action_undo_callback) },
+ /* name, stock id */ { FM_ACTION_REDO, GTK_STOCK_REDO,
+ /* label, accelerator */ N_("_Redo"), "<control>Y",
+ /* tooltip */ N_("Redo the last undone action"),
+ G_CALLBACK (action_redo_callback) },
/*
* multiview-TODO: decide whether "Reset to Defaults" should
* be window-wide, and not just view-wide.
@@ -8820,6 +8981,8 @@ real_update_menus (FMDirectoryView *view)
real_update_menus_volumes (view, selection, selection_count);
+ undo_update_menu (view);
+
nautilus_file_list_free (selection);
if (view->details->scripts_invalid) {
@@ -10822,3 +10985,4 @@ fm_directory_view_class_init (FMDirectoryViewClass *klass)
klass->trash = real_trash;
klass->delete = real_delete;
}
+
diff --git a/src/file-manager/nautilus-directory-view-ui.xml b/src/file-manager/nautilus-directory-view-ui.xml
index 80b9e88..4e926fa 100644
--- a/src/file-manager/nautilus-directory-view-ui.xml
+++ b/src/file-manager/nautilus-directory-view-ui.xml
@@ -58,6 +58,10 @@
<menuitem name="Copy" action="Copy"/>
<menuitem name="Paste" action="Paste"/>
</placeholder>
+ <placeholder name="Undostack Actions">
+ <menuitem name="Undo" action="Undo"/>
+ <menuitem name="Redo" action="Redo"/>
+ </placeholder>
<placeholder name="Select Items">
<menuitem name="Select All" action="Select All"/>
<menuitem name="Select Pattern" action="Select Pattern"/>
diff --git a/src/nautilus-shell-ui.xml b/src/nautilus-shell-ui.xml
index 2686beb..5aec5ce 100644
--- a/src/nautilus-shell-ui.xml
+++ b/src/nautilus-shell-ui.xml
@@ -21,6 +21,8 @@
<menuitem name="Close" action="Close"/>
</menu>
<menu action="Edit">
+ <placeholder name="Undostack Actions"/>
+ <separator/>
<placeholder name="Clipboard Actions">
</placeholder>
<separator/>
diff --git a/src/nautilus-window-menus.c b/src/nautilus-window-menus.c
index b8c0cc1..f911586 100644
--- a/src/nautilus-window-menus.c
+++ b/src/nautilus-window-menus.c
@@ -257,6 +257,7 @@ action_stop_callback (GtkAction *action,
nautilus_window_slot_stop_loading (slot);
}
+#ifdef TEXT_CHANGE_UNDO
static void
action_undo_callback (GtkAction *action,
gpointer user_data)
@@ -264,6 +265,7 @@ action_undo_callback (GtkAction *action,
nautilus_undo_manager_undo
(NAUTILUS_WINDOW (user_data)->application->undo_manager);
}
+#endif
static void
action_home_callback (GtkAction *action,
@@ -776,9 +778,11 @@ static const GtkActionEntry main_entries[] = {
N_("Prefere_nces"),
NULL, N_("Edit Nautilus preferences"),
G_CALLBACK (action_preferences_callback) },
+#ifdef TEXT_CHANGE_UNDO
/* name, stock id, label */ { "Undo", NULL, N_("_Undo"),
"<control>Z", N_("Undo the last text change"),
G_CALLBACK (action_undo_callback) },
+#endif
/* name, stock id, label */ { "Up", GTK_STOCK_GO_UP, N_("Open _Parent"),
"<alt>Up", N_("Open the parent folder"),
G_CALLBACK (action_up_callback) },
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]