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/caja-undostack-manager.c | 1988 ++++++++++++++++++++++++++++++ 1 file changed, 1988 insertions(+) create mode 100644 libcaja-private/caja-undostack-manager.c (limited to 'libcaja-private/caja-undostack-manager.c') 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; +} + +/** ---------------------------------------------------------------- */ -- cgit v1.2.1