From cdd4cad2ce3930bf1d484ab12371a5d6a354ea00 Mon Sep 17 00:00:00 2001 From: Stefano Karapetsas Date: Mon, 26 Dec 2011 19:52:59 +0100 Subject: ported nautilus-elementary UNDO-REDO (bzr rev 60) --- libcaja-private/Makefile.am | 2 + libcaja-private/caja-file-operations.c | 137 ++ libcaja-private/caja-file-private.h | 2 + libcaja-private/caja-file.c | 44 + libcaja-private/caja-undostack-manager.c | 1988 +++++++++++++++++++++++++++ libcaja-private/caja-undostack-manager.h | 184 +++ src/caja-shell-ui.xml | 4 +- src/caja-window-menus.c | 4 + src/file-manager/caja-directory-view-ui.xml | 4 + src/file-manager/fm-actions.h | 2 + src/file-manager/fm-directory-view.c | 171 +++ 11 files changed, 2541 insertions(+), 1 deletion(-) create mode 100644 libcaja-private/caja-undostack-manager.c create mode 100644 libcaja-private/caja-undostack-manager.h diff --git a/libcaja-private/Makefile.am b/libcaja-private/Makefile.am index 5d7bf9eb..7749f200 100644 --- a/libcaja-private/Makefile.am +++ b/libcaja-private/Makefile.am @@ -201,6 +201,8 @@ libcaja_private_la_SOURCES = \ caja-window-info.h \ caja-window-slot-info.c \ caja-window-slot-info.h \ + caja-undostack-manager.c \ + caja-undostack-manager.h \ $(NULL) $(lib_LTLIBRARIES): $(dependency_static_libs) diff --git a/libcaja-private/caja-file-operations.c b/libcaja-private/caja-file-operations.c index 90205b83..b35e98f6 100644 --- a/libcaja-private/caja-file-operations.c +++ b/libcaja-private/caja-file-operations.c @@ -68,6 +68,7 @@ #include "caja-trash-monitor.h" #include "caja-file-utilities.h" #include "caja-file-conflict-dialog.h" +#include "caja-undostack-manager.h" static gboolean confirm_trash_auto_value; @@ -88,6 +89,7 @@ typedef struct { gboolean merge_all; gboolean replace_all; gboolean delete_all; + CajaUndoStackActionData* undo_redo_data; } CommonJob; typedef struct { @@ -963,6 +965,10 @@ finalize_common (CommonJob *common) if (common->skip_readdir_error) { g_hash_table_destroy (common->skip_readdir_error); } + // Start UNDO-REDO + caja_undostack_manager_add_action (caja_undostack_manager_instance(), + common->undo_redo_data); + // End UNDO-REDO g_object_unref (common->progress); g_object_unref (common->cancellable); g_free (common); @@ -1758,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; } @@ -1774,6 +1782,9 @@ trash_files (CommonJob *job, GList *files, int *files_skipped) file = l->data; error = NULL; + + mtime = caja_undostack_manager_get_file_modification_time (file); + if (!g_file_trash (file, job->cancellable, &error)) { if (job->skip_all_error) { (*files_skipped)++; @@ -1821,6 +1832,10 @@ trash_files (CommonJob *job, GList *files, int *files_skipped) } else { caja_file_changes_queue_file_removed (file); + // Start UNDO-REDO + caja_undostack_manager_data_add_trashed_file (job->undo_redo_data, file, mtime); + // End UNDO-REDO + files_trashed++; report_trash_progress (job, files_trashed, total_files); } @@ -1965,6 +1980,15 @@ trash_or_delete_internal (GList *files, } else { inhibit_power_manager ((CommonJob *)job, _("Deleting Files")); } + // Start UNDO-REDO + // FIXME: Disabled, because of missing mechanism to restore a file from trash in a clean way + // see http://www.mail-archive.com/nautilus-list@gnome.org/msg04664.html + if (try_trash && !caja_undostack_manager_is_undo_redo(caja_undostack_manager_instance())) { + job->common.undo_redo_data = caja_undostack_manager_data_new (CAJA_UNDOSTACK_MOVETOTRASH, g_list_length(files)); + GFile* src_dir = g_file_get_parent (files->data); + caja_undostack_manager_data_set_src_dir (job->common.undo_redo_data, src_dir); + } + // End UNDO-REDO g_io_scheduler_push_job (delete_job, job, @@ -3362,6 +3386,9 @@ create_dest_dir (CommonJob *job, } return CREATE_DEST_DIR_FAILED; } + // Start UNDO-REDO + caja_undostack_manager_data_add_origin_target_pair (job->undo_redo_data, src, *dest); + // End UNDO-REDO caja_file_changes_queue_file_added (*dest); return CREATE_DEST_DIR_SUCCESS; } @@ -3974,6 +4001,8 @@ copy_move_file (CopyMoveJob *copy_job, unique_name_nr = 1; + // TODO: Here we should get the previous file name UNDO + /* another file in the same directory might have handled the invalid * filename condition for us */ @@ -4116,6 +4145,10 @@ copy_move_file (CopyMoveJob *copy_job, FALSE); } + // Start UNDO-REDO + caja_undostack_manager_data_add_origin_target_pair (job->undo_redo_data, src, dest); + // End UNDO-REDO + g_object_unref (dest); return; } @@ -4535,6 +4568,16 @@ caja_file_operations_copy (GList *files, inhibit_power_manager ((CommonJob *)job, _("Copying Files")); + // Start UNDO-REDO + if (!caja_undostack_manager_is_undo_redo(caja_undostack_manager_instance())) { + job->common.undo_redo_data = caja_undostack_manager_data_new (CAJA_UNDOSTACK_COPY, g_list_length(files)); + GFile* src_dir = g_file_get_parent (files->data); + caja_undostack_manager_data_set_src_dir (job->common.undo_redo_data, src_dir); + g_object_ref (target_dir); + caja_undostack_manager_data_set_dest_dir (job->common.undo_redo_data, target_dir); + } + // End UNDO-REDO + g_io_scheduler_push_job (copy_job, job, NULL, /* destroy notify */ @@ -4693,6 +4736,10 @@ move_file_prepare (CopyMoveJob *move_job, caja_file_changes_queue_schedule_position_remove (dest); } + // Start UNDO-REDO + caja_undostack_manager_data_add_origin_target_pair (job->undo_redo_data, src, dest); + // End UNDO-REDO + return; } @@ -5060,6 +5107,20 @@ caja_file_operations_move (GList *files, inhibit_power_manager ((CommonJob *)job, _("Moving Files")); + // Start UNDO-REDO + if (!caja_undostack_manager_is_undo_redo(caja_undostack_manager_instance())) { + if (g_file_has_uri_scheme (g_list_first(files)->data, "trash")) { + job->common.undo_redo_data = caja_undostack_manager_data_new (CAJA_UNDOSTACK_RESTOREFROMTRASH, g_list_length(files)); + } else { + job->common.undo_redo_data = caja_undostack_manager_data_new (CAJA_UNDOSTACK_MOVE, g_list_length(files)); + } + GFile* src_dir = g_file_get_parent (files->data); + caja_undostack_manager_data_set_src_dir (job->common.undo_redo_data, src_dir); + g_object_ref (target_dir); + caja_undostack_manager_data_set_dest_dir (job->common.undo_redo_data, target_dir); + } + // End UNDO-REDO + g_io_scheduler_push_job (move_job, job, NULL, /* destroy notify */ @@ -5153,6 +5214,9 @@ link_file (CopyMoveJob *job, path, common->cancellable, &error)) { + // Start UNDO-REDO + caja_undostack_manager_data_add_origin_target_pair (common->undo_redo_data, src, dest); + // End UNDO-REDO g_free (path); if (debuting_files) { g_hash_table_replace (debuting_files, g_object_ref (dest), GINT_TO_POINTER (TRUE)); @@ -5363,6 +5427,16 @@ caja_file_operations_link (GList *files, } job->debuting_files = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, NULL); + // Start UNDO-REDO + if (!caja_undostack_manager_is_undo_redo(caja_undostack_manager_instance())) { + job->common.undo_redo_data = caja_undostack_manager_data_new (CAJA_UNDOSTACK_CREATELINK, g_list_length(files)); + GFile* src_dir = g_file_get_parent (files->data); + caja_undostack_manager_data_set_src_dir (job->common.undo_redo_data, src_dir); + g_object_ref (target_dir); + caja_undostack_manager_data_set_dest_dir (job->common.undo_redo_data, target_dir); + } + // End UNDO-REDO + g_io_scheduler_push_job (link_job, job, NULL, /* destroy notify */ @@ -5394,6 +5468,16 @@ caja_file_operations_duplicate (GList *files, } job->debuting_files = g_hash_table_new_full (g_file_hash, (GEqualFunc)g_file_equal, g_object_unref, NULL); + // Start UNDO-REDO + if (!caja_undostack_manager_is_undo_redo(caja_undostack_manager_instance())) { + job->common.undo_redo_data = caja_undostack_manager_data_new (CAJA_UNDOSTACK_DUPLICATE, g_list_length(files)); + GFile* src_dir = g_file_get_parent (files->data); + caja_undostack_manager_data_set_src_dir (job->common.undo_redo_data, src_dir); + g_object_ref (src_dir); + caja_undostack_manager_data_set_dest_dir (job->common.undo_redo_data, src_dir); + } + // End UNDO-REDO + g_io_scheduler_push_job (copy_job, job, NULL, /* destroy notify */ @@ -5463,6 +5547,9 @@ set_permissions_file (SetPermissionsJob *job, if (!job_aborted (common) && g_file_info_has_attribute (info, G_FILE_ATTRIBUTE_UNIX_MODE)) { current = g_file_info_get_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE); + // Start UNDO-REDO + caja_undostack_manager_data_add_file_permissions(common->undo_redo_data, file, current); + // End UNDO-REDO current = (current & ~mask) | value; g_file_set_attribute_uint32 (file, G_FILE_ATTRIBUTE_UNIX_MODE, @@ -5546,6 +5633,15 @@ caja_file_set_permissions_recursive (const char *directory, job->done_callback = callback; job->done_callback_data = callback_data; + // Start UNDO-REDO + if (!caja_undostack_manager_is_undo_redo(caja_undostack_manager_instance())) { + job->common.undo_redo_data = caja_undostack_manager_data_new (CAJA_UNDOSTACK_RECURSIVESETPERMISSIONS, 1); + g_object_ref (job->file); + caja_undostack_manager_data_set_dest_dir (job->common.undo_redo_data, job->file); + caja_undostack_manager_data_set_recursive_permissions(job->common.undo_redo_data, file_permissions, file_mask, dir_permissions, dir_mask); + } + // End UNDO-REDO + g_io_scheduler_push_job (set_permissions_job, job, NULL, @@ -5790,6 +5886,13 @@ create_job (GIOSchedulerJob *io_job, res = g_file_make_directory (dest, common->cancellable, &error); + // Start UNDO-REDO + if (res) { + caja_undostack_manager_data_set_create_data(common->undo_redo_data, + g_file_get_uri(dest), + NULL); + } + // End UNDO-REDO } else { if (job->src) { res = g_file_copy (job->src, @@ -5798,6 +5901,13 @@ create_job (GIOSchedulerJob *io_job, common->cancellable, NULL, NULL, &error); + // Start UNDO-REDO + if (res) { + caja_undostack_manager_data_set_create_data(common->undo_redo_data, + g_file_get_uri(dest), + g_file_get_uri(job->src)); + } + // End UNDO-REDO } else { data = ""; length = 0; @@ -5820,6 +5930,13 @@ create_job (GIOSchedulerJob *io_job, res = g_output_stream_close (G_OUTPUT_STREAM (out), common->cancellable, &error); + // Start UNDO-REDO + if (res) { + caja_undostack_manager_data_set_create_data(common->undo_redo_data, + g_file_get_uri(dest), + g_strdup(data)); + } + // End UNDO-REDO } /* This will close if the write failed and we didn't close */ @@ -5984,6 +6101,12 @@ caja_file_operations_new_folder (GtkWidget *parent_view, job->has_position = TRUE; } + // Start UNDO-REDO + if (!caja_undostack_manager_is_undo_redo(caja_undostack_manager_instance())) { + job->common.undo_redo_data = caja_undostack_manager_data_new (CAJA_UNDOSTACK_CREATEFOLDER, 1); + } + // End UNDO-REDO + g_io_scheduler_push_job (create_job, job, NULL, /* destroy notify */ @@ -6022,6 +6145,12 @@ caja_file_operations_new_file_from_template (GtkWidget *parent_view, job->src = g_file_new_for_uri (template_uri); } + // Start UNDO-REDO + if (!caja_undostack_manager_is_undo_redo(caja_undostack_manager_instance())) { + job->common.undo_redo_data = caja_undostack_manager_data_new (CAJA_UNDOSTACK_CREATEFILEFROMTEMPLATE, 1); + } + // End UNDO-REDO + g_io_scheduler_push_job (create_job, job, NULL, /* destroy notify */ @@ -6059,6 +6188,12 @@ caja_file_operations_new_file (GtkWidget *parent_view, job->length = length; job->filename = g_strdup (target_filename); + // Start UNDO-REDO + if (!caja_undostack_manager_is_undo_redo(caja_undostack_manager_instance())) { + job->common.undo_redo_data = caja_undostack_manager_data_new (CAJA_UNDOSTACK_CREATEEMPTYFILE, 1); + } + // End UNDO-REDO + g_io_scheduler_push_job (create_job, job, NULL, /* destroy notify */ @@ -6122,6 +6257,8 @@ empty_trash_job_done (gpointer user_data) job->done_callback (job->done_callback_data); } + caja_undostack_manager_trash_has_emptied(caja_undostack_manager_instance()); + finalize_common ((CommonJob *)job); return FALSE; } diff --git a/libcaja-private/caja-file-private.h b/libcaja-private/caja-file-private.h index 937121bd..cc78386e 100644 --- a/libcaja-private/caja-file-private.h +++ b/libcaja-private/caja-file-private.h @@ -30,6 +30,7 @@ #include #include #include +#include #define CAJA_FILE_LARGE_TOP_LEFT_TEXT_MAXIMUM_CHARACTERS_PER_LINE 80 #define CAJA_FILE_LARGE_TOP_LEFT_TEXT_MAXIMUM_LINES 24 @@ -242,6 +243,7 @@ typedef struct gpointer data; GDestroyNotify free_data; + CajaUndoStackActionData* undo_redo_data; } CajaFileOperation; diff --git a/libcaja-private/caja-file.c b/libcaja-private/caja-file.c index 4b229ca6..b814bd0f 100644 --- a/libcaja-private/caja-file.c +++ b/libcaja-private/caja-file.c @@ -1658,6 +1658,10 @@ caja_file_operation_free (CajaFileOperation *op) if (op->free_data) { op->free_data (op->data); } + // Start UNDO-REDO + caja_undostack_manager_add_action (caja_undostack_manager_instance(), + op->undo_redo_data); + // End UNDO-REDO g_free (op); } @@ -1762,6 +1766,9 @@ rename_callback (GObject *source_object, res, &error); if (new_file != NULL) { + // Start UNDO-REDO + caja_undostack_manager_data_set_rename_information(op->undo_redo_data, G_FILE (source_object), new_file); + // End UNDO-REDO g_file_query_info_async (new_file, CAJA_FILE_DEFAULT_ATTRIBUTES, 0, @@ -1936,6 +1943,13 @@ caja_file_rename (CajaFile *file, /* Do the renaming. */ location = caja_file_get_location (file); + + // Start UNDO-REDO + if (!caja_undostack_manager_is_undo_redo(caja_undostack_manager_instance())) { + op->undo_redo_data = caja_undostack_manager_data_new (CAJA_UNDOSTACK_RENAME, 1); + } + // End UNDO-REDO + g_file_set_display_name_async (location, new_file_name, G_PRIORITY_DEFAULT, @@ -5157,6 +5171,15 @@ caja_file_set_permissions (CajaFile *file, return; } + // Start UNDO-REDO + if (!caja_undostack_manager_is_undo_redo(caja_undostack_manager_instance())) { + CajaUndoStackActionData* undo_redo_data = caja_undostack_manager_data_new (CAJA_UNDOSTACK_SETPERMISSIONS, 1); + caja_undostack_manager_data_set_file_permissions(undo_redo_data, caja_file_get_uri(file), file->details->permissions, new_permissions); + caja_undostack_manager_add_action (caja_undostack_manager_instance(), + undo_redo_data); + } + // End UNDO-REDO + info = g_file_info_new (); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_MODE, new_permissions); caja_file_set_attributes (file, info, callback, callback_data); @@ -5460,6 +5483,17 @@ caja_file_set_owner (CajaFile *file, return; } + // Start UNDO-REDO + if (!caja_undostack_manager_is_undo_redo(caja_undostack_manager_instance())) { + char* current_owner = caja_file_get_owner_as_string (file, FALSE); + CajaUndoStackActionData* undo_redo_data = caja_undostack_manager_data_new (CAJA_UNDOSTACK_CHANGEOWNER, 1); + caja_undostack_manager_data_set_owner_change_information(undo_redo_data, caja_file_get_uri(file), current_owner, user_name_or_id); + caja_undostack_manager_add_action (caja_undostack_manager_instance(), + undo_redo_data); + g_free(current_owner); + } + // End UNDO-REDO + info = g_file_info_new (); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_UID, new_id); caja_file_set_attributes (file, info, callback, callback_data); @@ -5723,6 +5757,16 @@ caja_file_set_group (CajaFile *file, return; } + // Start UNDO-REDO + if (!caja_undostack_manager_is_undo_redo(caja_undostack_manager_instance())) { + char* current_group = caja_file_get_group_name (file); + CajaUndoStackActionData* undo_redo_data = caja_undostack_manager_data_new (CAJA_UNDOSTACK_CHANGEGROUP, 1); + caja_undostack_manager_data_set_group_change_information(undo_redo_data, caja_file_get_uri(file), current_group, group_name_or_id); + caja_undostack_manager_add_action (caja_undostack_manager_instance(), + undo_redo_data); + g_free(current_group); + } + /* End UNDO-REDO info = g_file_info_new (); g_file_info_set_attribute_uint32 (info, G_FILE_ATTRIBUTE_UNIX_GID, new_id); diff --git a/libcaja-private/caja-undostack-manager.c b/libcaja-private/caja-undostack-manager.c new file mode 100644 index 00000000..0b63e972 --- /dev/null +++ b/libcaja-private/caja-undostack-manager.c @@ -0,0 +1,1988 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* CajaUndoStackManager - Manages undo of file operations (implementation) + * + * Copyright (C) 2007-2010 Amos Brocco + * Copyright (C) 2011 Stefano Karapetsas + * + * Authors: Amos Brocco , + * Stefano Karapetsas + * + * 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. + */ + +#include "caja-undostack-manager.h" +#include "caja-file-operations.h" +#include "caja-file.h" +#include +#include +#include +#include +#include +#include +#include + +/* ***************************************************************** + Private fields + ***************************************************************** */ + +struct _CajaUndoStackActionData +{ + /* Common stuff */ + CajaUndoStackActionType type; + gboolean isValid; + gboolean locked; /* True if the action is being undone/redone */ + gboolean freed; /* True if the action must be freed after undo/redo */ + guint count; /* Size of affected uris (count of items) */ + CajaUndoStackManager *manager; /* Pointer to the manager */ + + /* Copy / Move stuff */ + GFile *src_dir; + GFile *dest_dir; + GList *sources; /* Relative to src_dir */ + GList *destinations; /* Relative to dest_dir */ + + /* Cached labels/descriptions */ + char *undo_label; + char *undo_description; + char *redo_label; + char *redo_description; + + /* Create new file/folder stuff/set permissions */ + char *template; + char *target_uri; + + /* Rename stuff */ + char *old_uri; + char *new_uri; + + /* Trash stuff */ + GHashTable *trashed; + + /* Recursive change permissions stuff */ + GHashTable *original_permissions; + guint32 dir_mask; + guint32 dir_permissions; + guint32 file_mask; + guint32 file_permissions; + + /* Single file change permissions stuff */ + guint32 current_permissions; + guint32 new_permissions; + + /* Group */ + char *original_group_name_or_id; + char *new_group_name_or_id; + + /* Owner */ + char *original_user_name_or_id; + char *new_user_name_or_id; + +}; + +struct _CajaUndoStackManagerPrivate +{ + /* Private fields */ + GQueue *stack; + guint undo_levels; + guint index; + GMutex *mutex; /* Used to protect access to stack (because of async file ops) */ + gboolean dispose_has_run; + gboolean undo_redo_flag; + gboolean confirm_delete; +}; + +#define CAJA_UNDOSTACK_MANAGER_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((o), TYPE_CAJA_UNDOSTACK_MANAGER, CajaUndoStackManagerPrivate)) + +/* ***************************************************************** + Properties management prototypes + ***************************************************************** */ +enum +{ + PROP_UNDOSTACK_MANAGER_0, PROP_UNDO_LEVELS, PROP_CONFIRM_DELETE +}; + +static void caja_undostack_manager_set_property (GObject * object, + guint prop_id, const GValue * value, GParamSpec * pspec); + +static void caja_undostack_manager_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec); + +/* ***************************************************************** + Destructors prototypes + ***************************************************************** */ +static void caja_undostack_manager_finalize (GObject * object); + +static void caja_undostack_manager_dispose (GObject * object); + +/* ***************************************************************** + Type definition + ***************************************************************** */ +G_DEFINE_TYPE (CajaUndoStackManager, caja_undostack_manager, + G_TYPE_OBJECT); + +/* ***************************************************************** + Private methods prototypes + ***************************************************************** */ + +static void stack_clear_n_oldest (GQueue * stack, guint n); + +static void stack_fix_size (CajaUndoStackManagerPrivate * priv); + +static gboolean can_undo (CajaUndoStackManagerPrivate * priv); + +static gboolean can_redo (CajaUndoStackManagerPrivate * priv); + +static void stack_push_action (CajaUndoStackManagerPrivate * priv, + CajaUndoStackActionData * action); + +static CajaUndoStackActionData + * stack_scroll_left (CajaUndoStackManagerPrivate * priv); + +static CajaUndoStackActionData + * stack_scroll_right (CajaUndoStackManagerPrivate * priv); + +static CajaUndoStackActionData + * get_next_redo_action (CajaUndoStackManagerPrivate * priv); + +static CajaUndoStackActionData + * get_next_undo_action (CajaUndoStackManagerPrivate * priv); + +static gchar *get_undo_label (CajaUndoStackActionData * action); + +static gchar *get_undo_description (CajaUndoStackActionData * action); + +static gchar *get_redo_label (CajaUndoStackActionData * action); + +static gchar *get_redo_description (CajaUndoStackActionData * action); + +static void do_menu_update (CajaUndoStackManager * manager); + +static void free_undostack_action (gpointer data, gpointer user_data); + +static void undostack_dispose_all (GQueue * queue); + +static void undo_redo_done_transfer_callback (GHashTable * debuting_uris, + gpointer data); + +static void undo_redo_op_callback (gpointer callback_data); + +static void undo_redo_done_rename_callback (CajaFile * file, + GFile * result_location, GError * error, gpointer callback_data); + +static void undo_redo_done_delete_callback (GHashTable * debuting_uris, + gboolean user_cancel, gpointer callback_data); + +static void undo_redo_done_create_callback (GFile * new_file, + gpointer callback_data); + +static void clear_redo_actions (CajaUndoStackManagerPrivate * priv); + +static gchar *get_first_target_short_name (CajaUndoStackActionData * + action); + +static GList *construct_gfile_list (const GList * urilist, GFile * parent); + +static GList *construct_gfile_list_from_uri (char *uri); + +static GList *uri_list_to_gfile_list (GList * urilist); + +static char *get_uri_basename (char *uri); + +static char *get_uri_parent (char *uri); + +static char *get_uri_parent_path (char *uri); + +static GHashTable *retrieve_files_to_restore (GHashTable * trashed); + +/* ***************************************************************** + Base functions + ***************************************************************** */ +static void +caja_undostack_manager_class_init (CajaUndoStackManagerClass * klass) +{ + GParamSpec *undo_levels; + GParamSpec *confirm_delete; + GObjectClass *g_object_class; + + /* Add private structure */ + g_type_class_add_private (klass, sizeof (CajaUndoStackManagerPrivate)); + + /* Create properties */ + undo_levels = g_param_spec_uint ("undo-levels", "undo levels", + "Number of undo levels to be stored", + 1, UINT_MAX, 30, G_PARAM_READWRITE | G_PARAM_CONSTRUCT); + + confirm_delete = + g_param_spec_boolean ("confirm-delete", "confirm delete", + "Always confirm file deletion", FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT); + + /* Set properties get/set methods */ + g_object_class = G_OBJECT_CLASS (klass); + + g_object_class->set_property = caja_undostack_manager_set_property; + g_object_class->get_property = caja_undostack_manager_get_property; + + /* Install properties */ + g_object_class_install_property (g_object_class, PROP_UNDO_LEVELS, + undo_levels); + + g_object_class_install_property (g_object_class, PROP_CONFIRM_DELETE, + confirm_delete); + + /* The UI menu needs to update its status */ + g_signal_new ("request-menu-update", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | + G_SIGNAL_NO_HOOKS, 0, NULL, NULL, + g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); + + /* Hook deconstructors */ + g_object_class->dispose = caja_undostack_manager_dispose; + g_object_class->finalize = caja_undostack_manager_finalize; +} + +static void +caja_undostack_manager_init (CajaUndoStackManager * self) +{ + CajaUndoStackManagerPrivate *priv; + + priv = CAJA_UNDOSTACK_MANAGER_GET_PRIVATE (self); + + self->priv = priv; + + /* Initialize private fields */ + priv->stack = g_queue_new (); + priv->mutex = g_mutex_new (); + priv->index = 0; + priv->dispose_has_run = FALSE; + priv->undo_redo_flag = FALSE; + priv->confirm_delete = FALSE; +} + +static void +caja_undostack_manager_dispose (GObject * object) +{ + CajaUndoStackManager *self = CAJA_UNDOSTACK_MANAGER (object); + CajaUndoStackManagerPrivate *priv = self->priv; + + if (priv->dispose_has_run) + return; + + g_mutex_lock (priv->mutex); + + /* Free each undoable action in the stack and the stack itself */ + undostack_dispose_all (priv->stack); + g_queue_free (priv->stack); + g_mutex_unlock (priv->mutex); + + g_mutex_free (priv->mutex); + + priv->dispose_has_run = TRUE; + + G_OBJECT_CLASS (caja_undostack_manager_parent_class)->dispose (object); +} + +static void +caja_undostack_manager_finalize (GObject * object) +{ + G_OBJECT_CLASS (caja_undostack_manager_parent_class)->finalize (object); +} + +/* ***************************************************************** + Property management + ***************************************************************** */ +static void +caja_undostack_manager_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + g_return_if_fail (IS_CAJA_UNDOSTACK_MANAGER (object)); + + CajaUndoStackManager *manager = CAJA_UNDOSTACK_MANAGER (object); + CajaUndoStackManagerPrivate *priv = manager->priv; + guint new_undo_levels; + + switch (prop_id) { + case PROP_UNDO_LEVELS: + new_undo_levels = g_value_get_uint (value); + if (new_undo_levels > 0 && (priv->undo_levels != new_undo_levels)) { + priv->undo_levels = new_undo_levels; + g_mutex_lock (priv->mutex); + stack_fix_size (priv); + g_mutex_unlock (priv->mutex); + do_menu_update (manager); + } + break; + case PROP_CONFIRM_DELETE: + priv->confirm_delete = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +caja_undostack_manager_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + g_return_if_fail (IS_CAJA_UNDOSTACK_MANAGER (object)); + + CajaUndoStackManager *manager = CAJA_UNDOSTACK_MANAGER (object); + CajaUndoStackManagerPrivate *priv = manager->priv; + + switch (prop_id) { + case PROP_UNDO_LEVELS: + g_value_set_uint (value, priv->undo_levels); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/* ***************************************************************** + Public methods + ***************************************************************** */ + +/** **************************************************************** + * Returns the undo stack manager instance (singleton pattern) + ** ****************************************************************/ +CajaUndoStackManager * +caja_undostack_manager_instance (void) +{ + static CajaUndoStackManager *manager = NULL; + + if (manager == NULL) { + manager = + g_object_new (TYPE_CAJA_UNDOSTACK_MANAGER, "undo-levels", 32, NULL); + } + + return manager; +} + +/** **************************************************************** + * True if undoing / redoing + ** ****************************************************************/ +gboolean +caja_undostack_manager_is_undo_redo (CajaUndoStackManager * manager) +{ + CajaUndoStackManagerPrivate *priv = manager->priv; + if (priv->undo_redo_flag) { + priv->undo_redo_flag = FALSE; + return TRUE; + } + + return FALSE; +} + +void +caja_undostack_manager_request_menu_update (CajaUndoStackManager * + manager) +{ + do_menu_update (manager); +} + +/** **************************************************************** + * Redoes the last file operation + ** ****************************************************************/ +void +caja_undostack_manager_redo (CajaUndoStackManager * manager, + GtkWidget * parent_view, CajaUndostackFinishCallback cb) +{ + GList *uris; + CajaFile *file; + char *new_name; + char *puri; + CajaUndoStackManagerPrivate *priv = manager->priv; + + g_mutex_lock (priv->mutex); + + CajaUndoStackActionData *action = stack_scroll_left (priv); + + /* Action will be NULL if redo is not possible */ + if (action != NULL) { + action->locked = TRUE; + } + + g_mutex_unlock (priv->mutex); + + do_menu_update (manager); + + if (action != NULL) { + action->locked = TRUE; /* Remember to unlock when redo is finished */ + priv->undo_redo_flag = TRUE; + switch (action->type) { + case CAJA_UNDOSTACK_COPY: + uris = construct_gfile_list (action->sources, action->src_dir); + caja_file_operations_copy (uris, NULL, + action->dest_dir, NULL, undo_redo_done_transfer_callback, action); + eel_g_object_list_free (uris); + break; + case CAJA_UNDOSTACK_CREATEFILEFROMTEMPLATE: + puri = get_uri_parent (action->target_uri); + new_name = get_uri_basename (action->target_uri); + caja_file_operations_new_file_from_template (NULL, + NULL, + puri, + new_name, action->template, undo_redo_done_create_callback, action); + g_free (puri); + g_free (new_name); + break; + case CAJA_UNDOSTACK_DUPLICATE: + uris = construct_gfile_list (action->sources, action->src_dir); + caja_file_operations_duplicate (uris, NULL, NULL, + undo_redo_done_transfer_callback, action); + eel_g_object_list_free (uris); + break; + case CAJA_UNDOSTACK_RESTOREFROMTRASH: + case CAJA_UNDOSTACK_MOVE: + uris = construct_gfile_list (action->sources, action->src_dir); + caja_file_operations_move (uris, NULL, + action->dest_dir, NULL, undo_redo_done_transfer_callback, action); + eel_g_object_list_free (uris); + break; + case CAJA_UNDOSTACK_RENAME: + new_name = get_uri_basename (action->new_uri); + file = caja_file_get_by_uri (action->old_uri); + caja_file_rename (file, new_name, + undo_redo_done_rename_callback, action); + g_object_unref (file); + g_free (new_name); + break; + case CAJA_UNDOSTACK_CREATEEMPTYFILE: + puri = get_uri_parent (action->target_uri); + new_name = get_uri_basename (action->target_uri); + caja_file_operations_new_file (NULL, NULL, puri, + new_name, + action->template, + 0, undo_redo_done_create_callback, action); + g_free (puri); + g_free (new_name); + break; + case CAJA_UNDOSTACK_CREATEFOLDER: + puri = get_uri_parent (action->target_uri); + caja_file_operations_new_folder (NULL, NULL, puri, + undo_redo_done_create_callback, action); + g_free (puri); + break; + case CAJA_UNDOSTACK_MOVETOTRASH: + if (g_hash_table_size (action->trashed) > 0) { + GList *uri_to_trash = g_hash_table_get_keys (action->trashed); + uris = uri_list_to_gfile_list (uri_to_trash); + priv->undo_redo_flag = TRUE; + caja_file_operations_trash_or_delete + (uris, NULL, undo_redo_done_delete_callback, action); + g_list_free (uri_to_trash); + eel_g_object_list_free (uris); + } + break; + case CAJA_UNDOSTACK_CREATELINK: + uris = construct_gfile_list (action->sources, action->src_dir); + caja_file_operations_link (uris, NULL, + action->dest_dir, NULL, undo_redo_done_transfer_callback, action); + eel_g_object_list_free (uris); + break; + case CAJA_UNDOSTACK_SETPERMISSIONS: + file = caja_file_get_by_uri (action->target_uri); + caja_file_set_permissions (file, + action->new_permissions, undo_redo_done_rename_callback, action); + g_object_unref (file); + break; + case CAJA_UNDOSTACK_RECURSIVESETPERMISSIONS: + puri = g_file_get_uri (action->dest_dir); + caja_file_set_permissions_recursive (puri, + action->file_permissions, + action->file_mask, + action->dir_permissions, + action->dir_mask, undo_redo_op_callback, action); + g_free (puri); + break; + case CAJA_UNDOSTACK_CHANGEGROUP: + file = caja_file_get_by_uri (action->target_uri); + caja_file_set_group (file, + action->new_group_name_or_id, + undo_redo_done_rename_callback, action); + g_object_unref (file); + break; + case CAJA_UNDOSTACK_CHANGEOWNER: + file = caja_file_get_by_uri (action->target_uri); + caja_file_set_owner (file, + action->new_user_name_or_id, + undo_redo_done_rename_callback, action); + g_object_unref (file); + break; + case CAJA_UNDOSTACK_DELETE: + default: + priv->undo_redo_flag = FALSE; + break; /* We shouldn't be here */ + } + } + + (*cb) ((gpointer) parent_view); +} + +/** **************************************************************** + * Undoes the last file operation + ** ****************************************************************/ +void +caja_undostack_manager_undo (CajaUndoStackManager * manager, + GtkWidget * parent_view, CajaUndostackFinishCallback cb) +{ + GList *uris = NULL; + GHashTable *files_to_restore; + CajaFile *file; + char *new_name; + CajaUndoStackManagerPrivate *priv = manager->priv; + + g_mutex_lock (priv->mutex); + + CajaUndoStackActionData *action = stack_scroll_right (priv); + + if (action != NULL) { + action->locked = TRUE; + } + + g_mutex_unlock (priv->mutex); + + do_menu_update (manager); + + if (action != NULL) { + priv->undo_redo_flag = TRUE; + switch (action->type) { + case CAJA_UNDOSTACK_CREATEEMPTYFILE: + case CAJA_UNDOSTACK_CREATEFILEFROMTEMPLATE: + case CAJA_UNDOSTACK_CREATEFOLDER: + uris = construct_gfile_list_from_uri (action->target_uri); + case CAJA_UNDOSTACK_COPY: + case CAJA_UNDOSTACK_DUPLICATE: + case CAJA_UNDOSTACK_CREATELINK: + if (!uris) { + uris = construct_gfile_list (action->destinations, action->dest_dir); + uris = g_list_reverse (uris); // Deleting must be done in reverse + } + if (priv->confirm_delete) { + caja_file_operations_delete (uris, NULL, + undo_redo_done_delete_callback, action); + eel_g_object_list_free (uris); + } else { + /* We skip the confirmation message + */ + GList *f; + for (f = uris; f != NULL; f = f->next) { + char *name; + name = g_file_get_uri (f->data); + g_free (name); + g_file_delete (f->data, NULL, NULL); + g_object_unref (f->data); + } + g_list_free (uris); + /* Here we must do what's necessary for the callback */ + undo_redo_done_transfer_callback (NULL, action); + } + break; + case CAJA_UNDOSTACK_RESTOREFROMTRASH: + uris = construct_gfile_list (action->destinations, action->dest_dir); + caja_file_operations_trash_or_delete (uris, NULL, + undo_redo_done_delete_callback, action); + eel_g_object_list_free (uris); + break; + case CAJA_UNDOSTACK_MOVETOTRASH: + files_to_restore = retrieve_files_to_restore (action->trashed); + if (g_hash_table_size (files_to_restore) > 0) { + GList *l; + GList *gfiles_in_trash = g_hash_table_get_keys (files_to_restore); + GFile *item; + GFile *dest; + char *value; + + for (l = gfiles_in_trash; l != NULL; l = l->next) { + item = l->data; + value = g_hash_table_lookup (files_to_restore, item); + dest = g_file_new_for_uri (value); + g_file_move (item, dest, + G_FILE_COPY_NOFOLLOW_SYMLINKS, NULL, NULL, NULL, NULL); + g_object_unref (dest); + } + + g_list_free (gfiles_in_trash); + } + g_hash_table_destroy (files_to_restore); + /* Here we must do what's necessary for the callback */ + undo_redo_done_transfer_callback (NULL, action); + break; + case CAJA_UNDOSTACK_MOVE: + uris = construct_gfile_list (action->destinations, action->dest_dir); + caja_file_operations_move (uris, NULL, + action->src_dir, NULL, undo_redo_done_transfer_callback, action); + eel_g_object_list_free (uris); + break; + case CAJA_UNDOSTACK_RENAME: + new_name = get_uri_basename (action->old_uri); + file = caja_file_get_by_uri (action->new_uri); + caja_file_rename (file, new_name, + undo_redo_done_rename_callback, action); + g_object_unref (file); + g_free (new_name); + break; + case CAJA_UNDOSTACK_SETPERMISSIONS: + file = caja_file_get_by_uri (action->target_uri); + caja_file_set_permissions (file, + action->current_permissions, + undo_redo_done_rename_callback, action); + g_object_unref (file); + break; + case CAJA_UNDOSTACK_RECURSIVESETPERMISSIONS: + if (g_hash_table_size (action->original_permissions) > 0) { + GList *gfiles_list = + g_hash_table_get_keys (action->original_permissions); + guint32 *perm; + GList *l; + GFile *dest; + char *item; + + for (l = gfiles_list; l != NULL; l = l->next) { + item = l->data; + perm = g_hash_table_lookup (action->original_permissions, item); + dest = g_file_new_for_uri (item); + g_file_set_attribute_uint32 (dest, + G_FILE_ATTRIBUTE_UNIX_MODE, + *perm, G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, NULL, NULL); + g_object_unref (dest); + } + + g_list_free (gfiles_list); + /* Here we must do what's necessary for the callback */ + undo_redo_done_transfer_callback (NULL, action); + } + break; + case CAJA_UNDOSTACK_CHANGEGROUP: + file = caja_file_get_by_uri (action->target_uri); + caja_file_set_group (file, + action->original_group_name_or_id, + undo_redo_done_rename_callback, action); + g_object_unref (file); + break; + case CAJA_UNDOSTACK_CHANGEOWNER: + file = caja_file_get_by_uri (action->target_uri); + caja_file_set_owner (file, + action->original_user_name_or_id, + undo_redo_done_rename_callback, action); + g_object_unref (file); + break; + case CAJA_UNDOSTACK_DELETE: + default: + priv->undo_redo_flag = FALSE; + break; /* We shouldn't be here */ + } + } + + (*cb) ((gpointer) parent_view); +} + +/** **************************************************************** + * Adds an operation to the stack + ** ****************************************************************/ +void +caja_undostack_manager_add_action (CajaUndoStackManager * manager, + CajaUndoStackActionData * action) +{ + CajaUndoStackManagerPrivate *priv = manager->priv; + + if (!action) + return; + + if (!(action && action->isValid)) { + free_undostack_action ((gpointer) action, NULL); + return; + } + + action->manager = manager; + + g_mutex_lock (priv->mutex); + + stack_push_action (priv, action); + + g_mutex_unlock (priv->mutex); + + do_menu_update (manager); + +} + +/** **************************************************************** + * Callback after emptying the trash + ** ****************************************************************/ +void +caja_undostack_manager_trash_has_emptied (CajaUndoStackManager * + manager) +{ + CajaUndoStackManagerPrivate *priv = manager->priv; + + /* Clear actions from the oldest to the newest move to trash */ + + g_mutex_lock (priv->mutex); + + clear_redo_actions (priv); + + /* Search newest move to trash */ + guint i; + guint length = g_queue_get_length (priv->stack); + guint newest_move_to_trash_position = -1; + CajaUndoStackActionData *action = NULL; + + for (i = 0; i < length; i++) { + action = (CajaUndoStackActionData *) + g_queue_peek_nth (priv->stack, i); + if (action->type == CAJA_UNDOSTACK_MOVETOTRASH) { + newest_move_to_trash_position = i; + break; + } + } + + if (newest_move_to_trash_position >= 0) { + guint to_clear = length - newest_move_to_trash_position; + stack_clear_n_oldest (priv->stack, to_clear); + } + + g_mutex_unlock (priv->mutex); +} + +/** **************************************************************** + * Returns the modification time for the given file (used for undo trash) + ** ****************************************************************/ +guint64 +caja_undostack_manager_get_file_modification_time (GFile * file) +{ + GFileInfo *info; + guint64 mtime; + + 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; +} + +/** **************************************************************** + * Returns a new undo data container + ** ****************************************************************/ +CajaUndoStackActionData * +caja_undostack_manager_data_new (CajaUndoStackActionType type, + gint items_count) +{ + CajaUndoStackActionData *data = + g_slice_new0 (CajaUndoStackActionData); + data->type = type; + data->count = items_count; + + if (type == CAJA_UNDOSTACK_MOVETOTRASH) { + data->trashed = + g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + } else if (type == CAJA_UNDOSTACK_RECURSIVESETPERMISSIONS) { + data->original_permissions = + g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + } + + return data; +} + +/** **************************************************************** + * Sets the source directory + ** ****************************************************************/ +void +caja_undostack_manager_data_set_src_dir (CajaUndoStackActionData * + data, GFile * src) +{ + if (!data) + return; + + data->src_dir = src; +} + +/** **************************************************************** + * Sets the destination directory + ** ****************************************************************/ +void +caja_undostack_manager_data_set_dest_dir (CajaUndoStackActionData * + data, GFile * dest) +{ + if (!data) + return; + + data->dest_dir = dest; +} + +/** **************************************************************** + * Pushes an origin, target pair in an existing undo data container + ** ****************************************************************/ +void caja_undostack_manager_data_add_origin_target_pair + (CajaUndoStackActionData * data, GFile * origin, GFile * target) +{ + + if (!data) + return; + + char *src_relative = g_file_get_relative_path (data->src_dir, origin); + data->sources = g_list_append (data->sources, src_relative); + char *dest_relative = g_file_get_relative_path (data->dest_dir, target); + data->destinations = g_list_append (data->destinations, dest_relative); + + data->isValid = TRUE; +} + +/** **************************************************************** + * Pushes an trashed file with modification time in an existing undo data container + ** ****************************************************************/ +void +caja_undostack_manager_data_add_trashed_file (CajaUndoStackActionData + * data, GFile * file, guint64 mtime) +{ + + if (!data) + return; + + guint64 *modificationTime; + modificationTime = (guint64 *) malloc (sizeof (guint64)); + *modificationTime = mtime; + + char *originalURI = g_file_get_uri (file); + + g_hash_table_insert (data->trashed, originalURI, modificationTime); + + data->isValid = TRUE; +} + +/** **************************************************************** + * Pushes a recursive permission change data in an existing undo data container + ** ****************************************************************/ +void caja_undostack_manager_data_add_file_permissions + (CajaUndoStackActionData * data, GFile * file, guint32 permission) +{ + + if (!data) + return; + + guint32 *currentPermission; + currentPermission = (guint32 *) malloc (sizeof (guint32)); + *currentPermission = permission; + + char *originalURI = g_file_get_uri (file); + + g_hash_table_insert (data->original_permissions, originalURI, + currentPermission); + + data->isValid = TRUE; +} + +/** **************************************************************** + * Sets the original file permission in an existing undo data container + ** ****************************************************************/ +void caja_undostack_manager_data_set_file_permissions + (CajaUndoStackActionData * data, char *uri, + guint32 current_permissions, guint32 new_permissions) +{ + + if (!data) + return; + + data->target_uri = uri; + + data->current_permissions = current_permissions; + data->new_permissions = new_permissions; + + data->isValid = TRUE; +} + +/** **************************************************************** + * Sets the change owner information in an existing undo data container + ** ****************************************************************/ +void caja_undostack_manager_data_set_owner_change_information + (CajaUndoStackActionData * data, char *uri, + const char *current_user, const char *new_user) +{ + + if (!data) + return; + + data->target_uri = uri; + + data->original_user_name_or_id = g_strdup (current_user); + data->new_user_name_or_id = g_strdup (new_user); + + data->isValid = TRUE; +} + +/** **************************************************************** + * Sets the change group information in an existing undo data container + ** ****************************************************************/ +void caja_undostack_manager_data_set_group_change_information + (CajaUndoStackActionData * data, char *uri, + const char *current_group, const char *new_group) +{ + + if (!data) + return; + + data->target_uri = uri; + + data->original_group_name_or_id = g_strdup (current_group); + data->new_group_name_or_id = g_strdup (new_group); + + data->isValid = TRUE; +} + +/** **************************************************************** + * Sets the permission change mask + ** ****************************************************************/ +void caja_undostack_manager_data_set_recursive_permissions + (CajaUndoStackActionData * data, guint32 file_permissions, + guint32 file_mask, guint32 dir_permissions, guint32 dir_mask) +{ + + if (!data) + return; + + data->file_permissions = file_permissions; + data->file_mask = file_mask; + data->dir_permissions = dir_permissions; + data->dir_mask = dir_mask; + + data->isValid = TRUE; +} + +/** **************************************************************** + * Sets create file information + ** ****************************************************************/ +void +caja_undostack_manager_data_set_create_data (CajaUndoStackActionData * + data, char *target_uri, char *template) +{ + + if (!data) + return; + + data->template = g_strdup (template); + data->target_uri = g_strdup (target_uri); + + data->isValid = TRUE; +} + +/** **************************************************************** + * Sets rename information + ** ****************************************************************/ +void caja_undostack_manager_data_set_rename_information + (CajaUndoStackActionData * data, GFile * old_file, GFile * new_file) +{ + + if (!data) + return; + + data->old_uri = g_file_get_uri (old_file); + data->new_uri = g_file_get_uri (new_file); + + data->isValid = TRUE; +} + +/* ***************************************************************** + Private methods (nothing to see here, move along) + ***************************************************************** */ + +static CajaUndoStackActionData * +stack_scroll_right (CajaUndoStackManagerPrivate * priv) +{ + gpointer data = NULL; + + if (!can_undo (priv)) + return NULL; + + data = g_queue_peek_nth (priv->stack, priv->index); + if (priv->index < g_queue_get_length (priv->stack)) { + priv->index++; + } + + return data; +} + +/** ---------------------------------------------------------------- */ +static CajaUndoStackActionData * +stack_scroll_left (CajaUndoStackManagerPrivate * priv) +{ + gpointer data = NULL; + + if (!can_redo (priv)) + return NULL; + + priv->index--; + data = g_queue_peek_nth (priv->stack, priv->index); + + return data; +} + +/** ---------------------------------------------------------------- */ +static void +stack_clear_n_oldest (GQueue * stack, guint n) +{ + CajaUndoStackActionData *action; + guint i; + + for (i = 0; i < n; i++) { + action = (CajaUndoStackActionData *) + g_queue_pop_tail (stack); + if (action->locked) { + action->freed = TRUE; + } else { + free_undostack_action (action, NULL); + } + } +} + +/** ---------------------------------------------------------------- */ +static void +stack_fix_size (CajaUndoStackManagerPrivate * priv) +{ + guint length = g_queue_get_length (priv->stack); + + if (length > priv->undo_levels) { + if (priv->index > (priv->undo_levels + 1)) { + /* If the index will fall off the stack + * move it back to the maximum position */ + priv->index = priv->undo_levels + 1; + } + stack_clear_n_oldest (priv->stack, length - (priv->undo_levels)); + } +} + +/** ---------------------------------------------------------------- */ +static void +clear_redo_actions (CajaUndoStackManagerPrivate * priv) +{ + while (priv->index > 0) { + CajaUndoStackActionData *head = (CajaUndoStackActionData *) + g_queue_pop_head (priv->stack); + free_undostack_action (head, NULL); + priv->index--; + } +} + +/** ---------------------------------------------------------------- */ +static void +stack_push_action (CajaUndoStackManagerPrivate * priv, + CajaUndoStackActionData * action) +{ + guint length; + + clear_redo_actions (priv); + + g_queue_push_head (priv->stack, (gpointer) action); + length = g_queue_get_length (priv->stack); + + if (length > priv->undo_levels) { + stack_fix_size (priv); + } +} + +/** ---------------------------------------------------------------- */ +static gchar * +get_first_target_short_name (CajaUndoStackActionData * action) +{ + GList *targets_first; + gchar *file_name; + + targets_first = g_list_first (action->destinations); + file_name = (gchar *) g_strdup (targets_first->data); + + return file_name; +} + +/** ---------------------------------------------------------------- */ +static gchar * +get_undo_description (CajaUndoStackActionData * action) +{ + gchar *description = NULL; + gchar *source = NULL; + guint count; + + if (action != NULL) { + if (action->undo_description == NULL) { + if (action->src_dir) { + source = g_file_get_path (action->src_dir); + } + count = action->count; + switch (action->type) { + case CAJA_UNDOSTACK_COPY: + if (count != 1) { + description = g_strdup_printf (_("Delete %d copied items"), count); + } else { + gchar *name = get_first_target_short_name (action); + description = g_strdup_printf (_("Delete '%s'"), name); + g_free (name); + } + break; + case CAJA_UNDOSTACK_DUPLICATE: + if (count != 1) { + description = + g_strdup_printf (_("Delete %d duplicated items"), count); + } else { + gchar *name = get_first_target_short_name (action); + description = g_strdup_printf (_("Delete '%s'"), name); + g_free (name); + } + break; + case CAJA_UNDOSTACK_MOVE: + if (count != 1) { + description = + g_strdup_printf (_ + ("Move %d items back to '%s'"), count, source); + } else { + gchar *name = get_first_target_short_name (action); + description = + g_strdup_printf (_("Move '%s' back to '%s'"), name, source); + g_free (name); + } + break; + case CAJA_UNDOSTACK_RENAME: + { + char *from_name = get_uri_basename (action->new_uri); + char *to_name = get_uri_basename (action->old_uri); + description = + g_strdup_printf (_("Rename '%s' as '%s'"), from_name, to_name); + g_free (from_name); + g_free (to_name); + } + break; + case CAJA_UNDOSTACK_CREATEFILEFROMTEMPLATE: + case CAJA_UNDOSTACK_CREATEEMPTYFILE: + case CAJA_UNDOSTACK_CREATEFOLDER: + { + char *name = get_uri_basename (action->target_uri); + description = g_strdup_printf (_("Delete '%s'"), name); + g_free (name); + } + break; + case CAJA_UNDOSTACK_MOVETOTRASH: + { + count = g_hash_table_size (action->trashed); + if (count != 1) { + description = + g_strdup_printf (_("Restore %d items from trash"), count); + } else { + GList *keys = g_hash_table_get_keys (action->trashed); + GList *first = g_list_first (keys); + char *item = (char *) first->data; + char *name = get_uri_basename (item); + char *orig_path = get_uri_parent_path (item); + description = + g_strdup_printf (_("Restore '%s' to '%s'"), name, orig_path); + g_free (name); + g_free (orig_path); + g_list_free (keys); + } + } + break; + case CAJA_UNDOSTACK_RESTOREFROMTRASH: + { + if (count != 1) { + description = + g_strdup_printf (_("Move %d items back to trash"), count); + } else { + gchar *name = get_first_target_short_name (action); + description = g_strdup_printf (_("Move '%s' back to trash"), name); + g_free (name); + } + } + break; + case CAJA_UNDOSTACK_CREATELINK: + { + if (count != 1) { + description = + g_strdup_printf (_("Delete links to %d items"), count); + } else { + gchar *name = get_first_target_short_name (action); + description = g_strdup_printf (_("Delete link to '%s'"), name); + g_free (name); + } + } + break; + case CAJA_UNDOSTACK_RECURSIVESETPERMISSIONS: + { + char *name = g_file_get_path (action->dest_dir); + description = + g_strdup_printf (_ + ("Restore original permissions of items enclosed in '%s'"), name); + g_free (name); + } + break; + case CAJA_UNDOSTACK_SETPERMISSIONS: + { + char *name = get_uri_basename (action->target_uri); + description = + g_strdup_printf (_("Restore original permissions of '%s'"), name); + g_free (name); + } + break; + case CAJA_UNDOSTACK_CHANGEGROUP: + { + char *name = get_uri_basename (action->target_uri); + description = + g_strdup_printf (_ + ("Restore group of '%s' to '%s'"), + name, action->original_group_name_or_id); + g_free (name); + } + break; + case CAJA_UNDOSTACK_CHANGEOWNER: + { + char *name = get_uri_basename (action->target_uri); + description = + g_strdup_printf (_ + ("Restore owner of '%s' to '%s'"), + name, action->original_user_name_or_id); + g_free (name); + } + break; + default: + break; + } + if (source) { + g_free (source); + } + action->undo_description = description; + } else { + return action->undo_description; + } + } + + return description; +} + +/** ---------------------------------------------------------------- */ +static gchar * +get_redo_description (CajaUndoStackActionData * action) +{ + gchar *description = NULL; + gchar *destination = NULL; + guint count; + + if (action != NULL) { + if (action->redo_description == NULL) { + if (action->dest_dir) { + destination = g_file_get_path (action->dest_dir); + } + count = action->count; + switch (action->type) { + case CAJA_UNDOSTACK_COPY: + if (count != 1) { + description = + g_strdup_printf (_ + ("Copy %d items to '%s'"), count, destination); + } else { + gchar *name = get_first_target_short_name (action); + description = + g_strdup_printf (_("Copy '%s' to '%s'"), name, destination); + g_free (name); + } + break; + case CAJA_UNDOSTACK_DUPLICATE: + if (count != 1) { + description = + g_strdup_printf (_ + ("Duplicate of %d items in '%s'"), count, destination); + } else { + gchar *name = get_first_target_short_name (action); + description = + g_strdup_printf (_ + ("Duplicate '%s' in '%s'"), name, destination); + g_free (name); + } + break; + case CAJA_UNDOSTACK_MOVE: + if (count != 1) { + description = + g_strdup_printf (_ + ("Move %d items to '%s'"), count, destination); + } else { + gchar *name = get_first_target_short_name (action); + description = + g_strdup_printf (_("Move '%s' to '%s'"), name, destination); + g_free (name); + } + break; + case CAJA_UNDOSTACK_RENAME: + { + char *from_name = get_uri_basename (action->old_uri); + char *to_name = get_uri_basename (action->new_uri); + description = + g_strdup_printf (_("Rename '%s' as '%s'"), from_name, to_name); + g_free (from_name); + g_free (to_name); + } + break; + case CAJA_UNDOSTACK_CREATEFILEFROMTEMPLATE: + { + char *name = get_uri_basename (action->target_uri); + description = + g_strdup_printf (_("Create new file '%s' from template "), name); + g_free (name); + } + break; + case CAJA_UNDOSTACK_CREATEEMPTYFILE: + { + char *name = get_uri_basename (action->target_uri); + description = g_strdup_printf (_("Create an empty file '%s'"), name); + g_free (name); + } + break; + case CAJA_UNDOSTACK_CREATEFOLDER: + { + char *name = get_uri_basename (action->target_uri); + description = g_strdup_printf (_("Create a new folder '%s'"), name); + g_free (name); + } + break; + case CAJA_UNDOSTACK_MOVETOTRASH: + { + count = g_hash_table_size (action->trashed); + if (count != 1) { + description = g_strdup_printf (_("Move %d items to trash"), count); + } else { + GList *keys = g_hash_table_get_keys (action->trashed); + GList *first = g_list_first (keys); + char *item = (char *) first->data; + char *name = get_uri_basename (item); + description = g_strdup_printf (_("Move '%s' to trash"), name); + g_free (name); + g_list_free (keys); + } + } + break; + case CAJA_UNDOSTACK_RESTOREFROMTRASH: + { + if (count != 1) { + description = + g_strdup_printf (_("Restore %d items from trash"), count); + } else { + gchar *name = get_first_target_short_name (action); + description = g_strdup_printf (_("Restore '%s' from trash"), name); + g_free (name); + } + } + break; + case CAJA_UNDOSTACK_CREATELINK: + { + if (count != 1) { + description = + g_strdup_printf (_("Create links to %d items"), count); + } else { + gchar *name = get_first_target_short_name (action); + description = g_strdup_printf (_("Create link to '%s'"), name); + g_free (name); + } + } + break; + case CAJA_UNDOSTACK_RECURSIVESETPERMISSIONS: + { + char *name = g_file_get_path (action->dest_dir); + description = + g_strdup_printf (_("Set permissions of items enclosed in '%s'"), + name); + g_free (name); + } + break; + case CAJA_UNDOSTACK_SETPERMISSIONS: + { + char *name = get_uri_basename (action->target_uri); + description = g_strdup_printf (_("Set permissions of '%s'"), name); + g_free (name); + } + break; + case CAJA_UNDOSTACK_CHANGEGROUP: + { + char *name = get_uri_basename (action->target_uri); + description = + g_strdup_printf (_ + ("Set group of '%s' to '%s'"), + name, action->new_group_name_or_id); + g_free (name); + } + break; + case CAJA_UNDOSTACK_CHANGEOWNER: + { + char *name = get_uri_basename (action->target_uri); + description = + g_strdup_printf (_ + ("Set owner of '%s' to '%s'"), name, action->new_user_name_or_id); + g_free (name); + } + break; + default: + break; + } + if (destination) { + g_free (destination); + } + action->redo_description = description; + } else { + return action->redo_description; + } + } + + return description; +} + +/** ---------------------------------------------------------------- */ +static gchar * +get_undo_label (CajaUndoStackActionData * action) +{ + gchar *label = NULL; + guint count; + + if (action != NULL) { + if (action->undo_label == NULL) { + count = action->count; + switch (action->type) { + case CAJA_UNDOSTACK_COPY: + label = g_strdup_printf (ngettext + ("_Undo copy of %d item", + "_Undo copy of %d items", count), count); + break; + case CAJA_UNDOSTACK_DUPLICATE: + label = g_strdup_printf (ngettext + ("_Undo duplicate of %d item", + "_Undo duplicate of %d items", count), count); + break; + case CAJA_UNDOSTACK_MOVE: + label = g_strdup_printf (ngettext + ("_Undo move of %d item", + "_Undo move of %d items", count), count); + break; + case CAJA_UNDOSTACK_RENAME: + label = g_strdup_printf (ngettext + ("_Undo rename of %d item", + "_Undo rename of %d items", count), count); + break; + case CAJA_UNDOSTACK_CREATEEMPTYFILE: + label = g_strdup_printf (_("_Undo creation of an empty file")); + break; + case CAJA_UNDOSTACK_CREATEFILEFROMTEMPLATE: + label = g_strdup_printf (_("_Undo creation of a file from template")); + break; + case CAJA_UNDOSTACK_CREATEFOLDER: + label = g_strdup_printf (ngettext + ("_Undo creation of %d folder", + "_Undo creation of %d folders", count), count); + break; + case CAJA_UNDOSTACK_MOVETOTRASH: + label = g_strdup_printf (ngettext + ("_Undo move to trash of %d item", + "_Undo move to trash of %d items", count), count); + break; + case CAJA_UNDOSTACK_RESTOREFROMTRASH: + label = g_strdup_printf (ngettext + ("_Undo restore from trash of %d item", + "_Undo restore from trash of %d items", count), count); + break; + case CAJA_UNDOSTACK_CREATELINK: + label = g_strdup_printf (ngettext + ("_Undo create link to %d item", + "_Undo create link to %d items", count), count); + break; + case CAJA_UNDOSTACK_DELETE: + label = g_strdup_printf (ngettext + ("_Undo delete of %d item", + "_Undo delete of %d items", count), count); + break; + case CAJA_UNDOSTACK_RECURSIVESETPERMISSIONS: + label = g_strdup_printf (ngettext + ("Undo recursive change permissions of %d item", + "Undo recursive change permissions of %d items", + count), count); + break; + case CAJA_UNDOSTACK_SETPERMISSIONS: + label = g_strdup_printf (ngettext + ("Undo change permissions of %d item", + "Undo change permissions of %d items", count), count); + break; + case CAJA_UNDOSTACK_CHANGEGROUP: + label = g_strdup_printf (ngettext + ("Undo change group of %d item", + "Undo change group of %d items", count), count); + break; + case CAJA_UNDOSTACK_CHANGEOWNER: + label = g_strdup_printf (ngettext + ("Undo change owner of %d item", + "Undo change owner of %d items", count), count); + break; + default: + break; + } + action->undo_label = label; + } else { + return action->undo_label; + } + } + + return label; +} + +/** ---------------------------------------------------------------- */ +static gchar * +get_redo_label (CajaUndoStackActionData * action) +{ + gchar *label = NULL; + guint count; + + if (action != NULL) { + if (action->redo_label == NULL) { + count = action->count; + switch (action->type) { + case CAJA_UNDOSTACK_COPY: + label = g_strdup_printf (ngettext + ("_Redo copy of %d item", + "_Redo copy of %d items", count), count); + break; + case CAJA_UNDOSTACK_DUPLICATE: + label = g_strdup_printf (ngettext + ("_Redo duplicate of %d item", + "_Redo duplicate of %d items", count), count); + break; + case CAJA_UNDOSTACK_MOVE: + label = g_strdup_printf (ngettext + ("_Redo move of %d item", + "_Redo move of %d items", count), count); + break; + case CAJA_UNDOSTACK_RENAME: + label = g_strdup_printf (ngettext + ("_Redo rename of %d item", + "_Redo rename of %d items", count), count); + break; + case CAJA_UNDOSTACK_CREATEEMPTYFILE: + label = g_strdup_printf (_("_Redo creation of an empty file")); + break; + case CAJA_UNDOSTACK_CREATEFILEFROMTEMPLATE: + label = g_strdup_printf (_("_Redo creation of a file from template")); + break; + case CAJA_UNDOSTACK_CREATEFOLDER: + label = g_strdup_printf (ngettext + ("_Redo creation of %d folder", + "_Redo creation of %d folders", count), count); + break; + case CAJA_UNDOSTACK_MOVETOTRASH: + label = g_strdup_printf (ngettext + ("_Redo move to trash of %d item", + "_Redo move to trash of %d items", count), count); + break; + case CAJA_UNDOSTACK_RESTOREFROMTRASH: + label = g_strdup_printf (ngettext + ("_Redo restore from trash of %d item", + "_Redo restore from trash of %d items", count), count); + break; + case CAJA_UNDOSTACK_CREATELINK: + label = g_strdup_printf (ngettext + ("_Redo create link to %d item", + "_Redo create link to %d items", count), count); + break; + case CAJA_UNDOSTACK_DELETE: + label = g_strdup_printf (ngettext + ("_Redo delete of %d item", + "_Redo delete of %d items", count), count); + break; + case CAJA_UNDOSTACK_RECURSIVESETPERMISSIONS: + label = g_strdup_printf (ngettext + ("Redo recursive change permissions of %d item", + "Redo recursive change permissions of %d items", + count), count); + break; + case CAJA_UNDOSTACK_SETPERMISSIONS: + label = g_strdup_printf (ngettext + ("Redo change permissions of %d item", + "Redo change permissions of %d items", count), count); + break; + case CAJA_UNDOSTACK_CHANGEGROUP: + label = g_strdup_printf (ngettext + ("Redo change group of %d item", + "Redo change group of %d items", count), count); + break; + case CAJA_UNDOSTACK_CHANGEOWNER: + label = g_strdup_printf (ngettext + ("Redo change owner of %d item", + "Redo change owner of %d items", count), count); + break; + default: + break; + } + action->redo_label = label; + } else { + return action->redo_label; + } + } + + return label; +} + +/** ---------------------------------------------------------------- */ +static void +undo_redo_done_transfer_callback (GHashTable * debuting_uris, gpointer data) +{ + CajaUndoStackActionData *action; + + action = (CajaUndoStackActionData *) data; + + /* If the action needed to be freed but was locked, free now */ + if (action->freed) { + free_undostack_action (action, NULL); + } else { + action->locked = FALSE; + } + + /* Update menus */ + do_menu_update (action->manager); +} + +/** ---------------------------------------------------------------- */ +static void +undo_redo_done_delete_callback (GHashTable * + debuting_uris, gboolean user_cancel, gpointer callback_data) +{ + undo_redo_done_transfer_callback (debuting_uris, callback_data); +} + +/** ---------------------------------------------------------------- */ +static void +undo_redo_done_create_callback (GFile * new_file, gpointer callback_data) +{ + undo_redo_done_transfer_callback (NULL, callback_data); +} + +/** ---------------------------------------------------------------- */ +static void +undo_redo_op_callback (gpointer callback_data) +{ + undo_redo_done_transfer_callback (NULL, callback_data); +} + +/** ---------------------------------------------------------------- */ +static void +undo_redo_done_rename_callback (CajaFile * file, + GFile * result_location, GError * error, gpointer callback_data) +{ + undo_redo_done_transfer_callback (NULL, callback_data); +} + +/** ---------------------------------------------------------------- */ +static void +free_undostack_action (gpointer data, gpointer user_data) +{ + CajaUndoStackActionData *action = (CajaUndoStackActionData *) data; + + if (!action) + return; + + g_free (action->template); + g_free (action->target_uri); + g_free (action->old_uri); + g_free (action->new_uri); + + g_free (action->undo_label); + g_free (action->undo_description); + g_free (action->redo_label); + g_free (action->redo_description); + + g_free (action->original_group_name_or_id); + g_free (action->original_user_name_or_id); + g_free (action->new_group_name_or_id); + g_free (action->new_user_name_or_id); + + if (action->sources) { + g_list_foreach (action->sources, (GFunc) g_free, NULL); + g_list_free (action->sources); + } + if (action->destinations) { + g_list_foreach (action->destinations, (GFunc) g_free, NULL); + g_list_free (action->destinations); + } + + if (action->trashed) { + g_hash_table_destroy (action->trashed); + } + + if (action->original_permissions) { + g_hash_table_destroy (action->original_permissions); + } + + if (action->src_dir) + g_object_unref (action->src_dir); + if (action->dest_dir) + g_object_unref (action->dest_dir); + + if (action) + g_slice_free (CajaUndoStackActionData, action); +} + +/** ---------------------------------------------------------------- */ +static void +undostack_dispose_all (GQueue * queue) +{ + g_queue_foreach (queue, free_undostack_action, NULL); +} + +/** ---------------------------------------------------------------- */ +static gboolean +can_undo (CajaUndoStackManagerPrivate * priv) +{ + return (get_next_undo_action (priv) != NULL); +} + +/** ---------------------------------------------------------------- */ +static gboolean +can_redo (CajaUndoStackManagerPrivate * priv) +{ + return (get_next_redo_action (priv) != NULL); +} + +/** ---------------------------------------------------------------- */ +static CajaUndoStackActionData * +get_next_redo_action (CajaUndoStackManagerPrivate * priv) +{ + if (g_queue_is_empty (priv->stack)) { + return NULL; + } + + if (priv->index == 0) { + /* ... no redo actions */ + return NULL; + } + + CajaUndoStackActionData *action = g_queue_peek_nth (priv->stack, + priv->index - 1); + + if (action->locked) { + return NULL; + } else { + return action; + } +} + +/** ---------------------------------------------------------------- */ +static CajaUndoStackActionData * +get_next_undo_action (CajaUndoStackManagerPrivate * priv) +{ + if (g_queue_is_empty (priv->stack)) { + return NULL; + } + + guint stack_size = g_queue_get_length (priv->stack); + + if (priv->index == stack_size) { + return NULL; + } + + CajaUndoStackActionData *action = g_queue_peek_nth (priv->stack, + priv->index); + + if (action->locked) { + return NULL; + } else { + return action; + } +} + +/** ---------------------------------------------------------------- */ +static void +do_menu_update (CajaUndoStackManager * manager) +{ + + if (!manager) + return; + + CajaUndoStackActionData *action; + CajaUndoStackManagerPrivate *priv = manager->priv; + CajaUndoStackMenuData *data = g_slice_new0 (CajaUndoStackMenuData); + + g_mutex_lock (priv->mutex); + + action = get_next_undo_action (priv); + data->undo_label = get_undo_label (action); + data->undo_description = get_undo_description (action); + + action = get_next_redo_action (priv); + + data->redo_label = get_redo_label (action); + data->redo_description = get_redo_description (action); + + g_mutex_unlock (priv->mutex); + + /* Update menus */ + g_signal_emit_by_name (manager, "request-menu-update", data); + + /* Free the signal data */ + // Note: we do not own labels and descriptions, they are part of the action. + g_slice_free (CajaUndoStackMenuData, data); +} + +/** ---------------------------------------------------------------- */ +static GList * +construct_gfile_list (const GList * urilist, GFile * parent) +{ + const GList *l; + GList *file_list = NULL; + GFile *file; + + for (l = urilist; l != NULL; l = l->next) { + file = g_file_get_child (parent, l->data); + file_list = g_list_append (file_list, file); + } + + return file_list; +} + +/** ---------------------------------------------------------------- */ +static GList * +construct_gfile_list_from_uri (char *uri) +{ + GList *file_list = NULL; + GFile *file; + + file = g_file_new_for_uri (uri); + file_list = g_list_append (file_list, file); + + return file_list; +} + +/** ---------------------------------------------------------------- */ +static GList * +uri_list_to_gfile_list (GList * urilist) +{ + const GList *l; + GList *file_list = NULL; + GFile *file; + + for (l = urilist; l != NULL; l = l->next) { + file = g_file_new_for_uri (l->data); + file_list = g_list_append (file_list, file); + } + + return file_list; +} + +/** ---------------------------------------------------------------- */ +static char * +get_uri_basename (char *uri) +{ + GFile *f = g_file_new_for_uri (uri); + char *basename = g_file_get_basename (f); + g_object_unref (f); + return basename; +} + +/** ---------------------------------------------------------------- */ +static char * +get_uri_parent (char *uri) +{ + GFile *f = g_file_new_for_uri (uri); + GFile *p = g_file_get_parent (f); + char *parent = g_file_get_uri (p); + g_object_unref (f); + g_object_unref (p); + return parent; +} + +/** ---------------------------------------------------------------- */ +static char * +get_uri_parent_path (char *uri) +{ + GFile *f = g_file_new_for_uri (uri); + GFile *p = g_file_get_parent (f); + char *parent = g_file_get_path (p); + g_object_unref (f); + g_object_unref (p); + return parent; +} + +/** ---------------------------------------------------------------- */ +static GHashTable * +retrieve_files_to_restore (GHashTable * trashed) +{ + GFileEnumerator *enumerator; + GFileInfo *info; + GFile *trash; + GFile *item; + guint64 mtime_item; + guint64 *mtime; + char *origpath; + GFile *origfile; + char *origuri; + gpointer lookupvalue; + GHashTable *to_restore; + + 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 + ",trash::orig-path", G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS, FALSE, NULL); + + mtime = 0; + if (enumerator) { + 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, "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) { + item = g_file_get_child (trash, g_file_info_get_name (info)); /* File in the trash */ + g_hash_table_insert (to_restore, item, origuri); + } + } else { + g_free (origuri); + } + + } + g_file_enumerator_close (enumerator, FALSE, NULL); + g_object_unref (enumerator); + } + g_object_unref (trash); + + return to_restore; +} + +/** ---------------------------------------------------------------- */ diff --git a/libcaja-private/caja-undostack-manager.h b/libcaja-private/caja-undostack-manager.h new file mode 100644 index 00000000..6261d570 --- /dev/null +++ b/libcaja-private/caja-undostack-manager.h @@ -0,0 +1,184 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ + +/* CajaUndoStackManager - Manages undo of file operations (header) + * + * Copyright (C) 2007-2010 Amos Brocco + * Copyright (C) 2011 Stefano Karapetsas + * + * Authors: Amos Brocco , + * Stefano Karapetsas + * + * 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. + */ + +#ifndef CAJA_UNDOSTACK_MANAGER_H +#define CAJA_UNDOSTACK_MANAGER_H + +#include +#include +#include +#include + +/* Begin action structures */ + +typedef enum +{ + CAJA_UNDOSTACK_COPY, + CAJA_UNDOSTACK_DUPLICATE, + CAJA_UNDOSTACK_MOVE, + CAJA_UNDOSTACK_RENAME, + CAJA_UNDOSTACK_CREATEEMPTYFILE, + CAJA_UNDOSTACK_CREATEFILEFROMTEMPLATE, + CAJA_UNDOSTACK_CREATEFOLDER, + CAJA_UNDOSTACK_MOVETOTRASH, + CAJA_UNDOSTACK_CREATELINK, + CAJA_UNDOSTACK_DELETE, + CAJA_UNDOSTACK_RESTOREFROMTRASH, + CAJA_UNDOSTACK_SETPERMISSIONS, + CAJA_UNDOSTACK_RECURSIVESETPERMISSIONS, + CAJA_UNDOSTACK_CHANGEOWNER, + CAJA_UNDOSTACK_CHANGEGROUP +} CajaUndoStackActionType; + +typedef struct _CajaUndoStackActionData CajaUndoStackActionData; + +typedef struct _CajaUndoStackMenuData CajaUndoStackMenuData; + +struct _CajaUndoStackMenuData { + char* undo_label; + char* undo_description; + char* redo_label; + char* redo_description; +}; + +/* End action structures */ + +typedef void +(*CajaUndostackFinishCallback)(gpointer data); + +typedef struct _CajaUndoStackManagerPrivate CajaUndoStackManagerPrivate; + +typedef struct _CajaUndoStackManager +{ + GObject parent_instance; + + CajaUndoStackManagerPrivate* priv; + +} CajaUndoStackManager; + +typedef struct _CajaUndoStackManagerClass +{ + GObjectClass parent_class; + +} CajaUndoStackManagerClass; + +#define TYPE_CAJA_UNDOSTACK_MANAGER (caja_undostack_manager_get_type()) + +#define CAJA_UNDOSTACK_MANAGER(object) \ + (G_TYPE_CHECK_INSTANCE_CAST((object), TYPE_CAJA_UNDOSTACK_MANAGER, CajaUndoStackManager)) + +#define CAJA_UNDOSTACK_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), TYPE_CAJA_UNDOSTACK_MANAGER, CajaUndoStackManagerClass)) + +#define IS_CAJA_UNDOSTACK_MANAGER(object) \ + (G_TYPE_CHECK_INSTANCE_TYPE((object), TYPE_CAJA_UNDOSTACK_MANAGER)) + +#define IS_CAJA_UNDOSTACK_MANAGER_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), TYPE_CAJA_UNDOSTACK_MANAGER)) + +#define CAJA_UNDOSTACK_MANAGER_GET_CLASS(object) \ + (G_TYPE_INSTANCE_GET_CLASS((object), TYPE_CAJA_UNDOSTACK_MANAGER, CajaUndoStackManagerClass)) + +GType +caja_undostack_manager_get_type (void); + +void +caja_undostack_manager_add_action(CajaUndoStackManager* manager, + CajaUndoStackActionData* action); + +void +caja_undostack_manager_undo(CajaUndoStackManager* manager, + GtkWidget *parent_view, CajaUndostackFinishCallback cb); + +void +caja_undostack_manager_redo(CajaUndoStackManager* manager, + GtkWidget *parent_view, CajaUndostackFinishCallback cb); + +CajaUndoStackActionData* +caja_undostack_manager_data_new(CajaUndoStackActionType type, + gint items_count); + +gboolean +caja_undostack_manager_is_undo_redo(CajaUndoStackManager* manager); + +void +caja_undostack_manager_trash_has_emptied(CajaUndoStackManager* manager); + +CajaUndoStackManager* +caja_undostack_manager_instance(void); + +void +caja_undostack_manager_data_set_src_dir(CajaUndoStackActionData* data, + GFile* src); + +void +caja_undostack_manager_data_set_dest_dir(CajaUndoStackActionData* data, + GFile* dest); + +void +caja_undostack_manager_data_add_origin_target_pair( + CajaUndoStackActionData* data, GFile* origin, GFile* target); + +void +caja_undostack_manager_data_set_create_data( + CajaUndoStackActionData* data, char* target_uri, char* template_uri); + +void +caja_undostack_manager_data_set_rename_information( + CajaUndoStackActionData* data, GFile* old_file, GFile* new_file); + +guint64 +caja_undostack_manager_get_file_modification_time(GFile* file); + +void +caja_undostack_manager_data_add_trashed_file( + CajaUndoStackActionData* data, GFile* file, guint64 mtime); + +void +caja_undostack_manager_request_menu_update(CajaUndoStackManager* manager); + +void +caja_undostack_manager_data_add_file_permissions( + CajaUndoStackActionData* data, GFile* file, guint32 permission); + +void +caja_undostack_manager_data_set_recursive_permissions( + CajaUndoStackActionData* data, guint32 file_permissions, guint32 file_mask, + guint32 dir_permissions, guint32 dir_mask); + +void +caja_undostack_manager_data_set_file_permissions( + CajaUndoStackActionData* data, char* uri, guint32 current_permissions, guint32 new_permissions); + +void +caja_undostack_manager_data_set_owner_change_information( + CajaUndoStackActionData* data, char* uri, const char* current_user, const char* new_user); + +void +caja_undostack_manager_data_set_group_change_information( + CajaUndoStackActionData* data, char* uri, const char* current_group, const char* new_group); + +#endif /* CAJA_UNDOSTACK_MANAGER_H */ diff --git a/src/caja-shell-ui.xml b/src/caja-shell-ui.xml index 0c27d173..ce9268a2 100644 --- a/src/caja-shell-ui.xml +++ b/src/caja-shell-ui.xml @@ -21,8 +21,10 @@ + + - + diff --git a/src/caja-window-menus.c b/src/caja-window-menus.c index c11b2869..774bb7c9 100644 --- a/src/caja-window-menus.c +++ b/src/caja-window-menus.c @@ -273,6 +273,7 @@ action_stop_callback (GtkAction *action, caja_window_slot_stop_loading (slot); } +#ifdef TEXT_CHANGE_UNDO static void action_undo_callback (GtkAction *action, gpointer user_data) @@ -280,6 +281,7 @@ action_undo_callback (GtkAction *action, caja_undo_manager_undo (CAJA_WINDOW (user_data)->application->undo_manager); } +#endif static void action_home_callback (GtkAction *action, @@ -849,10 +851,12 @@ static const GtkActionEntry main_entries[] = NULL, N_("Edit Caja preferences"), G_CALLBACK (action_preferences_callback) }, + #ifdef TEXT_CHANGE_UNDO /* name, stock id, label */ { "Undo", NULL, N_("_Undo"), "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"), "Up", N_("Open the parent folder"), G_CALLBACK (action_up_callback) diff --git a/src/file-manager/caja-directory-view-ui.xml b/src/file-manager/caja-directory-view-ui.xml index 80b9e881..4e926fa0 100644 --- a/src/file-manager/caja-directory-view-ui.xml +++ b/src/file-manager/caja-directory-view-ui.xml @@ -58,6 +58,10 @@ + + + + diff --git a/src/file-manager/fm-actions.h b/src/file-manager/fm-actions.h index 077d82c1..17782322 100644 --- a/src/file-manager/fm-actions.h +++ b/src/file-manager/fm-actions.h @@ -105,6 +105,8 @@ #define FM_ACTION_STRETCH "Stretch" #define FM_ACTION_UNSTRETCH "Unstretch" #define FM_ACTION_ZOOM_ITEMS "Zoom Items" +#define FM_ACTION_UNDO "Undo" +#define FM_ACTION_REDO "Redo" #define FM_ACTION_SORT_TRASH_TIME "Sort by Trash Time" #endif /* FM_ACTIONS_H */ diff --git a/src/file-manager/fm-directory-view.c b/src/file-manager/fm-directory-view.c index a192aa58..a5504893 100644 --- a/src/file-manager/fm-directory-view.c +++ b/src/file-manager/fm-directory-view.c @@ -81,6 +81,7 @@ #include #include #include +#include /* Minimum starting update inverval */ #define UPDATE_INTERVAL_MIN 100 @@ -256,6 +257,13 @@ struct FMDirectoryViewDetails gboolean allow_moves; GdkPoint context_menu_position; + + gboolean undo_active; + gboolean redo_active; + gchar* undo_action_description; + gchar* undo_action_label; + gchar* redo_action_description; + gchar* redo_action_label; }; typedef struct { @@ -386,6 +394,22 @@ static inline void fm_directory_view_widget_to_file_operation_position (FMDirect static void fm_directory_view_widget_to_file_operation_position_xy (FMDirectoryView *view, int *x, int *y); +/* undo-related actions */ + +static void undo_redo_menu_update_callback (CajaUndoStackManager* manager, gpointer arg1, gpointer data); + +static void undo_update_menu (FMDirectoryView *view); + +static void finish_undoredo_callback (gpointer data); + +static void real_action_undo (FMDirectoryView *view); + +static void real_action_redo (FMDirectoryView *view); + +static void action_undo_callback (GtkAction *action, gpointer callback_data); + +static void action_redo_callback (GtkAction *action, gpointer callback_data); + EEL_CLASS_BOILERPLATE (FMDirectoryView, fm_directory_view, GTK_TYPE_SCROLLED_WINDOW) EEL_IMPLEMENT_MUST_OVERRIDE_SIGNAL (fm_directory_view, add_file) @@ -2042,6 +2066,21 @@ fm_directory_view_init (FMDirectoryView *view) sort_directories_first_changed_callback, view); eel_preferences_add_callback (CAJA_PREFERENCES_LOCKDOWN_COMMAND_LINE, lockdown_disable_command_line_changed_callback, view); + + /* Update undo actions stuff and connect signals from the undostack manager */ + view->details->undo_active = FALSE; + view->details->redo_active = FALSE; + view->details->undo_action_description = NULL; + view->details->undo_action_label = NULL; + view->details->redo_action_description = NULL; + view->details->redo_action_label = NULL; + + CajaUndoStackManager* manager = caja_undostack_manager_instance (); + + g_signal_connect_object (G_OBJECT(manager), "request-menu-update", + G_CALLBACK(undo_redo_menu_update_callback), view, 0); + + caja_undostack_manager_request_menu_update (caja_undostack_manager_instance()); } static void @@ -2770,6 +2809,10 @@ 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); @@ -3226,6 +3269,20 @@ schedule_changes (FMDirectoryView *view) g_timeout_add (UPDATE_INTERVAL_TIMEOUT_INTERVAL, changes_timeout_callback, view); } +static void +action_undo_callback (GtkAction *action, + gpointer callback_data) +{ + real_action_undo (FM_DIRECTORY_VIEW (callback_data)); +} + +static void +action_redo_callback (GtkAction *action, + gpointer callback_data) +{ + real_action_redo (FM_DIRECTORY_VIEW (callback_data)); +} + static void files_added_callback (CajaDirectory *directory, GList *files, @@ -6217,6 +6274,32 @@ action_paste_files_into_callback (GtkAction *action, } +static void +real_action_undo (FMDirectoryView *view) +{ + CajaUndoStackManager *manager = caja_undostack_manager_instance (); + + /* Disable menus because they are in an untrustworthy status */ + view->details->undo_active = FALSE; + view->details->redo_active = FALSE; + fm_directory_view_update_menus (view); + + caja_undostack_manager_undo (manager, GTK_WIDGET (view), finish_undoredo_callback); +} + +static void +real_action_redo (FMDirectoryView *view) +{ + CajaUndoStackManager *manager = caja_undostack_manager_instance (); + + /* Disable menus because they are in an untrustworthy status */ + view->details->undo_active = FALSE; + view->details->redo_active = FALSE; + fm_directory_view_update_menus (view); + + caja_undostack_manager_redo (manager, GTK_WIDGET (view), finish_undoredo_callback); +} + static void real_action_rename (FMDirectoryView *view, gboolean select_all) @@ -7260,6 +7343,15 @@ static const GtkActionEntry directory_view_entries[] = { /* label, accelerator */ N_("_Restore"), NULL, NULL, G_CALLBACK (action_restore_from_trash_callback) }, + /* name, stock id */ { FM_ACTION_UNDO, GTK_STOCK_UNDO, + /* label, accelerator */ N_("_Undo"), "Z", + /* tooltip */ N_("Undo the last action"), + G_CALLBACK (action_undo_callback) }, + /* name, stock id */ { FM_ACTION_REDO, GTK_STOCK_REDO, + /* label, accelerator */ N_("_Redo"), "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. @@ -8925,6 +9017,8 @@ real_update_menus (FMDirectoryView *view) real_update_menus_volumes (view, selection, selection_count); + undo_update_menu (view); + caja_file_list_free (selection); if (view->details->scripts_invalid) { @@ -9514,6 +9608,14 @@ metadata_for_files_in_directory_ready_callback (CajaDirectory *directory, finish_loading_if_all_metadata_loaded (view); } +static void +finish_undoredo_callback (gpointer data) +{ + FMDirectoryView *view; + + view = FM_DIRECTORY_VIEW (data); +} + char ** fm_directory_view_get_emblem_names_to_exclude (FMDirectoryView *view) { @@ -10934,3 +11036,72 @@ fm_directory_view_class_init (FMDirectoryViewClass *klass) klass->trash = real_trash; klass->delete = real_delete; } + +static void +undo_redo_menu_update_callback (CajaUndoStackManager* manager, gpointer arg, gpointer data) +{ + FMDirectoryView *view; + view = FM_DIRECTORY_VIEW (data); + + CajaUndoStackMenuData* menudata = (CajaUndoStackMenuData*) arg; + + g_free(view->details->undo_action_label); + g_free(view->details->undo_action_description); + g_free(view->details->redo_action_label); + g_free(view->details->redo_action_description); + + view->details->undo_active = menudata->undo_label ? TRUE : FALSE; + view->details->redo_active = menudata->redo_label ? TRUE : FALSE; + + view->details->undo_action_label = g_strdup (menudata->undo_label); + view->details->undo_action_description = g_strdup (menudata->undo_description); + view->details->redo_action_label = g_strdup (menudata->redo_label); + view->details->redo_action_description = g_strdup (menudata->redo_description); + + schedule_update_menus (view); +} + +static void +undo_update_menu (FMDirectoryView *view) +{ + GtkAction *action; + gboolean available = FALSE; + gchar* label; + gchar* tooltip; + + /* Update undo entry */ + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_UNDO); + available = view->details->undo_active; + if (available) { + label = view->details->undo_action_label; + tooltip = view->details->undo_action_description; + } else { + /* Reset to default info */ + label = _("Undo"); + tooltip = _("Undo the last action"); + } + g_object_set (action, + "label", label, + "tooltip", tooltip, + NULL); + gtk_action_set_sensitive (action, available); + + /* Update redo entry */ + action = gtk_action_group_get_action (view->details->dir_action_group, + FM_ACTION_REDO); + available = view->details->redo_active; + if (available) { + label = view->details->redo_action_label; + tooltip = view->details->redo_action_description; + } else { + /* Reset to default info */ + label = _("Redo"); + tooltip = _("Redo the last undone action"); + } + g_object_set (action, + "label", label, + "tooltip", tooltip, + NULL); + gtk_action_set_sensitive (action, available); +} -- cgit v1.2.1