[nautilus] undo: introduce undo support



commit dcf5d6796b5adc13a4b03d36f30d9f15025e9df9
Author: Amos Brocco <amos brocco gmail com>
Date:   Mon Jan 16 22:20:36 2012 -0500

    undo: introduce undo support

 libnautilus-private/Makefile.am                    |    5 +
 libnautilus-private/nautilus-debug.c               |    1 +
 libnautilus-private/nautilus-debug.h               |    1 +
 libnautilus-private/nautilus-file-operations.c     |  164 +++-
 libnautilus-private/nautilus-file-private.h        |    2 +
 libnautilus-private/nautilus-file-undo-manager.c   |  600 +++++++++
 libnautilus-private/nautilus-file-undo-manager.h   |   87 ++
 .../nautilus-file-undo-operations.c                | 1297 ++++++++++++++++++++
 .../nautilus-file-undo-operations.h                |   78 ++
 libnautilus-private/nautilus-file-undo-types.h     |  168 +++
 libnautilus-private/nautilus-file.c                |   62 +-
 src/nautilus-directory-view-ui.xml                 |    4 +
 src/nautilus-shell-ui.xml                          |    2 +
 src/nautilus-view.c                                |  125 ++-
 src/nautilus-window-menus.c                        |    4 +
 15 files changed, 2591 insertions(+), 9 deletions(-)
---
diff --git a/libnautilus-private/Makefile.am b/libnautilus-private/Makefile.am
index 6c8e015..1dda15e 100644
--- a/libnautilus-private/Makefile.am
+++ b/libnautilus-private/Makefile.am
@@ -188,6 +188,11 @@ libnautilus_private_la_SOURCES = \
 	nautilus-vfs-directory.h \
 	nautilus-vfs-file.c \
 	nautilus-vfs-file.h \
+	nautilus-file-undo-operations.c \
+	nautilus-file-undo-operations.h \
+	nautilus-file-undo-manager.c \
+	nautilus-file-undo-manager.h \
+	nautilus-file-undo-types.h \
 	$(nautilus_tracker_engine_sources) \
 	$(NULL)
 
diff --git a/libnautilus-private/nautilus-debug.c b/libnautilus-private/nautilus-debug.c
index f4ee2c7..4f874d8 100644
--- a/libnautilus-private/nautilus-debug.c
+++ b/libnautilus-private/nautilus-debug.c
@@ -49,6 +49,7 @@ static GDebugKey keys[] = {
   { "Previewer", NAUTILUS_DEBUG_PREVIEWER },
   { "Smclient", NAUTILUS_DEBUG_SMCLIENT },
   { "Window", NAUTILUS_DEBUG_WINDOW },
+  { "Undo", NAUTILUS_DEBUG_UNDO },
   { 0, }
 };
 
diff --git a/libnautilus-private/nautilus-debug.h b/libnautilus-private/nautilus-debug.h
index 56afff4..ea2e1d1 100644
--- a/libnautilus-private/nautilus-debug.h
+++ b/libnautilus-private/nautilus-debug.h
@@ -46,6 +46,7 @@ typedef enum {
   NAUTILUS_DEBUG_PREVIEWER = 1 << 11,
   NAUTILUS_DEBUG_SMCLIENT = 1 << 12,
   NAUTILUS_DEBUG_WINDOW = 1 << 13,
+  NAUTILUS_DEBUG_UNDO = 1 << 14,
 } DebugFlags;
 
 void nautilus_debug_set_flags (DebugFlags flags);
diff --git a/libnautilus-private/nautilus-file-operations.c b/libnautilus-private/nautilus-file-operations.c
index 55fca11..87b3554 100644
--- a/libnautilus-private/nautilus-file-operations.c
+++ b/libnautilus-private/nautilus-file-operations.c
@@ -63,6 +63,8 @@
 #include "nautilus-trash-monitor.h"
 #include "nautilus-file-utilities.h"
 #include "nautilus-file-conflict-dialog.h"
+#include "nautilus-file-undo-operations.h"
+#include "nautilus-file-undo-manager.h"
 
 /* TODO: TESTING!!! */
 
@@ -76,6 +78,7 @@ typedef struct {
 	GCancellable *cancellable;
 	GHashTable *skip_files;
 	GHashTable *skip_readdir_error;
+	NautilusFileUndoData *undo_redo_data;
 	gboolean skip_all_error;
 	gboolean skip_all_conflict;
 	gboolean merge_all;
@@ -953,6 +956,12 @@ finalize_common (CommonJob *common)
 	if (common->skip_readdir_error) {
 		g_hash_table_destroy (common->skip_readdir_error);
 	}
+
+	if (common->undo_redo_data != NULL) {
+		nautilus_file_undo_manager_add_action (nautilus_file_undo_manager_get (),
+							common->undo_redo_data);
+	}
+
 	g_object_unref (common->progress);
 	g_object_unref (common->cancellable);
 	g_free (common);
@@ -1257,7 +1266,10 @@ static void
 abort_job (CommonJob *job)
 {
 	g_cancellable_cancel (job->cancellable);
-	
+
+	/* destroy the undo action data too */
+	nautilus_file_undo_data_free (job->undo_redo_data);
+	job->undo_redo_data = NULL;
 }
 
 static gboolean
@@ -1752,6 +1764,8 @@ trash_files (CommonJob *job, GList *files, int *files_skipped)
 	char *primary, *secondary, *details;
 	int response;
 
+	guint64 mtime;
+
 	if (job_aborted (job)) {
 		return;
 	}
@@ -1768,6 +1782,9 @@ trash_files (CommonJob *job, GList *files, int *files_skipped)
 		file = l->data;
 
 		error = NULL;
+
+		mtime = nautilus_file_undo_manager_get_file_modification_time (file);
+
 		if (!g_file_trash (file, job->cancellable, &error)) {
 			if (job->skip_all_error) {
 				(*files_skipped)++;
@@ -1814,7 +1831,11 @@ trash_files (CommonJob *job, GList *files, int *files_skipped)
 			total_files--;
 		} else {
 			nautilus_file_changes_queue_file_removed (file);
-			
+
+			if (job->undo_redo_data != NULL) {
+				nautilus_file_undo_data_add_trashed_file (job->undo_redo_data, file, mtime);
+			}
+
 			files_trashed++;
 			report_trash_progress (job, files_trashed, total_files);
 		}
@@ -1958,6 +1979,10 @@ trash_or_delete_internal (GList                  *files,
 		inhibit_power_manager ((CommonJob *)job, _("Deleting Files"));
 	}
 	
+	if (try_trash && !nautilus_file_undo_manager_is_undo_redo (nautilus_file_undo_manager_get ())) {
+		job->common.undo_redo_data = nautilus_file_undo_data_new (NAUTILUS_FILE_UNDO_MOVE_TO_TRASH, g_list_length(files));
+	}
+
 	g_io_scheduler_push_job (delete_job,
 			   job,
 			   NULL,
@@ -3367,6 +3392,11 @@ create_dest_dir (CommonJob *job,
 		return CREATE_DEST_DIR_FAILED;
 	}
 	nautilus_file_changes_queue_file_added (*dest);
+
+	if (job->undo_redo_data != NULL) {
+		nautilus_file_undo_data_add_origin_target_pair (job->undo_redo_data, src, *dest);
+	}
+
 	return CREATE_DEST_DIR_SUCCESS;
 }
 
@@ -4121,7 +4151,11 @@ copy_move_file (CopyMoveJob *copy_job,
 						   dest,
 						   FALSE);
 		}
-			
+
+		if (job->undo_redo_data != NULL) {
+			nautilus_file_undo_data_add_origin_target_pair (job->undo_redo_data, src, dest);
+		}
+
 		g_object_unref (dest);
 		return;
 	}
@@ -4581,6 +4615,18 @@ nautilus_file_operations_copy (GList *files,
 
 	inhibit_power_manager ((CommonJob *)job, _("Copying Files"));
 
+	if (!nautilus_file_undo_manager_is_undo_redo (nautilus_file_undo_manager_get ())) {
+		GFile* src_dir;
+
+		job->common.undo_redo_data = nautilus_file_undo_data_new (NAUTILUS_FILE_UNDO_COPY, g_list_length(files));
+		src_dir = g_file_get_parent (files->data);
+
+		nautilus_file_undo_data_set_src_dir (job->common.undo_redo_data, src_dir);
+		nautilus_file_undo_data_set_dest_dir (job->common.undo_redo_data, target_dir);
+
+		g_object_unref (src_dir);
+	}
+
 	g_io_scheduler_push_job (copy_job,
 			   job,
 			   NULL, /* destroy notify */
@@ -4737,7 +4783,11 @@ move_file_prepare (CopyMoveJob *move_job,
 		} else {
 			nautilus_file_changes_queue_schedule_position_remove (dest);
 		}
-		
+
+		if (job->undo_redo_data != NULL) {
+			nautilus_file_undo_data_add_origin_target_pair (job->undo_redo_data, src, dest);
+		}
+
 		return;
 	}
 
@@ -5098,6 +5148,23 @@ nautilus_file_operations_move (GList *files,
 
 	inhibit_power_manager ((CommonJob *)job, _("Moving Files"));
 
+	if (!nautilus_file_undo_manager_is_undo_redo (nautilus_file_undo_manager_get ())) {
+		GFile* src_dir;
+
+		if (g_file_has_uri_scheme (g_list_first(files)->data, "trash")) {
+			job->common.undo_redo_data = nautilus_file_undo_data_new (NAUTILUS_FILE_UNDO_RESTORE_FROM_TRASH, g_list_length(files));
+		} else {
+			job->common.undo_redo_data = nautilus_file_undo_data_new (NAUTILUS_FILE_UNDO_MOVE, g_list_length(files));
+		}
+
+		src_dir = g_file_get_parent (files->data);
+
+		nautilus_file_undo_data_set_src_dir (job->common.undo_redo_data, src_dir);
+		nautilus_file_undo_data_set_dest_dir (job->common.undo_redo_data, target_dir);
+
+		g_object_unref (src_dir);
+	}
+
 	g_io_scheduler_push_job (move_job,
 				 job,
 				 NULL, /* destroy notify */
@@ -5191,6 +5258,11 @@ link_file (CopyMoveJob *job,
 					      path, 
 					      common->cancellable,
 					      &error)) {
+
+		if (common->undo_redo_data != NULL) {
+			nautilus_file_undo_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));
@@ -5396,6 +5468,18 @@ 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_file_undo_manager_is_undo_redo (nautilus_file_undo_manager_get ())) {
+		GFile* src_dir;
+
+		job->common.undo_redo_data = nautilus_file_undo_data_new (NAUTILUS_FILE_UNDO_CREATE_LINK, g_list_length(files));
+		src_dir = g_file_get_parent (files->data);
+
+		nautilus_file_undo_data_set_src_dir (job->common.undo_redo_data, src_dir);
+		nautilus_file_undo_data_set_dest_dir (job->common.undo_redo_data, target_dir);
+
+		g_object_unref (src_dir);
+	}
+
 	g_io_scheduler_push_job (link_job,
 			   job,
 			   NULL, /* destroy notify */
@@ -5427,6 +5511,19 @@ 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_file_undo_manager_is_undo_redo (nautilus_file_undo_manager_get ())) {
+		GFile* src_dir;
+
+		job->common.undo_redo_data =
+			nautilus_file_undo_data_new (NAUTILUS_FILE_UNDO_DUPLICATE, g_list_length(files));
+		src_dir = g_file_get_parent (files->data);
+
+		nautilus_file_undo_data_set_src_dir (job->common.undo_redo_data, src_dir);
+		nautilus_file_undo_data_set_dest_dir (job->common.undo_redo_data, src_dir);
+
+		g_object_unref (src_dir);
+	}
+
 	g_io_scheduler_push_job (copy_job,
 			   job,
 			   NULL, /* destroy notify */
@@ -5496,6 +5593,11 @@ 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);
+
+		if (common->undo_redo_data != NULL) {
+			nautilus_file_undo_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,
@@ -5578,7 +5680,16 @@ 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_file_undo_manager_is_undo_redo (nautilus_file_undo_manager_get ())) {
+		job->common.undo_redo_data = nautilus_file_undo_data_new (NAUTILUS_FILE_UNDO_RECURSIVE_SET_PERMISSIONS, 1);
+
+		nautilus_file_undo_data_set_recursive_permissions_dest_dir (job->common.undo_redo_data, job->file);
+		nautilus_file_undo_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,
@@ -5669,6 +5780,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,
@@ -5691,11 +5803,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,
@@ -5703,6 +5817,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,
@@ -5829,6 +5944,13 @@ create_job (GIOSchedulerJob *io_job,
 		res = g_file_make_directory (dest,
 					     common->cancellable,
 					     &error);
+
+		if (res && common->undo_redo_data != NULL) {
+			nautilus_file_undo_data_set_create_data (common->undo_redo_data,
+									 dest,
+									 NULL);
+		}
+
 	} else {
 		if (job->src) {
 			res = g_file_copy (job->src,
@@ -5837,6 +5959,17 @@ create_job (GIOSchedulerJob *io_job,
 					   common->cancellable,
 					   NULL, NULL,
 					   &error);
+
+			if (res && common->undo_redo_data != NULL) {
+				gchar *uri;
+
+				uri = g_file_get_uri (job->src);
+				nautilus_file_undo_data_set_create_data (common->undo_redo_data,
+										 dest, uri);
+
+				g_free (uri);
+			}
+
 		} else {
 			data = "";
 			length = 0;
@@ -5859,6 +5992,11 @@ create_job (GIOSchedulerJob *io_job,
 					res = g_output_stream_close (G_OUTPUT_STREAM (out),
 								     common->cancellable,
 								     &error);
+
+					if (res && common->undo_redo_data != NULL) {
+						nautilus_file_undo_data_set_create_data (common->undo_redo_data,
+												 dest, data);
+					}
 				}
 
 				/* This will close if the write failed and we didn't close */
@@ -6029,6 +6167,10 @@ nautilus_file_operations_new_folder (GtkWidget *parent_view,
 		job->has_position = TRUE;
 	}
 
+	if (!nautilus_file_undo_manager_is_undo_redo (nautilus_file_undo_manager_get ())) {
+		job->common.undo_redo_data = nautilus_file_undo_data_new (NAUTILUS_FILE_UNDO_CREATE_FOLDER, 1);
+	}
+
 	g_io_scheduler_push_job (create_job,
 			   job,
 			   NULL, /* destroy notify */
@@ -6067,6 +6209,10 @@ nautilus_file_operations_new_file_from_template (GtkWidget *parent_view,
 		job->src = g_file_new_for_uri (template_uri);
 	}
 
+	if (!nautilus_file_undo_manager_is_undo_redo (nautilus_file_undo_manager_get ())) {
+		job->common.undo_redo_data = nautilus_file_undo_data_new (NAUTILUS_FILE_UNDO_CREATE_FILE_FROM_TEMPLATE, 1);
+	}
+
 	g_io_scheduler_push_job (create_job,
 			   job,
 			   NULL, /* destroy notify */
@@ -6104,6 +6250,10 @@ nautilus_file_operations_new_file (GtkWidget *parent_view,
 	job->length = length;
 	job->filename = g_strdup (target_filename);
 
+	if (!nautilus_file_undo_manager_is_undo_redo (nautilus_file_undo_manager_get ())) {
+		job->common.undo_redo_data = nautilus_file_undo_data_new (NAUTILUS_FILE_UNDO_CREATE_EMPTY_FILE, length);
+	}
+
 	g_io_scheduler_push_job (create_job,
 			   job,
 			   NULL, /* destroy notify */
@@ -6148,7 +6298,7 @@ delete_trash_file (CommonJob *job,
 			g_object_unref (enumerator);
 		}
 	}
-	
+
 	if (!job_aborted (job) && del_file) {
 		g_file_delete (file, job->cancellable, NULL);
 	}
@@ -6166,6 +6316,8 @@ empty_trash_job_done (gpointer user_data)
 	if (job->done_callback) {
 		job->done_callback (job->done_callback_data);
 	}
+
+	nautilus_file_undo_manager_trash_has_emptied (nautilus_file_undo_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 cc14b3e..f06df02 100644
--- a/libnautilus-private/nautilus-file-private.h
+++ b/libnautilus-private/nautilus-file-private.h
@@ -28,6 +28,7 @@
 #include <libnautilus-private/nautilus-directory.h>
 #include <libnautilus-private/nautilus-file.h>
 #include <libnautilus-private/nautilus-monitor.h>
+#include <libnautilus-private/nautilus-file-undo-types.h>
 #include <eel/eel-glib-extensions.h>
 #include <eel/eel-string.h>
 
@@ -230,6 +231,7 @@ typedef struct {
 	
 	gpointer data;
 	GDestroyNotify free_data;
+	NautilusFileUndoData *undo_redo_data;
 } NautilusFileOperation;
 
 NautilusFile *nautilus_file_new_from_info                  (NautilusDirectory      *directory,
diff --git a/libnautilus-private/nautilus-file-undo-manager.c b/libnautilus-private/nautilus-file-undo-manager.c
new file mode 100644
index 0000000..81a451d
--- /dev/null
+++ b/libnautilus-private/nautilus-file-undo-manager.c
@@ -0,0 +1,600 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* nautilus-file-undo-manager.c - Manages the undo/redo stack
+ *
+ * Copyright (C) 2007-2011 Amos Brocco
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * 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.
+ *
+ * Authors: Amos Brocco <amos brocco gmail com>
+ *          Cosimo Cecchi <cosimoc redhat com>
+ *
+ */
+
+#include <config.h>
+
+#include "nautilus-file-undo-manager.h"
+
+#include "nautilus-file-operations.h"
+#include "nautilus-file.h"
+#include "nautilus-file-undo-operations.h"
+#include "nautilus-file-undo-types.h"
+
+#include <gio/gio.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <locale.h>
+#include <gdk/gdk.h>
+#include <eel/eel-glib-extensions.h>
+
+#define DEBUG_FLAG NAUTILUS_DEBUG_UNDO
+#include "nautilus-debug.h"
+
+/* Default depth of the undo/redo stack. */
+#define DEFAULT_UNDO_DEPTH 32
+
+#define NAUTILUS_FILE_UNDO_MANAGER_GET_PRIVATE(o)	\
+	 (G_TYPE_INSTANCE_GET_PRIVATE ((o), NAUTILUS_TYPE_FILE_UNDO_MANAGER, NautilusFileUndoManagerPrivate))
+
+enum {
+	SIGNAL_UNDO_CHANGED,
+	NUM_SIGNALS,
+};
+
+static guint signals[NUM_SIGNALS] = { 0, };
+
+G_DEFINE_TYPE (NautilusFileUndoManager, nautilus_file_undo_manager, G_TYPE_OBJECT)
+
+static NautilusFileUndoManager *singleton = NULL;
+
+/* must be called with the mutex locked */
+static NautilusFileUndoData *
+get_next_redo_action (NautilusFileUndoManagerPrivate *priv)
+{
+	NautilusFileUndoData *retval = NULL;
+
+	if (g_queue_is_empty (priv->stack)) {
+		DEBUG ("Queue is empty, no redo actions to return");
+		goto out;
+	}
+
+	if (priv->index == 0) {
+		DEBUG ("Queue has only undoable actions, no redo actions to return");
+		goto out;
+	}
+
+	DEBUG ("Getting redo action for index %d", priv->index);
+	retval = g_queue_peek_nth (priv->stack, priv->index - 1);
+
+	if (retval && retval->locked) {
+		DEBUG ("Action is locked, no redo actions to return");
+		retval = NULL;
+	}
+
+ out:
+	DEBUG ("Returning %p as next redo action", retval);
+	return retval;
+}
+
+/* must be called with the mutex locked */
+static NautilusFileUndoData *
+get_next_undo_action (NautilusFileUndoManagerPrivate *priv)
+{
+	NautilusFileUndoData *retval = NULL;
+	guint stack_size;
+
+	if (g_queue_is_empty (priv->stack)) {
+		DEBUG ("Queue is empty, no undo actions to return");
+		goto out;
+	}
+
+	stack_size = g_queue_get_length (priv->stack);
+
+	if (priv->index == stack_size) {
+		DEBUG ("Queue has only of undone actions, no undo actions to return");
+		goto out;
+	}
+
+	DEBUG ("Getting undo action for index %d", priv->index);
+	retval = g_queue_peek_nth (priv->stack, priv->index);
+
+	if (retval->locked) {
+		DEBUG ("Action is locked, no undo actions to return");
+		retval = NULL;
+	}
+
+ out:
+	DEBUG ("Returning %p as next undo action", retval);
+	return retval;
+}
+
+/* must be called with the mutex locked */
+static void
+clear_redo_actions (NautilusFileUndoManagerPrivate *priv)
+{
+	DEBUG ("Clearing redo actions, index is %d", priv->index);
+
+	while (priv->index > 0) {
+		NautilusFileUndoData *head;
+
+		head = g_queue_pop_head (priv->stack);
+		nautilus_file_undo_data_free (head);
+
+		DEBUG ("Removed action, index was %d", priv->index);
+
+		priv->index--;
+	}
+}
+
+/* must be called with the mutex locked */
+static gboolean
+can_undo (NautilusFileUndoManagerPrivate *priv)
+{
+	return (get_next_undo_action (priv) != NULL);
+}
+
+/* must be called with the mutex locked */
+static gboolean
+can_redo (NautilusFileUndoManagerPrivate *priv)
+{
+	return (get_next_redo_action (priv) != NULL);
+}
+
+/* must be called with the mutex locked */
+static NautilusFileUndoData *
+stack_scroll_right (NautilusFileUndoManagerPrivate *priv)
+{
+	NautilusFileUndoData *data;
+
+	if (!can_undo (priv)) {
+		DEBUG ("Scrolling stack right, but no undo actions");
+		return NULL;
+	}
+
+	data = g_queue_peek_nth (priv->stack, priv->index);
+
+	if (priv->index < g_queue_get_length (priv->stack)) {
+		priv->index++;
+		DEBUG ("Scrolling stack right, increasing index to %d", priv->index);
+	}
+
+	return data;
+}
+
+/* must be called with the mutex locked */
+static NautilusFileUndoData *
+stack_scroll_left (NautilusFileUndoManagerPrivate *priv)
+{
+	NautilusFileUndoData *data;
+
+	if (!can_redo (priv)) {
+		DEBUG ("Scrolling stack left, but no redo actions");
+		return NULL;
+	}
+
+	priv->index--;
+	data = g_queue_peek_nth (priv->stack, priv->index);
+
+	DEBUG ("Scrolling stack left, decreasing index to %d", priv->index);
+
+	return data;
+}
+
+/* must be called with the mutex locked */
+static void
+stack_clear_n_oldest (GQueue *stack,
+		      guint n)
+{
+	NautilusFileUndoData *action;
+	guint i;
+
+	DEBUG ("Clearing %u oldest actions from the undo stack", n);
+
+	for (i = 0; i < n; i++) {
+		action = (NautilusFileUndoData *) g_queue_pop_tail (stack);
+		if (action->locked) {
+			action->freed = TRUE;
+		} else {
+			nautilus_file_undo_data_free (action);
+		}
+	}
+}
+
+/* must be called with the mutex locked */
+static void
+stack_ensure_size (NautilusFileUndoManager *self)
+{
+	NautilusFileUndoManagerPrivate *priv = self->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));
+	}
+}
+
+/* must be called with the mutex locked */
+static void
+stack_push_action (NautilusFileUndoManager *self,
+		   NautilusFileUndoData *action)
+{
+	NautilusFileUndoManagerPrivate *priv = self->priv;
+
+	/* clear all the redo items, as we're pushing a new undoable action */
+	clear_redo_actions (priv);
+
+	g_queue_push_head (priv->stack, action);
+
+	/* cleanup items in excess, if any */
+	stack_ensure_size (self);
+}
+
+static void
+nautilus_file_undo_manager_init (NautilusFileUndoManager * self)
+{
+	NautilusFileUndoManagerPrivate *priv;
+
+	priv = NAUTILUS_FILE_UNDO_MANAGER_GET_PRIVATE (self);
+
+	self->priv = priv;
+
+	/* Initialize private fields */
+	priv->stack = g_queue_new ();
+	g_mutex_init(&priv->mutex);
+	priv->index = 0;
+	priv->undo_redo_flag = FALSE;
+
+	/* initialize the undo stack depth */
+	priv->undo_levels = DEFAULT_UNDO_DEPTH;
+
+	g_mutex_lock (&priv->mutex);
+	stack_ensure_size (self);
+	g_mutex_unlock (&priv->mutex);
+}
+
+static void
+nautilus_file_undo_manager_finalize (GObject * object)
+{
+	NautilusFileUndoManager *self = NAUTILUS_FILE_UNDO_MANAGER (object);
+	NautilusFileUndoManagerPrivate *priv = self->priv;
+
+	g_mutex_lock (&priv->mutex);
+	
+	g_queue_foreach (priv->stack, (GFunc) nautilus_file_undo_data_free, NULL);
+	g_queue_free (priv->stack);
+
+	g_mutex_unlock (&priv->mutex);
+	g_mutex_clear (&priv->mutex);
+
+	G_OBJECT_CLASS (nautilus_file_undo_manager_parent_class)->finalize (object);
+}
+
+static GObject *
+nautilus_file_undo_manager_constructor (GType type,
+					 guint n_construct_params,
+					 GObjectConstructParam *construct_params)
+{
+	GObject *retval;
+
+	if (singleton != NULL) {
+		return G_OBJECT (singleton);
+	}
+
+	retval = G_OBJECT_CLASS (nautilus_file_undo_manager_parent_class)->constructor
+		(type, n_construct_params, construct_params);
+
+	singleton = NAUTILUS_FILE_UNDO_MANAGER (retval);
+	g_object_add_weak_pointer (retval, (gpointer) &singleton);
+
+	return retval;
+}
+
+static void
+nautilus_file_undo_manager_class_init (NautilusFileUndoManagerClass *klass)
+{
+	GObjectClass *oclass;
+
+	oclass = G_OBJECT_CLASS (klass);
+
+	oclass->constructor = nautilus_file_undo_manager_constructor;
+	oclass->finalize = nautilus_file_undo_manager_finalize;
+
+	signals[SIGNAL_UNDO_CHANGED] =
+		g_signal_new ("undo-changed",
+			      G_TYPE_FROM_CLASS (klass),
+			      G_SIGNAL_RUN_LAST,
+			      0, NULL, NULL,
+			      g_cclosure_marshal_VOID__VOID,
+			      G_TYPE_NONE, 0);
+
+	g_type_class_add_private (klass, sizeof (NautilusFileUndoManagerPrivate));
+}
+
+void
+nautilus_file_undo_manager_trash_has_emptied (NautilusFileUndoManager *manager)
+{
+	NautilusFileUndoManagerPrivate *priv;
+	NautilusFileUndoData *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 = (NautilusFileUndoData *)g_queue_peek_nth (priv->stack, i);
+		if (action->type == NAUTILUS_FILE_UNDO_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);
+}
+
+guint64
+nautilus_file_undo_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;
+}
+
+static void
+refresh_strings (NautilusFileUndoData *action)
+{
+	gchar **descriptions;
+	gchar **labels;
+
+	descriptions = (gchar **) g_malloc0 (3 * sizeof (gchar *));
+	descriptions[2] = NULL;
+
+	labels = (gchar **) g_malloc0 (3 * sizeof (gchar *));
+	labels[2] = NULL;
+
+	action->strings_func (action, action->count,
+			      labels, descriptions);
+
+	action->undo_label = labels[0];
+	action->redo_label = labels[1];
+
+	action->undo_description = descriptions[0];
+	action->redo_description = descriptions[1];
+
+	g_free (descriptions);
+	g_free (labels);
+}
+
+static const gchar *
+get_redo_label (NautilusFileUndoData *action)
+{
+	if (action->redo_label == NULL) {
+		refresh_strings (action);
+	}
+
+	return action->redo_label;
+}
+
+static const gchar *
+get_undo_label (NautilusFileUndoData *action)
+{
+	if (action->undo_label == NULL) {
+		refresh_strings (action);
+	}
+
+	return action->undo_label;
+}
+
+static const char *
+get_undo_description (NautilusFileUndoData *action)
+{
+	if (action->undo_description == NULL) {
+		refresh_strings (action);
+	}
+
+	return action->undo_description;
+}
+
+static const char *
+get_redo_description (NautilusFileUndoData *action)
+{
+	if (action->redo_description == NULL) {
+		refresh_strings (action);
+	}
+
+	return action->redo_description;
+}
+
+static void
+do_undo_redo (NautilusFileUndoManager *self,
+	      gboolean undo,
+	      NautilusFileUndoFinishCallback callback,
+	      gpointer user_data)
+{
+	NautilusFileUndoManagerPrivate *priv = self->priv;
+	NautilusFileUndoData *action;
+
+	/* Update the menus invalidating undo/redo while an operation is already underway */
+	g_mutex_lock (&priv->mutex);
+
+	if (undo) {
+		action = stack_scroll_right (priv);
+	} else {
+		action = stack_scroll_left (priv);
+	}
+
+	/* Action will be NULL if redo is not possible */
+	if (action != NULL) {
+		action->locked = TRUE;  /* Remember to unlock when finished */
+	}
+
+	g_mutex_unlock (&priv->mutex);
+
+	g_signal_emit (self, signals[SIGNAL_UNDO_CHANGED], 0);
+
+	if (action != NULL) {
+		priv->undo_redo_flag = TRUE;
+
+		if (undo) {
+			action->undo_func (action);
+		} else {
+			action->redo_func (action);
+		}
+	}
+
+	if (callback != NULL) {
+		callback (user_data);
+	}
+}
+
+void
+nautilus_file_undo_manager_redo (NautilusFileUndoManager        *manager,
+                                  NautilusFileUndoFinishCallback  callback,
+                                  gpointer                         user_data)
+{
+	do_undo_redo (manager, FALSE, callback, user_data);
+}
+
+void
+nautilus_file_undo_manager_undo (NautilusFileUndoManager        *manager,
+				  NautilusFileUndoFinishCallback  callback,
+				  gpointer                         user_data)
+{
+	do_undo_redo (manager, TRUE, callback, user_data);
+}
+
+void
+nautilus_file_undo_manager_add_action (NautilusFileUndoManager    *self,
+                                        NautilusFileUndoData *action)
+{
+	NautilusFileUndoManagerPrivate *priv;
+
+	priv = self->priv;
+
+	DEBUG ("Adding action %p, type %d", action, action->type);
+
+	if (!(action && action->is_valid)) {
+		DEBUG ("Action %p is not valid, ignoring", action);
+		nautilus_file_undo_data_free (action);
+		return;
+	}
+
+	action->manager = self;
+
+	g_mutex_lock (&priv->mutex);
+	stack_push_action (self, action);
+	g_mutex_unlock (&priv->mutex);
+
+	g_signal_emit (self, signals[SIGNAL_UNDO_CHANGED], 0);
+}
+
+gboolean
+nautilus_file_undo_manager_is_undo_redo (NautilusFileUndoManager *manager)
+{
+	NautilusFileUndoManagerPrivate *priv;
+
+	priv = manager->priv;
+
+	if (priv->undo_redo_flag) {
+		priv->undo_redo_flag = FALSE;
+		return TRUE;
+	}
+
+	return FALSE;
+}
+
+NautilusFileUndoManager *
+nautilus_file_undo_manager_get (void)
+{
+	NautilusFileUndoManager *manager;
+
+	manager = g_object_new (NAUTILUS_TYPE_FILE_UNDO_MANAGER,
+				NULL);
+
+	return manager;
+}
+
+NautilusFileUndoMenuData *
+nautilus_file_undo_manager_get_menu_data (NautilusFileUndoManager *self)
+{
+	NautilusFileUndoData *action;
+	NautilusFileUndoManagerPrivate *priv;
+	NautilusFileUndoMenuData *data;
+
+	priv = self->priv;
+	data = g_slice_new0 (NautilusFileUndoMenuData);
+
+	DEBUG ("Getting menu data");
+
+	g_mutex_lock (&priv->mutex);
+
+	action = get_next_undo_action (priv);
+
+	if (action != NULL) {
+		data->undo_label = get_undo_label (action);
+		data->undo_description = get_undo_description (action);
+	}
+
+	action = get_next_redo_action (priv);
+
+	if (action != NULL) {
+		data->redo_label = get_redo_label (action);
+		data->redo_description = get_redo_description (action);
+	}
+
+	g_mutex_unlock (&priv->mutex);
+
+	return data;
+}
+
+void
+nautilus_file_undo_menu_data_free (NautilusFileUndoMenuData *data)
+{
+	g_slice_free (NautilusFileUndoMenuData, data);
+}
diff --git a/libnautilus-private/nautilus-file-undo-manager.h b/libnautilus-private/nautilus-file-undo-manager.h
new file mode 100644
index 0000000..f6a63a9
--- /dev/null
+++ b/libnautilus-private/nautilus-file-undo-manager.h
@@ -0,0 +1,87 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* nautilus-file-undo-manager.h - Manages the undo/redo stack
+ *
+ * Copyright (C) 2007-2011 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 gmail com>
+ */
+
+#ifndef __NAUTILUS_FILE_UNDO_MANAGER_H__
+#define __NAUTILUS_FILE_UNDO_MANAGER_H__
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+#include <gio/gio.h>
+
+#include <libnautilus-private/nautilus-file-undo-types.h>
+
+typedef struct _NautilusFileUndoManagerClass NautilusFileUndoManagerClass;
+typedef struct _NautilusFileUndoManagerPrivate NautilusFileUndoManagerPrivate;
+
+#define NAUTILUS_TYPE_FILE_UNDO_MANAGER\
+	(nautilus_file_undo_manager_get_type())
+#define NAUTILUS_FILE_UNDO_MANAGER(object)\
+	(G_TYPE_CHECK_INSTANCE_CAST((object), NAUTILUS_TYPE_FILE_UNDO_MANAGER,\
+				    NautilusFileUndoManager))
+#define NAUTILUS_FILE_UNDO_MANAGER_CLASS(klass)\
+	(G_TYPE_CHECK_CLASS_CAST((klass), NAUTILUS_TYPE_FILE_UNDO_MANAGER,\
+				 NautilusFileUndoManagerClass))
+#define NAUTILUS_IS_FILE_UNDO_MANAGER(object)\
+	(G_TYPE_CHECK_INSTANCE_TYPE((object), NAUTILUS_TYPE_FILE_UNDO_MANAGER))
+#define NAUTILUS_IS_FILE_UNDO_MANAGER_CLASS(klass)\
+	(G_TYPE_CHECK_CLASS_TYPE((klass), NAUTILUS_TYPE_FILE_UNDO_MANAGER))
+#define NAUTILUS_FILE_UNDO_MANAGER_GET_CLASS(object)\
+	(G_TYPE_INSTANCE_GET_CLASS((object), NAUTILUS_TYPE_FILE_UNDO_MANAGER,\
+				   NautilusFileUndoManagerClass))
+
+struct _NautilusFileUndoManager {
+	GObject parent_instance;
+
+	/* < private > */
+	NautilusFileUndoManagerPrivate* priv;
+};
+
+struct _NautilusFileUndoManagerClass {
+	GObjectClass parent_class;
+};
+
+
+GType nautilus_file_undo_manager_get_type (void) G_GNUC_CONST;
+
+NautilusFileUndoManager * nautilus_file_undo_manager_get (void);
+
+void nautilus_file_undo_manager_add_action (NautilusFileUndoManager *manager,
+					     NautilusFileUndoData *action);
+void nautilus_file_undo_manager_undo (NautilusFileUndoManager *manager,
+				       NautilusFileUndoFinishCallback callback,
+				       gpointer user_data);
+void nautilus_file_undo_manager_redo (NautilusFileUndoManager *manager, 
+				       NautilusFileUndoFinishCallback callback,
+				       gpointer user_data);
+
+gboolean nautilus_file_undo_manager_is_undo_redo (NautilusFileUndoManager *manager);
+void nautilus_file_undo_manager_trash_has_emptied (NautilusFileUndoManager *manager);
+void nautilus_file_undo_manager_request_menu_update (NautilusFileUndoManager        *manager);
+guint64 nautilus_file_undo_manager_get_file_modification_time (GFile *file);
+
+NautilusFileUndoMenuData * nautilus_file_undo_manager_get_menu_data (NautilusFileUndoManager *self);
+void nautilus_file_undo_menu_data_free (NautilusFileUndoMenuData *data);
+
+#endif /* __NAUTILUS_FILE_UNDO_MANAGER_H__ */
diff --git a/libnautilus-private/nautilus-file-undo-operations.c b/libnautilus-private/nautilus-file-undo-operations.c
new file mode 100644
index 0000000..f2d61c9
--- /dev/null
+++ b/libnautilus-private/nautilus-file-undo-operations.c
@@ -0,0 +1,1297 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* nautilus-file-undo-operations.c - Manages undo/redo of file operations
+ *
+ * Copyright (C) 2007-2011 Amos Brocco
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * 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.
+ *
+ * Authors: Amos Brocco <amos brocco gmail com>
+ *          Cosimo Cecchi <cosimoc redhat com>
+ *
+ */
+
+#include <config.h>
+
+#include "nautilus-file-undo-operations.h"
+
+#include <glib/gi18n.h>
+
+#include "nautilus-file-operations.h"
+#include "nautilus-file.h"
+#include "nautilus-file-undo-manager.h"
+#include "nautilus-file-undo-types.h"
+
+void
+nautilus_file_undo_data_free (NautilusFileUndoData *action)
+{
+	g_return_if_fail (action != NULL);
+	
+	g_free (action->undo_label);
+	g_free (action->undo_description);
+	g_free (action->redo_label);
+	g_free (action->redo_description);
+	
+	action->finalize_func (action);
+}
+
+static char *
+get_first_target_short_name (NautilusFileUndoDataExt *eaction)
+{
+	GList *targets_first;
+	char *file_name = NULL;
+
+	targets_first = g_list_first (eaction->destinations);
+
+	if (targets_first != NULL &&
+	    targets_first->data != NULL) {
+		file_name = g_file_get_basename (targets_first->data);
+	}
+
+	return file_name;
+}
+
+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;
+}
+
+static void
+undo_redo_done_transfer_callback (GHashTable * debuting_uris,
+                                  gpointer data)
+{
+	NautilusFileUndoData *action = data;
+	NautilusFileUndoManager *manager = action->manager;
+
+	/* If the action needed to be freed but was locked, free now */
+	if (action->freed) {
+		nautilus_file_undo_data_free (action);
+	} else {
+		action->locked = FALSE;
+	}
+
+	/* Update menus */
+	g_signal_emit_by_name (manager, "undo-changed", 0);
+}
+
+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
+undo_redo_done_create_callback (GFile * new_file,
+				gpointer callback_data)
+{
+	undo_redo_done_transfer_callback (NULL, callback_data);
+}
+
+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_op_callback (gpointer callback_data)
+{
+	undo_redo_done_transfer_callback (NULL, callback_data);
+}
+
+/* undo helpers */
+
+static void
+delete_files (NautilusFileUndoManager *self,
+	      GList *files,
+	      NautilusFileUndoData *action)
+{
+	nautilus_file_operations_delete (files, NULL,
+					 undo_redo_done_delete_callback, action);
+}
+
+static void
+create_undo_func (NautilusFileUndoData *action)
+{
+	GList *files = NULL;
+	NautilusFileUndoDataCreate* eaction = (NautilusFileUndoDataCreate*) action;
+
+	files = g_list_append (files, g_object_ref (eaction->target_file));
+	delete_files (action->manager, files, action);
+
+	g_list_free_full (files, g_object_unref);
+}
+
+static void
+copy_or_link_undo_func (NautilusFileUndoData *action)
+{
+	GList *files;
+	NautilusFileUndoDataExt* eaction = (NautilusFileUndoDataExt*) action;
+
+	files = g_list_copy (eaction->destinations);
+	files = g_list_reverse (files); /* Deleting must be done in reverse */
+
+	delete_files (action->manager, files, action);
+
+	g_list_free (files);
+}
+
+static void
+restore_undo_func (NautilusFileUndoData *action)
+{
+	NautilusFileUndoDataExt* eaction = (NautilusFileUndoDataExt*) action;
+	nautilus_file_operations_trash_or_delete (eaction->destinations, NULL,
+						  undo_redo_done_delete_callback, action);
+}
+
+static void
+trash_undo_func (NautilusFileUndoData *action)
+{
+	NautilusFileUndoManagerPrivate *priv = action->manager->priv;
+	GHashTable *files_to_restore;
+	NautilusFileUndoDataTrash* eaction = (NautilusFileUndoDataTrash*) action;
+
+	/* Internally managed op, clear the undo_redo_flag.
+	 * Same as calling nautilus_file_undo_manager_is_undo_redo()
+	 * minus the function call and unused return val.
+	 */
+
+	priv->undo_redo_flag = FALSE;
+	files_to_restore = retrieve_files_to_restore (eaction->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);
+}
+
+static void
+move_undo_func (NautilusFileUndoData *action)
+{
+	NautilusFileUndoDataExt* eaction = (NautilusFileUndoDataExt*) action;
+	nautilus_file_operations_move (eaction->destinations, NULL,
+				       eaction->src_dir, NULL,
+				       undo_redo_done_transfer_callback, action);
+}
+
+static void
+rename_undo_func (NautilusFileUndoData *action)
+{
+	gchar *new_name;
+	NautilusFile *file;
+	NautilusFileUndoDataRename* eaction = (NautilusFileUndoDataRename*) action;
+
+	new_name = g_file_get_basename (eaction->old_file);
+	file = nautilus_file_get (eaction->new_file);
+
+	nautilus_file_rename (file, new_name,
+			      undo_redo_done_rename_callback, action);
+
+	nautilus_file_unref (file);
+	g_free (new_name);
+}
+
+static void
+set_permissions_undo_func (NautilusFileUndoData *action)
+{
+	NautilusFile *file;
+	NautilusFileUndoDataPermissions* eaction = (NautilusFileUndoDataPermissions*) action;
+
+	file = nautilus_file_get (eaction->target_file);
+
+	nautilus_file_set_permissions (file,
+				       eaction->current_permissions,
+				       undo_redo_done_rename_callback, action);
+
+	nautilus_file_unref (file);
+}
+
+static void
+recursive_permissions_undo_func (NautilusFileUndoData *action)
+{
+	NautilusFileUndoManagerPrivate *priv = action->manager->priv;
+	NautilusFileUndoDataRecursivePermissions* eaction = (NautilusFileUndoDataRecursivePermissions*) action;
+
+	/* Internally managed op, clear the undo_redo_flag. */
+	priv->undo_redo_flag = FALSE;
+
+	if (g_hash_table_size (eaction->original_permissions) > 0) {
+		GList *gfiles_list;
+		guint32 *perm;
+		GList *l;
+		GFile *dest;
+		char *item;
+
+		gfiles_list = g_hash_table_get_keys (eaction->original_permissions);
+		for (l = gfiles_list; l != NULL; l = l->next) {
+			item = l->data;
+			perm = g_hash_table_lookup (eaction->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);
+	}
+}
+
+static void
+change_group_undo_func (NautilusFileUndoData *action)
+{
+	NautilusFile *file;
+	NautilusFileUndoDataOwnership* eaction = (NautilusFileUndoDataOwnership*) action;
+
+	file = nautilus_file_get (eaction->target_file);
+
+	nautilus_file_set_group (file,
+				 eaction->original_ownership,
+				 undo_redo_done_rename_callback, action);
+
+	nautilus_file_unref (file);
+}
+
+static void
+change_owner_undo_func (NautilusFileUndoData *action)
+{
+	NautilusFile *file;
+	NautilusFileUndoDataOwnership* eaction = (NautilusFileUndoDataOwnership*) action;
+
+	file = nautilus_file_get (eaction->target_file);
+
+	nautilus_file_set_owner (file,
+				 eaction->original_ownership,
+				 undo_redo_done_rename_callback, action);
+
+	nautilus_file_unref (file);
+}
+
+/* redo helpers */
+
+static void
+copy_redo_func (NautilusFileUndoData *action)
+{
+	GList *locations;
+	NautilusFileUndoDataExt* eaction = (NautilusFileUndoDataExt*) action;
+
+	locations = eaction->sources;
+	nautilus_file_operations_copy (locations, NULL,
+				       eaction->dest_dir, NULL,
+				       undo_redo_done_transfer_callback, action);
+}
+
+static void
+create_from_template_redo_func (NautilusFileUndoData *action)
+{
+	GFile *parent;
+	gchar *parent_uri, *new_name;
+	NautilusFileUndoDataCreate* eaction = (NautilusFileUndoDataCreate*) action;
+
+	parent = g_file_get_parent (eaction->target_file);
+	parent_uri = g_file_get_uri (parent);
+	new_name = g_file_get_parse_name (eaction->target_file);
+	nautilus_file_operations_new_file_from_template (NULL, NULL,
+							 parent_uri, new_name,
+							 eaction->template,
+							 undo_redo_done_create_callback, action);
+
+	g_free (parent_uri);
+	g_free (new_name);
+	g_object_unref (parent);
+}
+
+static void
+duplicate_redo_func (NautilusFileUndoData *action)
+{
+	GList *locations;
+	NautilusFileUndoDataExt* eaction = (NautilusFileUndoDataExt*) action;
+
+	locations = eaction->sources;
+	nautilus_file_operations_duplicate (locations, NULL, NULL,
+					    undo_redo_done_transfer_callback, action);
+}
+
+static void
+move_restore_redo_func (NautilusFileUndoData *action)
+{
+	GList *locations;
+	NautilusFileUndoDataExt* eaction = (NautilusFileUndoDataExt*) action;
+
+	locations = eaction->sources;
+	nautilus_file_operations_move (locations, NULL,
+				       eaction->dest_dir, NULL,
+				       undo_redo_done_transfer_callback, action);
+}
+
+static void
+rename_redo_func (NautilusFileUndoData *action)
+{
+	gchar *new_name;
+	NautilusFile *file;
+	NautilusFileUndoDataRename* eaction = (NautilusFileUndoDataRename*) action;
+
+	new_name = g_file_get_basename (eaction->new_file);
+	file = nautilus_file_get (eaction->old_file);
+	nautilus_file_rename (file, new_name,
+			      undo_redo_done_rename_callback, action);
+
+	nautilus_file_unref (file);
+	g_free (new_name);
+}
+
+static void
+create_empty_redo_func (NautilusFileUndoData *action)
+{
+	GFile *parent;
+	gchar *parent_uri;
+	gchar *new_name;
+	NautilusFileUndoDataCreate* eaction = (NautilusFileUndoDataCreate*) action;
+
+	parent = g_file_get_parent (eaction->target_file);
+	parent_uri = g_file_get_uri (parent);
+	new_name = g_file_get_parse_name (eaction->target_file);
+	nautilus_file_operations_new_file (NULL, NULL, parent_uri,
+					   new_name,
+					   eaction->template,
+					   action->count, undo_redo_done_create_callback, action);
+
+	g_free (parent_uri);
+	g_free (new_name);
+	g_object_unref (parent);
+}
+
+static void
+create_folder_redo_func (NautilusFileUndoData *action)
+{
+	GFile *parent;
+	gchar *parent_uri;
+	NautilusFileUndoDataCreate* eaction = (NautilusFileUndoDataCreate*) action;
+
+	parent = g_file_get_parent (eaction->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);
+	g_object_unref (parent);
+}
+
+static void
+trash_redo_func (NautilusFileUndoData *action)
+{
+	NautilusFileUndoDataTrash* eaction = (NautilusFileUndoDataTrash*) action;
+	
+	if (g_hash_table_size (eaction->trashed) > 0) {
+		GList *uri_to_trash, *locations;
+
+		uri_to_trash = g_hash_table_get_keys (eaction->trashed);
+		locations = uri_list_to_gfile_list (uri_to_trash);
+		nautilus_file_operations_trash_or_delete (locations, NULL,
+							  undo_redo_done_delete_callback, action);
+		g_list_free (uri_to_trash);
+		g_list_free_full (locations, g_object_unref);
+	}
+}
+
+static void
+create_link_redo_func (NautilusFileUndoData *action)
+{
+	GList *locations;
+	NautilusFileUndoDataExt* eaction = (NautilusFileUndoDataExt*) action;
+
+	locations = eaction->sources;
+	nautilus_file_operations_link (locations, NULL,
+				       eaction->dest_dir, NULL,
+				       undo_redo_done_transfer_callback, action);
+}
+
+static void
+set_permissions_redo_func (NautilusFileUndoData *action)
+{
+	NautilusFile *file;
+	NautilusFileUndoDataPermissions* eaction = (NautilusFileUndoDataPermissions*) action;
+
+	file = nautilus_file_get (eaction->target_file);
+	nautilus_file_set_permissions (file, eaction->new_permissions,
+				       undo_redo_done_rename_callback, action);
+
+	nautilus_file_unref (file);
+}
+
+static void
+recursive_permissions_redo_func (NautilusFileUndoData *action)
+{
+	gchar *parent_uri;
+	NautilusFileUndoDataRecursivePermissions* eaction = (NautilusFileUndoDataRecursivePermissions*) action;
+
+	parent_uri = g_file_get_uri (eaction->dest_dir);
+	nautilus_file_set_permissions_recursive (parent_uri,
+						 eaction->file_permissions,
+						 eaction->file_mask,
+						 eaction->dir_permissions,
+						 eaction->dir_mask,
+						 undo_redo_op_callback,
+						 action);
+	g_free (parent_uri);
+}
+
+static void
+change_group_redo_func (NautilusFileUndoData *action)
+{
+	NautilusFile *file;
+	NautilusFileUndoDataOwnership* eaction = (NautilusFileUndoDataOwnership*) action;
+
+	file = nautilus_file_get (eaction->target_file);
+	
+	nautilus_file_set_group (file,
+				 eaction->new_ownership,
+				 undo_redo_done_rename_callback,
+				 action);
+
+	nautilus_file_unref (file);
+}
+
+static void
+change_owner_redo_func (NautilusFileUndoData *action)
+{
+	NautilusFile *file;
+	NautilusFileUndoDataOwnership* eaction = (NautilusFileUndoDataOwnership*) action;
+
+	file = nautilus_file_get (eaction->target_file);
+	nautilus_file_set_owner (file,
+				 eaction->new_ownership,
+				 undo_redo_done_rename_callback,
+				 action);
+
+	nautilus_file_unref (file);
+}
+
+/* description helpers */
+
+static void
+copy_description_func (NautilusFileUndoData *action,
+		       guint count,
+		       gchar **labels,
+		       gchar **descriptions)
+{
+	gchar *destination;
+	NautilusFileUndoDataExt* eaction = (NautilusFileUndoDataExt*) action;
+
+	destination = g_file_get_path (eaction->dest_dir);
+
+	if (count != 1) {
+		descriptions[0] = g_strdup_printf (_("Delete %d copied items"), count);
+		descriptions[1] = g_strdup_printf (_("Copy %d items to '%s'"), count, destination);
+
+		labels[0] = g_strdup_printf (_("_Undo Copy %d items"), count);
+		labels[1] = g_strdup_printf (_("_Redo Copy %d items"), count);
+	} else {
+		gchar *name;
+
+		name = get_first_target_short_name (eaction);
+		descriptions[0] = g_strdup_printf (_("Delete '%s'"), name);
+		descriptions[1] = g_strdup_printf (_("Copy '%s' to '%s'"), name, destination);
+
+		labels[0] = g_strdup (_("_Undo Copy"));
+		labels[1] = g_strdup (_("_Redo Copy"));
+
+		g_free (name);
+	}
+}
+
+static void
+duplicate_description_func (NautilusFileUndoData *action,
+			    guint count,
+			    gchar **labels,
+			    gchar **descriptions)
+{
+	gchar *destination;
+	NautilusFileUndoDataExt* eaction = (NautilusFileUndoDataExt*) action;
+
+	destination = g_file_get_path (eaction->dest_dir);
+
+	if (count != 1) {
+		descriptions[0] = g_strdup_printf (_("Delete %d duplicated items"), count);
+		descriptions[1] = g_strdup_printf (_("Duplicate of %d items in '%s'"),
+						   count, destination);
+
+		labels[0] = g_strdup_printf (_("_Undo Duplicate %d items"), count);
+		labels[1] = g_strdup_printf (_("_Redo Duplicate %d items"), count);
+	} else {
+		char *name;
+
+		name = get_first_target_short_name (eaction);
+		descriptions[0] = g_strdup_printf (_("Delete '%s'"), name);
+		descriptions[1] = g_strdup_printf (_("Duplicate '%s' in '%s'"),
+						   name, destination);
+
+		labels[0] = g_strdup (_("_Undo Duplicate"));
+		labels[1] = g_strdup (_("_Redo Duplicate"));
+
+		g_free (name);
+	}
+
+}
+
+static void
+move_description_func (NautilusFileUndoData *action,
+		       guint count,
+		       gchar **labels,
+		       gchar **descriptions)
+{
+	gchar *source, *destination;
+	NautilusFileUndoDataExt* eaction = (NautilusFileUndoDataExt*) action;
+
+    source = g_file_get_path (eaction->src_dir);
+	destination = g_file_get_path (eaction->dest_dir);
+
+	if (count != 1) {
+		descriptions[0] = g_strdup_printf (_("Move %d items back to '%s'"), count, source);
+		descriptions[1] = g_strdup_printf (_("Move %d items to '%s'"), count, destination);
+
+		labels[0] = g_strdup_printf (_("_Undo Move %d items"), count);
+		labels[1] = g_strdup_printf (_("_Redo Move %d items"), count);
+	} else {
+		char *name;
+
+		name = get_first_target_short_name (eaction);
+		descriptions[0] = g_strdup_printf (_("Move '%s' back to '%s'"), name, source);
+		descriptions[1] = g_strdup_printf (_("Move '%s' to '%s'"), name, destination);
+
+		labels[0] = g_strdup (_("_Undo Move"));
+		labels[1] = g_strdup (_("_Redo Move"));
+
+		g_free (name);
+	}
+}
+
+static void
+rename_description_func (NautilusFileUndoData *action,
+			 guint count,
+			 gchar **labels,
+			 gchar **descriptions)
+{
+	gchar *new_name, *old_name;
+	NautilusFileUndoDataRename* eaction = (NautilusFileUndoDataRename*) action;
+
+	new_name = g_file_get_parse_name (eaction->new_file);
+	old_name = g_file_get_parse_name (eaction->old_file);
+
+	descriptions[0] = g_strdup_printf (_("Rename '%s' as '%s'"), new_name, old_name);
+	descriptions[1] = g_strdup_printf (_("Rename '%s' as '%s'"), old_name, new_name);
+
+	labels[0] = g_strdup (_("_Undo Rename"));
+	labels[1] = g_strdup (_("_Redo Rename"));
+
+	g_free (old_name);
+	g_free (new_name);
+}
+
+static void
+create_undo_common (NautilusFileUndoDataCreate *eaction,
+		    guint count,
+		    gchar **descriptions)
+{
+	char *name;
+
+	name = g_file_get_parse_name (eaction->target_file);
+	descriptions[0] = g_strdup_printf (_("Delete '%s'"), name);
+	
+	g_free (name);
+}
+
+static void
+create_empty_description_func (NautilusFileUndoData *action,
+			       guint count,
+			       gchar **labels,
+			       gchar **descriptions)
+{
+	gchar *name;
+	NautilusFileUndoDataCreate* eaction = (NautilusFileUndoDataCreate*) action;
+
+	create_undo_common (eaction, count, descriptions);
+
+	name = g_file_get_parse_name (eaction->target_file);
+	descriptions[1] = g_strdup_printf (_("Create an empty file '%s'"), name);
+
+	labels[0] = g_strdup (_("_Undo Create Empty File"));
+	labels[1] = g_strdup (_("_Redo Create Empty File"));
+
+	g_free (name);
+}
+
+static void
+create_from_template_description_func (NautilusFileUndoData *action,
+				       guint count,
+				       gchar **labels,
+				       gchar **descriptions)
+{
+	char *name;
+	NautilusFileUndoDataCreate* eaction = (NautilusFileUndoDataCreate*) action;
+
+	create_undo_common (eaction, count, descriptions);
+
+	name = g_file_get_parse_name (eaction->target_file);
+	descriptions[1] = g_strdup_printf (_("Create new file '%s' from template "), name);
+	g_free (name);
+
+	labels[0] = g_strdup (_("_Undo Create from Template"));
+	labels[1] = g_strdup (_("_Redo Create from Template"));
+}
+
+static void
+create_folder_description_func (NautilusFileUndoData *action,
+				guint count,
+				gchar **labels,
+				gchar **descriptions)
+{
+	char *name;
+	NautilusFileUndoDataCreate* eaction = (NautilusFileUndoDataCreate*) action;
+
+	create_undo_common (eaction, count, descriptions);
+
+	name = g_file_get_parse_name (eaction->target_file);
+	descriptions[1] = g_strdup_printf (_("Create a new folder '%s'"), name);
+
+	labels[0] = g_strdup (_("_Undo Create Folder"));
+	labels[1] = g_strdup (_("_Redo Create Folder"));
+
+	g_free (name);
+}
+
+static void
+trash_description_func (NautilusFileUndoData *action,
+			guint count,
+			gchar **labels,
+			gchar **descriptions)
+{
+	NautilusFileUndoDataTrash* eaction = (NautilusFileUndoDataTrash*) action;
+	count = g_hash_table_size (eaction->trashed);
+
+	if (count != 1) {
+		descriptions[0] = g_strdup_printf (_("Restore %d items from trash"), count);
+		descriptions[1] = g_strdup_printf (_("Move %d items to trash"), count);
+	} else {
+		GList *keys;
+		char *name, *orig_path;
+		GFile *file;
+
+		keys = g_hash_table_get_keys (eaction->trashed);
+		file = g_file_new_for_commandline_arg (keys->data);
+		name = g_file_get_basename (file);
+		orig_path = g_file_get_path (file);
+		descriptions[0] = g_strdup_printf (_("Restore '%s' to '%s'"), name, orig_path);
+
+		g_free (name);
+		g_free (orig_path);
+		g_list_free (keys);
+
+		name = g_file_get_parse_name (file);
+		descriptions[1] = g_strdup_printf (_("Move '%s' to trash"), name);
+
+		g_free (name);
+		g_object_unref (file);
+
+		labels[0] = g_strdup (_("_Undo Trash"));
+		labels[1] = g_strdup (_("_Redo Trash"));
+	}
+}
+
+static void
+restore_description_func (NautilusFileUndoData *action,
+			  guint count,
+			  gchar **labels,
+			  gchar **descriptions)
+{
+	NautilusFileUndoDataExt* eaction = (NautilusFileUndoDataExt*) action;
+	
+	if (count != 1) {
+		descriptions[0] = g_strdup_printf (_("Move %d items back to trash"), count);
+		descriptions[1] = g_strdup_printf (_("Restore %d items from trash"), count);
+	} else {
+		char *name;
+
+		name = get_first_target_short_name (eaction);
+		descriptions[0] = g_strdup_printf (_("Move '%s' back to trash"), name);
+		descriptions[1] = g_strdup_printf (_("Restore '%s' from trash"), name);
+		
+		g_free (name);
+
+		labels[0] = g_strdup (_("_Undo Restore from Trash"));
+		labels[1] = g_strdup (_("_Redo Restore from Trash"));
+	}
+}
+
+static void
+create_link_description_func (NautilusFileUndoData *action,
+			      guint count,
+			      gchar **labels,
+			      gchar **descriptions)
+{
+	NautilusFileUndoDataExt* eaction = (NautilusFileUndoDataExt*) action;
+	
+	if (count != 1) {
+		descriptions[0] = g_strdup_printf (_("Delete links to %d items"), count);
+		descriptions[1] = g_strdup_printf (_("Create links to %d items"), count);
+	} else {
+		char *name;
+
+		name = get_first_target_short_name (eaction);
+		descriptions[0] = g_strdup_printf (_("Delete link to '%s'"), name);
+		descriptions[1] = g_strdup_printf (_("Create link to '%s'"), name);
+
+		labels[0] = g_strdup (_("_Undo Create Link"));
+		labels[1] = g_strdup (_("_Redo Create Link"));
+ 
+		g_free (name);
+	}
+}
+
+static void
+recursive_permissions_description_func (NautilusFileUndoData *action,
+					guint count,
+					gchar **labels,
+					gchar **descriptions)
+{
+	char *name;
+	NautilusFileUndoDataRecursivePermissions* eaction = (NautilusFileUndoDataRecursivePermissions*) action;
+
+	name = g_file_get_path (eaction->dest_dir);
+
+	descriptions[0] = g_strdup_printf (_("Restore original permissions of items enclosed in '%s'"), name);
+	descriptions[1] = g_strdup_printf (_("Set permissions of items enclosed in '%s'"), name);
+
+	labels[0] = g_strdup (_("_Undo Change Permissions"));
+	labels[1] = g_strdup (_("_Redo Change Permissions"));
+	
+	g_free (name);
+}
+
+static void
+set_permissions_description_func (NautilusFileUndoData *action,
+				  guint count,
+				  gchar **labels,
+				  gchar **descriptions)
+{
+	char *name;
+	NautilusFileUndoDataPermissions* eaction = (NautilusFileUndoDataPermissions*) action;
+
+	name = g_file_get_parse_name (eaction->target_file);
+	descriptions[0] = g_strdup_printf (_("Restore original permissions of '%s'"), name);
+	descriptions[1] = g_strdup_printf (_("Set permissions of '%s'"), name);
+
+	labels[0] = g_strdup (_("_Undo Change Permissions"));
+	labels[1] = g_strdup (_("_Redo Change Permissions"));
+	
+	g_free (name);
+}
+
+static void
+change_group_description_func (NautilusFileUndoData *action,
+			       guint count,
+			       gchar **labels,
+			       gchar **descriptions)
+{
+	gchar *name;
+	NautilusFileUndoDataOwnership* eaction = (NautilusFileUndoDataOwnership*) action;
+
+	name = g_file_get_parse_name (eaction->target_file);
+	descriptions[0] = g_strdup_printf (_("Restore group of '%s' to '%s'"),
+					   name, eaction->original_ownership);
+	descriptions[1] = g_strdup_printf (_("Set group of '%s' to '%s'"),
+					   name, eaction->new_ownership);
+
+	labels[0] = g_strdup (_("_Undo Change Group"));
+	labels[1] = g_strdup (_("_Redo Change Group"));
+
+	g_free (name);
+}
+
+static void
+change_owner_description_func (NautilusFileUndoData *action,
+			       guint count,
+			       gchar **labels,
+			       gchar **descriptions)
+{
+	gchar *name;
+	NautilusFileUndoDataOwnership* eaction = (NautilusFileUndoDataOwnership*) action;
+
+	name = g_file_get_parse_name (eaction->target_file);
+	descriptions[0] = g_strdup_printf (_("Restore owner of '%s' to '%s'"),
+					   name, eaction->original_ownership);
+	descriptions[1] = g_strdup_printf (_("Set owner of '%s' to '%s'"),
+					   name, eaction->new_ownership);
+
+	labels[0] = g_strdup (_("_Undo Change Owner"));
+	labels[1] = g_strdup (_("_Redo Change Owner"));
+
+	g_free (name);
+}
+
+static void
+finalize_undo_data_ext (NautilusFileUndoData* data)
+{
+	NautilusFileUndoDataExt* extdata = (NautilusFileUndoDataExt*) data;
+	if (extdata->sources) {
+		g_list_foreach (extdata->sources, (GFunc) g_free, NULL);
+		g_list_free (extdata->sources);
+	}
+	if (extdata->destinations) {
+		g_list_foreach (extdata->destinations, (GFunc) g_free, NULL);
+		g_list_free (extdata->destinations);
+	}
+	if (extdata->src_dir) {
+		g_object_unref (extdata->src_dir);
+	}
+	if (extdata->dest_dir) {
+		g_object_unref (extdata->dest_dir);
+	}
+	g_slice_free (NautilusFileUndoDataExt, extdata);
+}
+
+static void
+finalize_undo_data_rename (NautilusFileUndoData* data)
+{
+	NautilusFileUndoDataRename* extdata = (NautilusFileUndoDataRename*) data;
+	if (extdata->old_file) {
+		g_object_unref (extdata->old_file);
+	}
+	if (extdata->new_file) { 
+		g_free (extdata->new_file);
+	}
+	g_slice_free (NautilusFileUndoDataRename, extdata);	
+}
+
+static void
+finalize_undo_data_create (NautilusFileUndoData* data)
+{
+	NautilusFileUndoDataCreate* extdata = (NautilusFileUndoDataCreate*) data;
+	if (extdata->target_file) {
+		g_object_unref (extdata->target_file);
+	}
+	g_free (extdata->template);	
+	g_slice_free (NautilusFileUndoDataCreate, extdata);
+}
+
+static void
+finalize_undo_data_trash (NautilusFileUndoData* data)
+{
+	NautilusFileUndoDataTrash* extdata = (NautilusFileUndoDataTrash*) data;
+	if (extdata->trashed) {
+		g_hash_table_destroy (extdata->trashed);
+	}
+	g_slice_free (NautilusFileUndoDataTrash, extdata);
+}
+
+static void
+finalize_undo_data_permissions (NautilusFileUndoData* data)
+{
+	NautilusFileUndoDataPermissions* extdata = (NautilusFileUndoDataPermissions*) data;
+	if (extdata->target_file) {
+		g_object_unref (extdata->target_file);
+	}
+	g_slice_free (NautilusFileUndoDataPermissions, extdata);
+}
+
+static void
+finalize_undo_data_recursivepermissions (NautilusFileUndoData* data)
+{
+	NautilusFileUndoDataRecursivePermissions* extdata = (NautilusFileUndoDataRecursivePermissions*) data;
+	if (extdata->original_permissions) {
+		g_hash_table_destroy (extdata->original_permissions);
+	}
+	if (extdata->dest_dir) {
+		g_object_unref (extdata->dest_dir);
+	}
+	g_slice_free (NautilusFileUndoDataRecursivePermissions, extdata);
+}
+
+static void
+finalize_undo_data_ownership (NautilusFileUndoData* data)
+{
+	NautilusFileUndoDataOwnership* extdata = (NautilusFileUndoDataOwnership*) data;
+	if (extdata->target_file) {
+		g_object_unref (extdata->target_file);
+	}
+	g_free (extdata->original_ownership);
+	g_free (extdata->new_ownership);
+	g_slice_free (NautilusFileUndoDataOwnership, extdata);
+}
+
+
+static NautilusFileUndoData *
+create_from_type (NautilusFileUndoDataType type)
+{
+	struct {
+		void (* undo_func)               (NautilusFileUndoData *data);
+		void (* redo_func)               (NautilusFileUndoData *data);
+		void (* strings_func)            (NautilusFileUndoData *data,
+						  guint count,
+						  gchar **labels,
+						  gchar **descriptions);
+		void (* finalize_func)           (NautilusFileUndoData *data);
+		gsize alloc_size;
+	} const mappings[NAUTILUS_FILE_UNDO_NUM_TYPES] = {
+		/* copy action */
+		{ copy_or_link_undo_func, copy_redo_func,
+		  copy_description_func, finalize_undo_data_ext,
+		  sizeof(NautilusFileUndoDataExt) },
+		/* duplicate action */
+		{ copy_or_link_undo_func, duplicate_redo_func,
+		  duplicate_description_func, finalize_undo_data_ext,
+		  sizeof(NautilusFileUndoDataExt) },
+		/* move action */
+		{ move_undo_func, move_restore_redo_func,
+		  move_description_func, finalize_undo_data_ext,
+		  sizeof(NautilusFileUndoDataExt) },
+		/* rename action */
+		{ rename_undo_func, rename_redo_func,
+		  rename_description_func, finalize_undo_data_rename,
+		  sizeof(NautilusFileUndoDataRename) },
+		/* create empty action */
+		{ create_undo_func, create_empty_redo_func,
+		  create_empty_description_func, finalize_undo_data_create,
+		  sizeof(NautilusFileUndoDataCreate) },
+		/* create from template action */
+		{ create_undo_func, create_from_template_redo_func,
+		  create_from_template_description_func, finalize_undo_data_create,
+		  sizeof(NautilusFileUndoDataCreate) },
+		/* create folder action */
+		{ create_undo_func, create_folder_redo_func,
+		  create_folder_description_func, finalize_undo_data_create,
+		  sizeof(NautilusFileUndoDataCreate) },
+		/* move to trash action */
+		{ trash_undo_func, trash_redo_func,
+		  trash_description_func, finalize_undo_data_trash,
+		  sizeof(NautilusFileUndoDataTrash) },
+		/* restore from trash action */
+		{ restore_undo_func, move_restore_redo_func,
+		  restore_description_func, finalize_undo_data_ext,
+		  sizeof(NautilusFileUndoDataExt) },
+		/* create link action */
+		{ create_undo_func, create_link_redo_func,
+		  create_link_description_func, finalize_undo_data_ext,
+		  sizeof(NautilusFileUndoDataExt) },
+		/* recursive permissions action */
+		{ recursive_permissions_undo_func, recursive_permissions_redo_func,
+		  recursive_permissions_description_func, finalize_undo_data_recursivepermissions,
+		  sizeof(NautilusFileUndoDataRecursivePermissions) },
+		/* set permissions action */
+		{ set_permissions_undo_func, set_permissions_redo_func,
+		  set_permissions_description_func , finalize_undo_data_permissions,
+		  sizeof(NautilusFileUndoDataPermissions) },
+		/* change group action */
+		{ change_group_undo_func, change_group_redo_func,
+		  change_group_description_func, finalize_undo_data_ownership,
+		  sizeof(NautilusFileUndoDataOwnership) },
+		/* change owner action */
+		{ change_owner_undo_func, change_owner_redo_func,
+		  change_owner_description_func, finalize_undo_data_ownership,
+		  sizeof(NautilusFileUndoDataOwnership) },
+	};
+
+	NautilusFileUndoData *retval;
+
+	retval = g_slice_alloc0 (mappings[type].alloc_size);
+	retval->undo_func = mappings[type].undo_func;
+	retval->redo_func = mappings[type].redo_func;
+	retval->strings_func = mappings[type].strings_func;
+	retval->finalize_func = mappings[type].finalize_func;
+
+	return retval;
+}
+
+/* functions to manipulate the action data */
+NautilusFileUndoData *
+nautilus_file_undo_data_new (NautilusFileUndoDataType type,
+                                     gint                        items_count)
+{
+	NautilusFileUndoData *data;
+
+	data = create_from_type (type);
+	data->type = type;
+	data->count = items_count;
+
+	return data;
+}
+
+void
+nautilus_file_undo_data_set_src_dir (NautilusFileUndoData        *action_data,
+                                             GFile                       *src)
+{
+	NautilusFileUndoDataExt* eaction_data = (NautilusFileUndoDataExt*) action_data;
+	eaction_data->src_dir = g_object_ref (src);
+}
+
+void
+nautilus_file_undo_data_set_dest_dir (NautilusFileUndoData        *action_data,
+                                              GFile                       *dest)
+{
+	NautilusFileUndoDataExt* eaction_data = (NautilusFileUndoDataExt*) action_data;
+	eaction_data->dest_dir = g_object_ref (dest);
+}
+
+void
+nautilus_file_undo_data_add_origin_target_pair (NautilusFileUndoData        *action_data,
+                                                        GFile                       *origin,
+                                                        GFile                       *target)
+{
+	NautilusFileUndoDataExt* eaction_data = (NautilusFileUndoDataExt*) action_data;
+	eaction_data->sources =
+		g_list_append (eaction_data->sources, g_object_ref (origin));
+	eaction_data->destinations =
+		g_list_append (eaction_data->destinations, g_object_ref (target));
+
+	action_data->is_valid = TRUE;
+}
+
+void
+nautilus_file_undo_data_add_trashed_file (NautilusFileUndoData        *action_data,
+                                                  GFile                       *file,
+                                                  guint64                      mtime)
+{
+	guint64 *modification_time;
+	char *original_uri;
+	NautilusFileUndoDataTrash* eaction_data = (NautilusFileUndoDataTrash*) action_data;
+
+	if (eaction_data->trashed == NULL) {
+		eaction_data->trashed =
+			g_hash_table_new_full (g_str_hash, g_str_equal,
+					       g_free, g_free);
+	}
+
+	modification_time = g_new (guint64, 1);
+	*modification_time = mtime;
+
+	original_uri = g_file_get_uri (file);
+
+	g_hash_table_insert (eaction_data->trashed, original_uri, modification_time);
+
+	action_data->is_valid = TRUE;
+}
+
+void
+nautilus_file_undo_data_add_file_permissions (NautilusFileUndoData        *action_data,
+                                                      GFile                       *file,
+                                                      guint32                      permission)
+{
+	guint32 *current_permissions;
+	char *original_uri;
+	NautilusFileUndoDataRecursivePermissions* eaction_data = (NautilusFileUndoDataRecursivePermissions*) action_data;
+
+	if (eaction_data->original_permissions == NULL) {
+		eaction_data->original_permissions =
+			g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+	}
+
+	current_permissions = g_new (guint32, 1);
+	*current_permissions = permission;
+
+	original_uri = g_file_get_uri (file);
+
+	g_hash_table_insert (eaction_data->original_permissions, original_uri, current_permissions);
+
+	action_data->is_valid = TRUE;
+}
+
+void
+nautilus_file_undo_data_set_file_permissions (NautilusFileUndoData        *action_data,
+                                                      GFile                       *file,
+                                                      guint32                      current_permissions,
+                                                      guint32                      new_permissions)
+{
+	NautilusFileUndoDataPermissions* eaction_data = (NautilusFileUndoDataPermissions*) action_data;
+	eaction_data->target_file = g_object_ref (file);
+	eaction_data->current_permissions = current_permissions;
+	eaction_data->new_permissions = new_permissions;
+
+	action_data->is_valid = TRUE;
+}
+
+void
+nautilus_file_undo_data_set_owner_change_information (NautilusFileUndoData        *action_data,
+                                                              GFile                       *file,
+                                                              const char                  *current_user,
+                                                              const char                  *new_user)
+{
+	NautilusFileUndoDataOwnership* eaction_data = (NautilusFileUndoDataOwnership*) action_data;
+	eaction_data->target_file = g_object_ref (file);
+	eaction_data->original_ownership = g_strdup (current_user);
+	eaction_data->new_ownership = g_strdup (new_user);
+
+	action_data->is_valid = TRUE;
+}
+
+void
+nautilus_file_undo_data_set_group_change_information (NautilusFileUndoData        *action_data,
+                                                              GFile                       *file,
+                                                              const char                  *current_group,
+                                                              const char                  *new_group)
+{
+	NautilusFileUndoDataOwnership* eaction_data = (NautilusFileUndoDataOwnership*) action_data;
+	eaction_data->target_file = g_object_ref (file);
+	eaction_data->original_ownership = g_strdup (current_group);
+	eaction_data->new_ownership = g_strdup (new_group);
+
+	action_data->is_valid = TRUE;
+}
+
+void
+nautilus_file_undo_data_set_recursive_permissions (NautilusFileUndoData         *action_data,
+                                                           guint32                      file_permissions,
+                                                           guint32                      file_mask,
+                                                           guint32                      dir_permissions,
+                                                           guint32                      dir_mask)
+{
+	NautilusFileUndoDataRecursivePermissions* eaction_data = (NautilusFileUndoDataRecursivePermissions*) action_data;
+	eaction_data->file_permissions = file_permissions;
+	eaction_data->file_mask = file_mask;
+	eaction_data->dir_permissions = dir_permissions;
+	eaction_data->dir_mask = dir_mask;
+
+	action_data->is_valid = TRUE;
+}
+
+void
+nautilus_file_undo_data_set_recursive_permissions_dest_dir (NautilusFileUndoData        *action_data,
+                                                            GFile                       *dest)
+{
+	NautilusFileUndoDataRecursivePermissions* eaction_data = (NautilusFileUndoDataRecursivePermissions*) action_data;
+	eaction_data->dest_dir = g_object_ref (dest);
+}
+
+void
+nautilus_file_undo_data_set_create_data (NautilusFileUndoData        *action_data,
+                                         GFile                       *file,
+                                         const char                  *template)
+{
+	NautilusFileUndoDataCreate* eaction_data = (NautilusFileUndoDataCreate*) action_data;
+	eaction_data->target_file = g_object_ref (file);
+	eaction_data->template = g_strdup (template);
+
+	action_data->is_valid = TRUE;
+}
+
+void
+nautilus_file_undo_data_set_rename_information (NautilusFileUndoData        *action_data,
+                                                        GFile                       *old_file,
+                                                        GFile                       *new_file)
+{
+	NautilusFileUndoDataRename* eaction_data = (NautilusFileUndoDataRename*) action_data;
+	eaction_data->old_file = g_object_ref (old_file);
+	eaction_data->new_file = g_object_ref (new_file);
+
+	action_data->is_valid = TRUE;
+}
diff --git a/libnautilus-private/nautilus-file-undo-operations.h b/libnautilus-private/nautilus-file-undo-operations.h
new file mode 100644
index 0000000..69a79c2
--- /dev/null
+++ b/libnautilus-private/nautilus-file-undo-operations.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* nautilus-file-undo-operations.h - Manages undo/redo of file operations
+ *
+ * Copyright (C) 2007-2011 Amos Brocco
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * 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.
+ *
+ * Authors: Amos Brocco <amos brocco gmail com>
+ *          Cosimo Cecchi <cosimoc redhat com>
+ *
+ */
+
+#ifndef __NAUTILUS_FILE_UNDO_OPERATIONS_H__
+#define __NAUTILUS_FILE_UNDO_OPERATIONS_H__
+
+#include <glib.h>
+#include <libnautilus-private/nautilus-file-undo-types.h>
+
+NautilusFileUndoData *nautilus_file_undo_data_new (NautilusFileUndoDataType type,
+                                                                   gint items_count);
+void nautilus_file_undo_data_free (NautilusFileUndoData *action);
+
+void nautilus_file_undo_data_set_src_dir (NautilusFileUndoData *data, 
+                                                  GFile *src);
+void nautilus_file_undo_data_set_dest_dir (NautilusFileUndoData *data, 
+						   GFile *dest);
+
+void nautilus_file_undo_data_add_origin_target_pair (NautilusFileUndoData *data, 
+							     GFile *origin, 
+							     GFile *target);
+void nautilus_file_undo_data_set_create_data (NautilusFileUndoData *data, 
+						      GFile *target_file,
+						      const char *template_uri);
+void nautilus_file_undo_data_set_rename_information (NautilusFileUndoData *data, 
+							     GFile *old_file, 
+							     GFile *new_file);
+void nautilus_file_undo_data_add_trashed_file (NautilusFileUndoData *data, 
+						       GFile *file, 
+						       guint64 mtime);
+void nautilus_file_undo_data_add_file_permissions (NautilusFileUndoData *data, 
+							   GFile *file, 
+							   guint32 permission);
+void nautilus_file_undo_data_set_recursive_permissions (NautilusFileUndoData *data, 
+								guint32 file_permissions, 
+								guint32 file_mask, 
+								guint32 dir_permissions, 
+								guint32 dir_mask);
+void nautilus_file_undo_data_set_recursive_permissions_dest_dir (NautilusFileUndoData *data,
+                                GFile *dest);
+void nautilus_file_undo_data_set_file_permissions (NautilusFileUndoData *data, 
+							   GFile *file,
+							   guint32 current_permissions,
+							   guint32 new_permissions);
+void nautilus_file_undo_data_set_owner_change_information (NautilusFileUndoData *data, 
+								   GFile *file,
+								   const char *current_user, 
+								   const char *new_user);
+void nautilus_file_undo_data_set_group_change_information (NautilusFileUndoData *data, 
+								   GFile *file,
+								   const char *current_group, 
+								   const char *new_group);
+
+#endif /* __NAUTILUS_FILE_UNDO_OPERATIONS_H__ */
diff --git a/libnautilus-private/nautilus-file-undo-types.h b/libnautilus-private/nautilus-file-undo-types.h
new file mode 100644
index 0000000..b582d6d
--- /dev/null
+++ b/libnautilus-private/nautilus-file-undo-types.h
@@ -0,0 +1,168 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+
+/* nautilus-file-undo-types.h - Data structures used by undo/redo
+ *
+ * Copyright (C) 2007-2011 Amos Brocco
+ * Copyright (C) 2010 Red Hat, Inc.
+ *
+ * This program 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 program 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 program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Authors: Amos Brocco <amos brocco gmail com>
+ *          Cosimo Cecchi <cosimoc redhat com>
+ *
+ */
+
+#ifndef __NAUTILUS_FILE_UNDO_TYPES_H__
+#define __NAUTILUS_FILE_UNDO_TYPES_H__
+
+#include <glib.h>
+#include <gio/gio.h>
+
+typedef struct _NautilusFileUndoManager NautilusFileUndoManager;
+typedef void (* NautilusFileUndoFinishCallback) (gpointer data);
+
+typedef enum {
+	NAUTILUS_FILE_UNDO_COPY,
+	NAUTILUS_FILE_UNDO_DUPLICATE,
+	NAUTILUS_FILE_UNDO_MOVE,
+	NAUTILUS_FILE_UNDO_RENAME,
+	NAUTILUS_FILE_UNDO_CREATE_EMPTY_FILE,
+	NAUTILUS_FILE_UNDO_CREATE_FILE_FROM_TEMPLATE,
+	NAUTILUS_FILE_UNDO_CREATE_FOLDER,
+	NAUTILUS_FILE_UNDO_MOVE_TO_TRASH,
+	NAUTILUS_FILE_UNDO_RESTORE_FROM_TRASH,
+	NAUTILUS_FILE_UNDO_CREATE_LINK,
+	NAUTILUS_FILE_UNDO_RECURSIVE_SET_PERMISSIONS,
+	NAUTILUS_FILE_UNDO_SET_PERMISSIONS,
+	NAUTILUS_FILE_UNDO_CHANGE_GROUP,
+	NAUTILUS_FILE_UNDO_CHANGE_OWNER,
+	NAUTILUS_FILE_UNDO_NUM_TYPES,
+} NautilusFileUndoDataType;
+
+typedef struct {
+	const char *undo_label;
+	const char *undo_description;
+	const char *redo_label;
+	const char *redo_description;
+} NautilusFileUndoMenuData;
+
+typedef struct _NautilusFileUndoData NautilusFileUndoData;
+
+struct _NautilusFileUndoData
+{
+	NautilusFileUndoDataType type;
+
+	void (* undo_func) (NautilusFileUndoData *data);
+	void (* redo_func) (NautilusFileUndoData *data);
+
+	NautilusFileUndoFinishCallback callback;
+	gpointer callback_user_data;
+
+	NautilusFileUndoManager *manager;
+	guint is_valid : 1;
+	guint locked : 1;	/* True if the action is being undone/redone */
+	guint freed : 1;	/* True if the action must be freed after undo/redo */
+	guint count;		/* Number of items */
+
+	void    (* strings_func) (NautilusFileUndoData *data,
+				  guint count,
+				  gchar **labels,
+				  gchar **descriptions);
+
+	gchar *undo_label;
+	gchar *redo_label;
+	gchar *undo_description;
+	gchar *redo_description;
+	
+	void (* finalize_func) (NautilusFileUndoData *data);
+};
+
+typedef struct 
+{
+	NautilusFileUndoData base_data;
+	/* Copy / Move / Duplicate / Link / Restore from trash stuff */
+	GFile *src_dir;
+	GFile *dest_dir;
+	GList *sources;	     /* Relative to src_dir */
+	GList *destinations; /* Relative to dest_dir */
+} NautilusFileUndoDataExt;
+
+typedef struct
+{
+	NautilusFileUndoData base_data;
+	/* Create new file/folder stuff/set permissions */
+	char *template;
+	GFile *target_file;
+} NautilusFileUndoDataCreate;
+
+typedef struct
+{
+	NautilusFileUndoData base_data;
+	/* Rename stuff */
+	GFile *old_file;
+	GFile *new_file;
+} NautilusFileUndoDataRename;
+
+typedef struct
+{
+	NautilusFileUndoData base_data;
+	/* Trash stuff */
+	GHashTable *trashed;
+} NautilusFileUndoDataTrash;
+
+typedef struct
+{
+	NautilusFileUndoData base_data;
+	/* Recursive change permissions stuff */
+	GFile *dest_dir;
+	GHashTable *original_permissions;
+	guint32 dir_mask;
+	guint32 dir_permissions;
+	guint32 file_mask;
+	guint32 file_permissions;
+} NautilusFileUndoDataRecursivePermissions;
+
+typedef struct
+{
+	NautilusFileUndoData base_data;
+	/* Single file change permissions stuff */
+	GFile *target_file;
+	guint32 current_permissions;
+	guint32 new_permissions;
+} NautilusFileUndoDataPermissions;
+
+typedef struct
+{
+	NautilusFileUndoData base_data;
+	/* Group and Owner change stuff */
+	GFile *target_file;
+	char *original_ownership;
+	char *new_ownership;
+} NautilusFileUndoDataOwnership;
+
+struct _NautilusFileUndoManagerPrivate
+{
+	GQueue *stack;
+
+	/* Used to protect access to stack (because of async file ops) */
+	GMutex mutex;
+
+	guint undo_levels;
+	guint index;
+	guint undo_redo_flag : 1;
+};
+
+#endif /* __NAUTILUS_FILE_UNDO_TYPES_H__ */
diff --git a/libnautilus-private/nautilus-file.c b/libnautilus-private/nautilus-file.c
index e69a794..58b49ef 100644
--- a/libnautilus-private/nautilus-file.c
+++ b/libnautilus-private/nautilus-file.c
@@ -44,6 +44,8 @@
 #include "nautilus-search-directory-file.h"
 #include "nautilus-thumbnails.h"
 #include "nautilus-vfs-file.h"
+#include "nautilus-file-undo-operations.h"
+#include "nautilus-file-undo-manager.h"
 #include "nautilus-saved-search-file.h"
 #include <eel/eel-debug.h>
 #include <eel/eel-glib-extensions.h>
@@ -1651,6 +1653,11 @@ nautilus_file_operation_free (NautilusFileOperation *op)
 	if (op->free_data) {
 		op->free_data (op->data);
 	}
+
+	if (op->undo_redo_data != NULL) {
+		nautilus_file_undo_manager_add_action (nautilus_file_undo_manager_get (), op->undo_redo_data);
+	}
+
 	g_free (op);
 }
 
@@ -1755,6 +1762,10 @@ rename_callback (GObject *source_object,
 						   res, &error);
 
 	if (new_file != NULL) {
+		if (op->undo_redo_data != NULL) {
+			nautilus_file_undo_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,
@@ -1925,10 +1936,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_file_undo_manager_is_undo_redo (nautilus_file_undo_manager_get ())) {
+		op->undo_redo_data = nautilus_file_undo_data_new (NAUTILUS_FILE_UNDO_RENAME, 1);
+	}
 
-	location = nautilus_file_get_location (file);
+	/* Do the renaming. */
 	g_file_set_display_name_async (location,
 				       new_file_name,
 				       G_PRIORITY_DEFAULT,
@@ -5055,9 +5070,21 @@ nautilus_file_set_permissions (NautilusFile *file,
 		return;
 	}
 
+	if (!nautilus_file_undo_manager_is_undo_redo (nautilus_file_undo_manager_get ())) {
+		NautilusFileUndoData *undo_redo_data;
+
+		undo_redo_data = nautilus_file_undo_data_new (NAUTILUS_FILE_UNDO_SET_PERMISSIONS, 1);
+		nautilus_file_undo_data_set_file_permissions (undo_redo_data,
+		                                                      nautilus_file_get_location (file),
+		                                                      file->details->permissions,
+		                                                      new_permissions);
+		nautilus_file_undo_manager_add_action (nautilus_file_undo_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);
 }
 
@@ -5357,6 +5384,22 @@ nautilus_file_set_owner (NautilusFile *file,
 		(* callback) (file, NULL, NULL, callback_data);
 		return;
 	}
+	
+	if (!nautilus_file_undo_manager_is_undo_redo (nautilus_file_undo_manager_get ())) {
+		NautilusFileUndoData *undo_redo_data;
+		char* current_owner;
+
+		current_owner = nautilus_file_get_owner_as_string (file, FALSE);
+		undo_redo_data = nautilus_file_undo_data_new (NAUTILUS_FILE_UNDO_CHANGE_OWNER, 1);
+
+		nautilus_file_undo_data_set_owner_change_information (undo_redo_data,
+		                                                              nautilus_file_get_location (file),
+		                                                              current_owner,
+		                                                              user_name_or_id);
+		nautilus_file_undo_manager_add_action (nautilus_file_undo_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);
@@ -5621,6 +5664,21 @@ nautilus_file_set_group (NautilusFile *file,
 		return;
 	}
 
+	if (!nautilus_file_undo_manager_is_undo_redo (nautilus_file_undo_manager_get ())) {
+		NautilusFileUndoData *undo_redo_data;
+		char *current_group;
+
+		current_group = nautilus_file_get_group_name (file);
+		undo_redo_data = nautilus_file_undo_data_new (NAUTILUS_FILE_UNDO_CHANGE_GROUP, 1);
+
+		nautilus_file_undo_data_set_group_change_information (undo_redo_data,
+		                                                              nautilus_file_get_location (file),
+		                                                              current_group,
+		                                                              group_name_or_id);
+		nautilus_file_undo_manager_add_action (nautilus_file_undo_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/src/nautilus-directory-view-ui.xml b/src/nautilus-directory-view-ui.xml
index 62d1a1b..30dc7e2 100644
--- a/src/nautilus-directory-view-ui.xml
+++ b/src/nautilus-directory-view-ui.xml
@@ -56,6 +56,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 b7ec379..0861fa6 100644
--- a/src/nautilus-shell-ui.xml
+++ b/src/nautilus-shell-ui.xml
@@ -32,6 +32,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-view.c b/src/nautilus-view.c
index fd8bb0c..1470bb3 100644
--- a/src/nautilus-view.c
+++ b/src/nautilus-view.c
@@ -78,6 +78,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-file-undo-manager.h>
 
 #define DEBUG_FLAG NAUTILUS_DEBUG_DIRECTORY_VIEW
 #include <libnautilus-private/nautilus-debug.h>
@@ -2351,6 +2352,13 @@ nautilus_view_get_selection_count (NautilusView *view)
 	return len;
 }
 
+static void
+undo_manager_changed_cb (NautilusFileUndoManager* manager,
+			 NautilusView *view)
+{
+	schedule_update_menus (view);
+}
+
 void
 nautilus_view_set_selection (NautilusView *nautilus_view,
 			     GList *selection)
@@ -2404,6 +2412,7 @@ nautilus_view_init (NautilusView *view)
 	NautilusDirectory *scripts_directory;
 	NautilusDirectory *templates_directory;
 	char *templates_uri;
+	NautilusFileUndoManager* manager;
 
 	view->details = G_TYPE_INSTANCE_GET_PRIVATE (view, NAUTILUS_TYPE_VIEW,
 						     NautilusViewDetails);
@@ -2481,6 +2490,10 @@ nautilus_view_init (NautilusView *view)
 	g_signal_connect_swapped (nautilus_window_state,
 				  "changed::" NAUTILUS_WINDOW_STATE_START_WITH_STATUS_BAR,
 				  G_CALLBACK (nautilus_view_display_selection_info), view);
+				  
+	manager = nautilus_file_undo_manager_get ();
+	g_signal_connect_object (manager, "undo-changed",
+				 G_CALLBACK (undo_manager_changed_cb), view, 0);				  
 
 	/* Accessibility */
 	atk_object = gtk_widget_get_accessible (GTK_WIDGET (view));
@@ -3146,6 +3159,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);
@@ -5989,6 +6004,104 @@ invoke_external_bulk_rename_utility (NautilusView *view,
 }
 
 static void
+update_undo_actions (NautilusView *view,
+		     gboolean invalidate)
+{
+	NautilusFileUndoManager *manager;
+	NautilusFileUndoMenuData *menu_data;
+	GtkAction *action;
+	const gchar *label, *tooltip;
+	gboolean available;
+	gboolean undo_active, redo_active;
+
+	manager = nautilus_file_undo_manager_get ();
+	menu_data = nautilus_file_undo_manager_get_menu_data (manager);
+
+	undo_active = menu_data->undo_label != NULL && !invalidate;
+	redo_active = menu_data->redo_label != NULL && !invalidate;
+
+	/* Update undo entry */
+	action = gtk_action_group_get_action (view->details->dir_action_group,
+					      "Undo");
+	available = undo_active;
+	if (available) {
+		label = menu_data->undo_label;
+		tooltip = menu_data->undo_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,
+					      "Redo");
+	available = redo_active;
+	if (available) {
+		label = menu_data->redo_label;
+		tooltip = menu_data->redo_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);
+
+	nautilus_file_undo_menu_data_free (menu_data);
+}
+
+static void
+real_action_undo (NautilusView *view)
+{
+	NautilusFileUndoManager *manager;
+
+	manager = nautilus_file_undo_manager_get ();
+
+	/* Disable menus because they are in an untrustworthy status */
+	update_undo_actions (view, TRUE);
+
+	nautilus_file_undo_manager_undo (manager, NULL, NULL);
+}
+
+static void
+real_action_redo (NautilusView *view)
+{
+	NautilusFileUndoManager *manager;
+
+	manager = nautilus_file_undo_manager_get ();
+
+	/* Disable menus because they are in an untrustworthy status */
+	update_undo_actions (view, TRUE);
+
+	nautilus_file_undo_manager_redo (manager, NULL, NULL);
+}
+
+static void
+action_undo_callback (GtkAction *action,
+		      gpointer callback_data)
+{
+	real_action_undo (NAUTILUS_VIEW (callback_data));
+}
+
+static void
+action_redo_callback (GtkAction *action,
+		      gpointer callback_data)
+{
+	real_action_redo (NAUTILUS_VIEW (callback_data));
+}
+
+static void
 real_action_rename (NautilusView *view,
 		    gboolean select_all)
 {
@@ -6934,7 +7047,15 @@ static const GtkActionEntry directory_view_entries[] = {
   /* name, stock id */         { "Restore From Trash", NULL,
   /* label, accelerator */       N_("_Restore"), NULL,
 				 NULL,
-				 G_CALLBACK (action_restore_from_trash_callback) },
+                 G_CALLBACK (action_restore_from_trash_callback) },
+ /* name, stock id */          { "Undo", GTK_STOCK_UNDO,
+ /* label, accelerator */        N_("_Undo"), "<control>Z",
+ /* tooltip */                   N_("Undo the last action"),
+                                 G_CALLBACK (action_undo_callback) },
+ /* name, stock id */	       { "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.
@@ -8454,6 +8575,8 @@ real_update_menus (NautilusView *view)
 
 	real_update_menus_volumes (view, selection, selection_count);
 
+	update_undo_actions (view, FALSE);
+
 	nautilus_file_list_free (selection);
 
 	if (view->details->scripts_invalid) {
diff --git a/src/nautilus-window-menus.c b/src/nautilus-window-menus.c
index 4dc4a22..149ba2a 100644
--- a/src/nautilus-window-menus.c
+++ b/src/nautilus-window-menus.c
@@ -106,6 +106,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) 
@@ -115,6 +116,7 @@ action_undo_callback (GtkAction *action,
 	app = nautilus_application_get_singleton ();
 	nautilus_undo_manager_undo (app->undo_manager);
 }
+#endif
 
 static void
 action_home_callback (GtkAction *action, 
@@ -918,9 +920,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]