[nautilus/wip/alexpandelea/batchRename] Add Batch Rename operation



commit 840481fa160c9d496b5f9057d9f73b12387c2bf2
Author: Alexandru Pandelea <alexandru pandelea gmail com>
Date:   Tue Jul 26 13:10:51 2016 +0300

    Add Batch Rename operation

 src/nautilus-batch-rename-utilities.c |   43 ++++++-
 src/nautilus-batch-rename-utilities.h |    3 +-
 src/nautilus-batch-rename.c           |  221 +++++++++++++++++++++---------
 src/nautilus-directory-private.h      |    2 +
 src/nautilus-directory.c              |   23 +++
 src/nautilus-directory.h              |    2 +
 src/nautilus-file-private.h           |    3 +
 src/nautilus-file-undo-operations.c   |  183 +++++++++++++++++++++++++
 src/nautilus-file-undo-operations.h   |   29 ++++
 src/nautilus-file.c                   |  243 ++++++++++++++++++++++++++++++++-
 src/nautilus-file.h                   |    4 +
 src/nautilus-files-view.c             |   21 +++-
 12 files changed, 702 insertions(+), 75 deletions(-)
---
diff --git a/src/nautilus-batch-rename-utilities.c b/src/nautilus-batch-rename-utilities.c
index c8efc08..da9bc8f 100644
--- a/src/nautilus-batch-rename-utilities.c
+++ b/src/nautilus-batch-rename-utilities.c
@@ -209,12 +209,14 @@ list_has_duplicates (NautilusBatchRename *dialog,
                      GList               *new_names,
                      GList               *selection,
                      GList               *parents_list,
-                     gboolean             same_parent)
+                     gboolean             same_parent,
+                     GCancellable        *cancellable)
 {
         GList *directory_files, *l1, *l2, *l3, *result;
         NautilusFile *file1, *file2;
-        GString *file_name1, *file_name2, *new_name;
+        GString *file_name1, *file_name2, *file_name3, *new_name;
         NautilusDirectory *parent;
+        gboolean is_renameable_desktop_file, have_conflict;
 
         result = NULL;
 
@@ -239,16 +241,33 @@ list_has_duplicates (NautilusBatchRename *dialog,
         l3 = selection;
 
         for (l1 = new_names; l1 != NULL; l1 = l1->next) {
+                if (g_cancellable_is_cancelled (cancellable)) {
+                        return NULL;
+                        g_list_free_full (result, g_free);
+                        break;
+                }
+
                 file1 = NAUTILUS_FILE (l3->data);
                 new_name = l1->data;
+                is_renameable_desktop_file = nautilus_file_is_mime_type (file1, "application/x-desktop");
+
+                have_conflict = FALSE;
+
+                if (!is_renameable_desktop_file && strstr (new_name->str, "/") != NULL) {
+                        result = g_list_prepend (result, strdup (new_name->str));
+
+                        continue;
+                }
+
 
                 g_string_erase (file_name1, 0, -1);
                 g_string_append (file_name1, nautilus_file_get_name (file1));
 
                 /* check for duplicate only if the name has changed */
                 if (!g_string_equal (new_name, file_name1)) {
+                        /* check with already existing files */
                         for (l2 = directory_files; l2 != NULL; l2 = l2->next) {
-                               file2 = NAUTILUS_FILE (l2->data);
+                                file2 = NAUTILUS_FILE (l2->data);
 
                                 g_string_erase (file_name2, 0, -1);
                                 g_string_append (file_name2, nautilus_file_get_name (file2));
@@ -256,14 +275,32 @@ list_has_duplicates (NautilusBatchRename *dialog,
                                 if (g_string_equal (new_name, file_name2) &&
                                     !file_name_changed (selection, new_names, new_name, NULL)) {
                                         result = g_list_prepend (result, strdup (new_name->str));
+                                        have_conflict = TRUE;
+
                                         break;
                                 }
                         }
+
+                        /* check with files that will result from the batch rename, unless
+                         * this file already has a conflict */
+                        if (!have_conflict)
+                                for (l2 = new_names; l2 != NULL; l2 = l2->next) {
+                                        file_name3 = l2->data;
+                                        if (l1 != l2 && g_string_equal (new_name, file_name3)) {
+                                                result = g_list_prepend (result, strdup (new_name->str));
+
+                                                break;
+                                        }
+                                }
                 }
 
                 l3 = l3->next;
         }
 
+        if (g_cancellable_is_cancelled (cancellable)) {
+                return NULL;
+        }
+
         g_string_free (file_name1, TRUE);
         g_string_free (file_name2, TRUE);
 
diff --git a/src/nautilus-batch-rename-utilities.h b/src/nautilus-batch-rename-utilities.h
index 087098f..7c51fe6 100644
--- a/src/nautilus-batch-rename-utilities.h
+++ b/src/nautilus-batch-rename-utilities.h
@@ -15,7 +15,8 @@ GList* list_has_duplicates                      (NautilusBatchRename         *di
                                                  GList                       *names,
                                                  GList                       *selection,
                                                  GList                       *parents_list,
-                                                 gboolean                     same_parent);
+                                                 gboolean                     same_parent,
+                                                 GCancellable                *cancellable);
 
 GList* nautilus_batch_rename_sort               (GList                       *selection,
                                                  SortingMode                  mode,
diff --git a/src/nautilus-batch-rename.c b/src/nautilus-batch-rename.c
index 8bfaeb2..0b3e879 100644
--- a/src/nautilus-batch-rename.c
+++ b/src/nautilus-batch-rename.c
@@ -55,8 +55,11 @@ struct _NautilusBatchRename
         GtkWidget               *conflict_up;
 
         GList                   *listbox_rows;
+        GList                   *listbox_labels_new;
+        GList                   *listbox_labels_old;
 
         GList                   *selection;
+        GList                   *new_names;
         NautilusBatchRenameMode  mode;
         NautilusDirectory       *model;
 
@@ -76,6 +79,9 @@ struct _NautilusBatchRename
         gint                     checked_parents;
         GList                   *duplicates;
         GList                   *distinct_parents;
+        GTask                   *task;
+        GCancellable            *cancellable;
+        gboolean                 checking_conflicts;
 
         GtkSizeGroup            *size_group1;
         GtkSizeGroup            *size_group2;
@@ -186,20 +192,7 @@ rename_files_on_names_accepted (NautilusBatchRename *dialog,
                                 GList               *new_names)
 {
         /* do the actual rename here */
-        GList *l1;
-        GList *l2;
-        GList *selection;
-        NautilusFile *file;
-        GString *new_name;
-
-        selection = dialog->selection;
-
-        for (l1 = selection, l2 = new_names; l1 != NULL && l2 != NULL; l1 = l1->next, l2 = l2->next) {
-                file = NAUTILUS_FILE (l1->data);
-                new_name = l2->data;
-
-                nautilus_rename_file (file, new_name->str, NULL, NULL);
-        }
+        nautilus_file_batch_rename (dialog->selection, new_names, NULL, NULL);
 
         gtk_window_close (GTK_WINDOW (dialog));
 }
@@ -250,6 +243,7 @@ create_row_for_label (NautilusBatchRename *dialog,
         label_new = g_object_new (GTK_TYPE_LABEL,
                                   "label",new_text,
                                   "hexpand", TRUE,
+                                  "selectable", TRUE,
                                   "xalign", 0.0,
                                   "margin-start", 6,
                                   NULL);
@@ -257,6 +251,7 @@ create_row_for_label (NautilusBatchRename *dialog,
         label_old = g_object_new (GTK_TYPE_LABEL,
                                   "label",old_text,
                                   "hexpand", TRUE,
+                                  "selectable", TRUE,
                                   "xalign", 0.0,
                                   "margin-start", 6,
                                   NULL);
@@ -264,14 +259,17 @@ create_row_for_label (NautilusBatchRename *dialog,
         gtk_label_set_ellipsize (GTK_LABEL (label_new), PANGO_ELLIPSIZE_END);
         gtk_label_set_ellipsize (GTK_LABEL (label_old), PANGO_ELLIPSIZE_END);
 
-        gtk_size_group_add_widget (dialog->size_group1, label_new);
-        gtk_size_group_add_widget (dialog->size_group2, label_old);
+        //gtk_size_group_add_widget (dialog->size_group1, label_new);
+        //gtk_size_group_add_widget (dialog->size_group2, label_old);
 
         gtk_box_pack_end (GTK_BOX (box), label_new, TRUE, FALSE, 0);
         gtk_box_pack_end (GTK_BOX (box), icon, TRUE, FALSE, 0);
         gtk_box_pack_end (GTK_BOX (box), label_old, TRUE, FALSE, 0);
         gtk_list_box_row_set_selectable (GTK_LIST_BOX_ROW (row), TRUE);
 
+        dialog->listbox_labels_new = g_list_append (dialog->listbox_labels_new, label_new);
+        dialog->listbox_labels_old = g_list_append (dialog->listbox_labels_old, label_old);
+
         gtk_container_add (GTK_CONTAINER (row), box);
         gtk_widget_show_all (row);
 
@@ -279,31 +277,21 @@ create_row_for_label (NautilusBatchRename *dialog,
 }
 
 static void
-fill_display_listbox (NautilusBatchRename *dialog,
-                      GList               *new_names)
+fill_display_listbox (NautilusBatchRename *dialog)
 {
         GtkWidget *row;
         GList *l1;
         GList *l2;
-        GList *l;
         NautilusFile *file;
         GString *new_name;
 
-        /* clear rows from listbox (if any) */
-        if (dialog->listbox_rows != NULL)
-                for (l = dialog->listbox_rows; l != NULL; l = l->next) {
-                        gtk_container_remove (GTK_CONTAINER (dialog->conflict_listbox),
-                                              GTK_WIDGET (l->data));
-                }
-
-        g_list_free (dialog->listbox_rows);
         dialog->listbox_rows = NULL;
         dialog->size_group1 = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
         dialog->size_group2 = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
 
         /* add rows to a list so that they can be removed when the renaming
          * result changes */
-        for (l1 = new_names, l2 = dialog->selection; l1 != NULL || l2 != NULL; l1 = l1->next, l2 = l2->next) 
{
+        for (l1 = dialog->new_names, l2 = dialog->selection; l1 != NULL || l2 != NULL; l1 = l1->next, l2 = 
l2->next) {
                 file = NAUTILUS_FILE (l2->data);
                 new_name = l1->data;
 
@@ -333,28 +321,28 @@ file_has_conflict (NautilusBatchRename *dialog,
 static void
 select_nth_conflict (NautilusBatchRename *dialog)
 {
-        GList *l, *new_names, *l2;
+        GList *l, *l2;
         GString *file_name, *display_text, *new_name;
         gint n, nth_conflict;
-        gint selected_row;
         NautilusFile *file;
-
-        selected_row = 0;
+        GtkAdjustment *adjustment;
+        GtkAllocation allocation;
 
         nth_conflict = dialog->selected_conflict;
         n = nth_conflict;
         l = g_list_nth (dialog->duplicates, n);
 
+        /* the conflict that has to be selected */
         file_name = g_string_new (l->data);
         display_text = g_string_new ("");
 
         n = 0;
 
-        new_names = batch_rename_get_new_names (dialog);
         l2 = dialog->selection;
 
-        for (l = new_names; l != NULL; l = l->next) {
+        for (l = dialog->new_names; l != NULL; l = l->next) {
                 file = NAUTILUS_FILE (l2->data);
+
                 new_name = l->data;
 
                 /* g_strcmp0 is used for not selecting a file that doesn't change
@@ -364,7 +352,9 @@ select_nth_conflict (NautilusBatchRename *dialog)
                     nth_conflict == 0)
                         break;
 
-                if (file_has_conflict (dialog, new_name))
+                /* a file can only have a conflict if it's name has changed */
+                if (g_strcmp0 (new_name->str, nautilus_file_get_name (file)) &&
+                    file_has_conflict (dialog, new_name))
                         nth_conflict--;
 
                 n++;
@@ -376,13 +366,22 @@ select_nth_conflict (NautilusBatchRename *dialog)
         gtk_list_box_select_row (GTK_LIST_BOX (dialog->conflict_listbox),
                                  l->data);
 
-        g_string_append_printf (display_text,
-                                "\"%s\" would conflict with an existing file.",
-                                file_name->str);
+        /* scroll to the selected row */
+        adjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (dialog->scrolled_window));
+        gtk_widget_get_allocation (GTK_WIDGET (l->data), &allocation);
+        gtk_adjustment_set_value (adjustment, (allocation.height + 1)*n);
+
+        if (strstr (file_name->str, "/") == NULL)
+                g_string_append_printf (display_text,
+                                        "\"%s\" would conflict with an existing file.",
+                                        file_name->str);
+        else
+                g_string_append_printf (display_text,
+                                        "\"%s\" has unallowed character '/'.",
+                                        file_name->str);
+
         gtk_label_set_label (GTK_LABEL (dialog->conflict_label),
                              display_text->str);
-
-        g_list_free_full (new_names, string_free);
 }
 
 static void
@@ -414,11 +413,31 @@ move_next_conflict_up (NautilusBatchRename *dialog)
 }
 
 static void
-update_conflicts (NautilusBatchRename *dialog,
-                  GList               *new_names)
+update_listbox (NautilusBatchRename *dialog)
 {
+        GList *l1, *l2;
+        NautilusFile *file;
+        gchar *old_name;
+        GtkLabel *label;
+        GString *new_name;
+
         /* Update listbox that shows the result of the renaming for each file */
-        fill_display_listbox (dialog, new_names);
+        for (l1 = dialog->new_names, l2 = dialog->listbox_labels_new; l1 != NULL && l2 != NULL; l1 = 
l1->next, l2 = l2->next) {
+                label = GTK_LABEL (l2->data);
+                new_name = l1->data;
+
+                gtk_label_set_label (label, new_name->str);
+        }
+
+        for (l1 = dialog->selection, l2 = dialog->listbox_labels_old; l1 != NULL && l2 != NULL; l1 = 
l1->next, l2 = l2->next) {
+                label = GTK_LABEL (l2->data);
+                file = NAUTILUS_FILE (l1->data);
+
+                old_name = nautilus_file_get_name (file);
+
+                gtk_label_set_label (label, old_name);
+        }
+
 
         /* check if there are name conflicts and display them if they exist */
         if (dialog->duplicates != NULL) {
@@ -456,16 +475,13 @@ check_conflict_for_file (NautilusBatchRename *dialog,
         NautilusFile *file1, *file2;
         GString *new_name, *file_name1, *file_name2;
         GList *l1, *l2, *l3;
-        GList *new_names;
-
-        new_names = batch_rename_get_new_names (dialog);
 
         file_name1 = g_string_new ("");
         file_name2 = g_string_new ("");
 
         current_directory = nautilus_directory_get_uri (directory);
 
-        for (l1 = dialog->selection, l2 = new_names; l1 != NULL && l2 != NULL; l1 = l1->next, l2 = l2->next) 
{
+        for (l1 = dialog->selection, l2 = dialog->new_names; l1 != NULL && l2 != NULL; l1 = l1->next, l2 = 
l2->next) {
                 file1 = NAUTILUS_FILE (l1->data);
 
                 g_string_erase (file_name1, 0, -1);
@@ -490,7 +506,7 @@ check_conflict_for_file (NautilusBatchRename *dialog,
                                 g_free (name);
 
                                 if (g_string_equal (new_name, file_name2) &&
-                                    !file_name_changed (dialog->selection, new_names, new_name, parent_uri)) 
{
+                                    !file_name_changed (dialog->selection, dialog->new_names, new_name, 
parent_uri)) {
                                         dialog->duplicates = g_list_prepend (dialog->duplicates,
                                                                              strdup (new_name->str));
                                         break;
@@ -502,8 +518,6 @@ check_conflict_for_file (NautilusBatchRename *dialog,
          * the listbox with the conflicts if it is. */
         if (dialog->checked_parents == g_list_length (dialog->distinct_parents) - 1) {
                 dialog->duplicates = g_list_reverse (dialog->duplicates);
-
-                update_conflicts (dialog, new_names);
         }
 
         dialog->checked_parents++;
@@ -512,9 +526,66 @@ check_conflict_for_file (NautilusBatchRename *dialog,
 }
 
 static void
+list_has_duplicates_callback (GObject *object,
+                              GAsyncResult *res,
+                              gpointer user_data)
+{
+        NautilusBatchRename *dialog;
+
+        dialog = NAUTILUS_BATCH_RENAME (object);
+
+        dialog->checking_conflicts = FALSE;
+
+        update_listbox (dialog);
+}
+
+static void
+list_has_duplicates_async_thread (GTask        *task,
+                                  gpointer      object,
+                                  gpointer      task_data,
+                                  GCancellable *cancellable)
+{
+        NautilusBatchRename *dialog;
+
+        dialog = NAUTILUS_BATCH_RENAME (object);
+
+        dialog->duplicates = list_has_duplicates (dialog,
+                                                  dialog->model,
+                                                  dialog->new_names,
+                                                  dialog->selection,
+                                                  dialog->distinct_parents,
+                                                  dialog->same_parent,
+                                                  cancellable);
+
+        g_task_return_pointer (task, object, NULL);
+
+}
+
+static void
+list_has_duplicates_async (NautilusBatchRename *dialog,
+                           int                  io_priority,
+                           GAsyncReadyCallback  callback,
+                           gpointer             user_data)
+{
+        if (dialog->checking_conflicts == TRUE)
+                g_cancellable_cancel (dialog->cancellable);
+
+        dialog->cancellable = g_cancellable_new ();
+
+        dialog->checking_conflicts = TRUE;
+        dialog->task = g_task_new (dialog, dialog->cancellable, callback, user_data);
+
+        g_task_set_priority (dialog->task, io_priority);
+        g_task_run_in_thread (dialog->task, list_has_duplicates_async_thread);
+
+        g_object_unref (dialog->task);
+}
+
+static void
 file_names_widget_entry_on_changed (NautilusBatchRename *dialog)
 {
-        GList *new_names;
+        if (dialog->cancellable != NULL)
+                g_cancellable_cancel (dialog->cancellable);
 
         if(dialog->selection == NULL)
                 return;
@@ -524,33 +595,42 @@ file_names_widget_entry_on_changed (NautilusBatchRename *dialog)
                 dialog->duplicates = NULL;
         }
 
-        new_names = batch_rename_get_new_names(dialog);
-        dialog->checked_parents = 0;
-        dialog->duplicates = list_has_duplicates (dialog,
-                                                  dialog->model,
-                                                  new_names,
-                                                  dialog->selection,
-                                                  dialog->distinct_parents,
-                                                  dialog->same_parent);
+        if (dialog->new_names != NULL)
+                g_list_free_full (dialog->new_names, string_free);
 
-        update_conflicts (dialog, new_names);
+        dialog->new_names = batch_rename_get_new_names (dialog);
+        dialog->checked_parents = 0;
 
-        g_list_free_full (new_names, string_free);
+        list_has_duplicates_async (dialog,
+                                   G_PRIORITY_DEFAULT,
+                                   list_has_duplicates_callback,
+                                   NULL);
 }
 
 static void
 file_names_widget_on_activate (NautilusBatchRename *dialog)
 {
-        GList *new_names;
+        GList *l;
+
+        /* wait for checking conflicts to finish, to be sure that
+         * the rename can actually take place */
+        while (dialog->checking_conflicts){
+
+        }
 
         if (!gtk_widget_is_sensitive (dialog->rename_button))
                 return;
 
-        new_names = batch_rename_get_new_names(dialog);
+        /* clear rows from listbox */
+        if (dialog->listbox_rows != NULL)
+                for (l = dialog->listbox_rows; l != NULL; l = l->next)
+                        gtk_container_remove (GTK_CONTAINER (dialog->conflict_listbox),
+                                              GTK_WIDGET (l->data));
+
+        g_list_free (dialog->listbox_rows);
 
         /* if names are all right call rename_files_on_names_accepted*/
-        rename_files_on_names_accepted (dialog, new_names);
-        g_list_free_full (new_names, string_free);
+        rename_files_on_names_accepted (dialog, dialog->new_names);
 }
 
 static void
@@ -673,6 +753,11 @@ nautilus_batch_rename_finalize (GObject *object)
                 g_hash_table_destroy (dialog->create_date);
 
         g_list_free_full (dialog->distinct_parents, g_free);
+        g_list_free_full (dialog->new_names, string_free);
+        g_list_free_full (dialog->duplicates, g_free);
+
+        g_object_unref (dialog->size_group1);
+        g_object_unref (dialog->size_group2);
 
         G_OBJECT_CLASS (nautilus_batch_rename_parent_class)->finalize (object);
 }
@@ -759,6 +844,8 @@ nautilus_batch_rename_new (GList *selection, NautilusDirectory *model, NautilusW
         /* update display text */
         file_names_widget_entry_on_changed (dialog);
 
+        fill_display_listbox (dialog);
+
         g_free (dialog_title);
 
         return GTK_WIDGET (dialog);
@@ -783,4 +870,8 @@ nautilus_batch_rename_init (NautilusBatchRename *self)
         gtk_label_set_ellipsize (GTK_LABEL (self->conflict_label), PANGO_ELLIPSIZE_END);
 
         self->duplicates = NULL;
+        self->new_names = NULL;
+        self->listbox_rows = NULL;
+
+        self->checking_conflicts = FALSE;
 }
\ No newline at end of file
diff --git a/src/nautilus-directory-private.h b/src/nautilus-directory-private.h
index dae1b96..0128fa0 100644
--- a/src/nautilus-directory-private.h
+++ b/src/nautilus-directory-private.h
@@ -185,6 +185,8 @@ void               nautilus_directory_emit_files_changed              (NautilusD
                                                                       GList                     
*changed_files);
 void               nautilus_directory_emit_change_signals             (NautilusDirectory         *directory,
                                                                       GList                     
*changed_files);
+void              nautilus_directory_emit_change_selection           (NautilusDirectory         *directory,
+                                                                      GList                     *selection);
 void               emit_change_signals_for_all_files                 (NautilusDirectory         *directory);
 void               emit_change_signals_for_all_files_in_all_directories (void);
 void               nautilus_directory_emit_done_loading               (NautilusDirectory         *directory);
diff --git a/src/nautilus-directory.c b/src/nautilus-directory.c
index 3595406..13ecb4a 100644
--- a/src/nautilus-directory.c
+++ b/src/nautilus-directory.c
@@ -44,6 +44,7 @@ enum {
        FILES_CHANGED,
        DONE_LOADING,
        LOAD_ERROR,
+       CHANGE_SELECTION,
        LAST_SIGNAL
 };
 
@@ -159,6 +160,15 @@ nautilus_directory_class_init (NautilusDirectoryClass *klass)
                                     G_TYPE_FILE,
                                     G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
 
+       signals[CHANGE_SELECTION] =
+               g_signal_new ("change-selection",
+                             G_TYPE_FROM_CLASS (object_class),
+                             G_SIGNAL_RUN_LAST,
+                             G_STRUCT_OFFSET (NautilusDirectoryClass, change_selection),
+                             NULL, NULL,
+                             g_cclosure_marshal_VOID__POINTER,
+                             G_TYPE_NONE, 1, G_TYPE_POINTER);
+
        klass->get_file_list = real_get_file_list;
        klass->is_editable = real_is_editable;
        klass->handles_location = real_handles_location;
@@ -885,6 +895,19 @@ nautilus_directory_emit_files_changed (NautilusDirectory *directory,
 }
 
 void
+nautilus_directory_emit_change_selection (NautilusDirectory *directory,
+                                         GList             *selection)
+{
+       nautilus_profile_start (NULL);
+       if (selection != NULL) {
+               g_signal_emit (directory,
+                              signals[CHANGE_SELECTION], 0,
+                              selection);
+       }
+       nautilus_profile_end (NULL);
+}
+
+void
 nautilus_directory_emit_change_signals (NautilusDirectory *directory,
                                             GList *changed_files)
 {
diff --git a/src/nautilus-directory.h b/src/nautilus-directory.h
index dc8c732..ca4fae7 100644
--- a/src/nautilus-directory.h
+++ b/src/nautilus-directory.h
@@ -103,6 +103,8 @@ typedef struct
        void     (* load_error)          (NautilusDirectory         *directory,
                                          GError                    *error);
 
+       void     (* change_selection)       (NautilusDirectory         *directory,
+                                            GList                     *selection);
        /*** Virtual functions for subclasses to override. ***/
        gboolean (* contains_file)       (NautilusDirectory         *directory,
                                          NautilusFile              *file);
diff --git a/src/nautilus-file-private.h b/src/nautilus-file-private.h
index c498651..f0ecf72 100644
--- a/src/nautilus-file-private.h
+++ b/src/nautilus-file-private.h
@@ -214,6 +214,9 @@ struct NautilusFileDetails
 
 typedef struct {
        NautilusFile *file;
+       GList *files;
+       gint renamed_files;
+       gint skipped_files;
        GCancellable *cancellable;
        NautilusFileOperationCallback callback;
        gpointer callback_data;
diff --git a/src/nautilus-file-undo-operations.c b/src/nautilus-file-undo-operations.c
index 25d90bb..0bcf281 100644
--- a/src/nautilus-file-undo-operations.c
+++ b/src/nautilus-file-undo-operations.c
@@ -977,6 +977,189 @@ nautilus_file_undo_info_rename_set_data_post (NautilusFileUndoInfoRename *self,
        self->priv->new_file = g_object_ref (new_file);
 }
 
+/* batch rename */
+G_DEFINE_TYPE (NautilusFileUndoInfoBatchRename, nautilus_file_undo_info_batch_rename, 
NAUTILUS_TYPE_FILE_UNDO_INFO);
+
+struct _NautilusFileUndoInfoBatchRenameDetails {
+        GList *old_files;
+        GList *new_files;
+        GList *old_display_names;
+        GList *new_display_names;
+};
+
+static void
+batch_rename_strings_func (NautilusFileUndoInfo *info,
+                           gchar               **undo_label,
+                           gchar               **undo_description,
+                           gchar               **redo_label,
+                           gchar               **redo_description)
+{
+        NautilusFileUndoInfoBatchRename *self = NAUTILUS_FILE_UNDO_INFO_BATCH_RENAME (info);
+
+        *undo_description = g_strdup_printf (_("Batch rename '%d' files"),
+                                             g_list_length (self->priv->new_files));
+        *redo_description = g_strdup_printf (_("Batch rename '%d' files"),
+                                             g_list_length (self->priv->new_files));
+
+        *undo_label = g_strdup (_("_Undo Batch rename"));
+        *redo_label = g_strdup (_("_Redo Batch rename"));
+}
+
+static void
+batch_rename_redo_func (NautilusFileUndoInfo *info,
+                        GtkWindow *parent_window)
+{
+        NautilusFileUndoInfoBatchRename *self = NAUTILUS_FILE_UNDO_INFO_BATCH_RENAME (info);
+
+        GList *l, *files;
+        NautilusFile *file;
+        GFile *old_file;
+
+        files = NULL;
+
+        for (l = self->priv->old_files; l != NULL; l = l->next) {
+                old_file = l->data;
+
+                file = nautilus_file_get (old_file);
+                files = g_list_append (files, file);
+        }
+
+        nautilus_file_batch_rename (files, self->priv->new_display_names, file_undo_info_operation_callback, 
self);
+}
+
+static void
+batch_rename_undo_func (NautilusFileUndoInfo *info,
+                        GtkWindow *parent_window)
+{
+        NautilusFileUndoInfoBatchRename *self = NAUTILUS_FILE_UNDO_INFO_BATCH_RENAME (info);
+
+        GList *l, *files;
+        NautilusFile *file;
+        GFile *new_file;
+
+        files = NULL;
+
+        for (l = self->priv->new_files; l != NULL; l = l->next) {
+                new_file = l->data;
+
+                file = nautilus_file_get (new_file);
+                files = g_list_append (files, file);
+        }
+
+        nautilus_file_batch_rename (files, self->priv->old_display_names, file_undo_info_operation_callback, 
self);
+}
+
+static void
+nautilus_file_undo_info_batch_rename_init (NautilusFileUndoInfoBatchRename *self)
+{
+        self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, nautilus_file_undo_info_batch_rename_get_type (),
+                                                  NautilusFileUndoInfoBatchRenameDetails);
+}
+
+static void
+nautilus_file_undo_info_batch_rename_finalize (GObject *obj)
+{
+        GList *l;
+        GFile *file;
+        GString *string;
+        NautilusFileUndoInfoBatchRename *self = NAUTILUS_FILE_UNDO_INFO_BATCH_RENAME (obj);
+
+        for (l = self->priv->new_files; l != NULL; l = l->next){
+                file = l->data;
+
+                g_clear_object (&file);
+        }
+
+        for (l = self->priv->old_files; l != NULL; l = l->next){
+                file = l->data;
+
+                g_clear_object (&file);
+        }
+
+        for (l = self->priv->new_display_names; l != NULL; l = l->next) {
+                string = l->data;
+
+                g_string_free (string, TRUE);
+        }
+
+        for (l = self->priv->old_display_names; l != NULL; l = l->next) {
+                string = l->data;
+
+                g_string_free (string, TRUE);
+        }
+
+        g_list_free (self->priv->new_files);
+        g_list_free (self->priv->old_files);
+        g_list_free (self->priv->new_display_names);
+        g_list_free (self->priv->old_display_names);
+
+        G_OBJECT_CLASS (nautilus_file_undo_info_batch_rename_parent_class)->finalize (obj);
+}
+
+static void
+nautilus_file_undo_info_batch_rename_class_init (NautilusFileUndoInfoBatchRenameClass *klass)
+{
+        GObjectClass *oclass = G_OBJECT_CLASS (klass);
+        NautilusFileUndoInfoClass *iclass = NAUTILUS_FILE_UNDO_INFO_CLASS (klass);
+
+        oclass->finalize = nautilus_file_undo_info_batch_rename_finalize;
+
+        iclass->undo_func = batch_rename_undo_func;
+        iclass->redo_func = batch_rename_redo_func;
+        iclass->strings_func = batch_rename_strings_func;
+
+        g_type_class_add_private (klass, sizeof (NautilusFileUndoInfoBatchRenameDetails));
+}
+
+NautilusFileUndoInfo *
+nautilus_file_undo_info_batch_rename_new (gint item_count)
+{
+        return g_object_new (NAUTILUS_TYPE_FILE_UNDO_INFO_BATCH_RENAME,
+                             "op-type", NAUTILUS_FILE_UNDO_OP_BATCH_RENAME,
+                             "item-count", item_count,
+                             NULL);
+}
+
+void
+nautilus_file_undo_info_batch_rename_set_data_pre (NautilusFileUndoInfoBatchRename *self,
+                                                   GList                           *old_files)
+{
+        GList *l;
+        GString *old_name;
+        GFile *file;
+
+        self->priv->old_files = old_files;
+        self->priv->old_display_names = NULL;
+
+        for (l = old_files; l != NULL; l = l->next) {
+                file = l->data;
+
+                old_name = g_string_new (g_file_get_basename (file));
+
+                self->priv->old_display_names = g_list_append (self->priv->old_display_names, old_name);
+        }
+}
+
+void
+nautilus_file_undo_info_batch_rename_set_data_post (NautilusFileUndoInfoBatchRename *self,
+                                                    GList                           *new_files)
+{
+        GList *l;
+        GString *new_name;
+        GFile *file;
+
+        self->priv->new_files = new_files;
+        self->priv->new_display_names = NULL;
+
+        for (l = new_files; l != NULL; l = l->next) {
+                file = l->data;
+
+                new_name = g_string_new (g_file_get_basename (file));
+
+                self->priv->new_display_names = g_list_append (self->priv->new_display_names, new_name);
+        }
+}
+
 /* trash */
 G_DEFINE_TYPE (NautilusFileUndoInfoTrash, nautilus_file_undo_info_trash, NAUTILUS_TYPE_FILE_UNDO_INFO)
 
diff --git a/src/nautilus-file-undo-operations.h b/src/nautilus-file-undo-operations.h
index cec1c7c..8a153c3 100644
--- a/src/nautilus-file-undo-operations.h
+++ b/src/nautilus-file-undo-operations.h
@@ -33,6 +33,7 @@ typedef enum {
        NAUTILUS_FILE_UNDO_OP_DUPLICATE,
        NAUTILUS_FILE_UNDO_OP_MOVE,
        NAUTILUS_FILE_UNDO_OP_RENAME,
+       NAUTILUS_FILE_UNDO_OP_BATCH_RENAME,
        NAUTILUS_FILE_UNDO_OP_CREATE_EMPTY_FILE,
        NAUTILUS_FILE_UNDO_OP_CREATE_FILE_FROM_TEMPLATE,
        NAUTILUS_FILE_UNDO_OP_CREATE_FOLDER,
@@ -185,6 +186,34 @@ void nautilus_file_undo_info_rename_set_data_pre (NautilusFileUndoInfoRename *se
 void nautilus_file_undo_info_rename_set_data_post (NautilusFileUndoInfoRename *self,
                                                   GFile                      *new_file);
 
+/* batch rename */
+#define NAUTILUS_TYPE_FILE_UNDO_INFO_BATCH_RENAME         (nautilus_file_undo_info_batch_rename_get_type ())
+#define NAUTILUS_FILE_UNDO_INFO_BATCH_RENAME(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), 
NAUTILUS_TYPE_FILE_UNDO_INFO_BATCH_RENAME, NautilusFileUndoInfoBatchRename))
+#define NAUTILUS_FILE_UNDO_INFO_BATCH_RENAME_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), 
NAUTILUS_TYPE_FILE_UNDO_INFO_BATCH_RENAME, NautilusFileUndoInfoBatchRenameClass))
+#define NAUTILUS_IS_FILE_UNDO_INFO_BATCH_RENAME(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), 
NAUTILUS_TYPE_FILE_UNDO_INFO_BATCH_RENAME))
+#define NAUTILUS_IS_FILE_UNDO_INFO_BATCH_RENAME_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), 
NAUTILUS_TYPE_FILE_UNDO_INFO_BATCH_RENAME))
+#define NAUTILUS_FILE_UNDO_INFO_BATCH_RENAME_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), 
NAUTILUS_TYPE_FILE_UNDO_INFO_BATCH_RENAME, NautilusFileUndoInfoBatchRenameClass))
+
+typedef struct _NautilusFileUndoInfoBatchRename      NautilusFileUndoInfoBatchRename;
+typedef struct _NautilusFileUndoInfoBatchRenameClass NautilusFileUndoInfoBatchRenameClass;
+typedef struct _NautilusFileUndoInfoBatchRenameDetails NautilusFileUndoInfoBatchRenameDetails;
+
+struct _NautilusFileUndoInfoBatchRename {
+       NautilusFileUndoInfo parent;
+       NautilusFileUndoInfoBatchRenameDetails *priv;
+};
+
+struct _NautilusFileUndoInfoBatchRenameClass {
+       NautilusFileUndoInfoClass parent_class;
+};
+
+GType nautilus_file_undo_info_batch_rename_get_type (void) G_GNUC_CONST;
+NautilusFileUndoInfo *nautilus_file_undo_info_batch_rename_new (gint item_count);
+void nautilus_file_undo_info_batch_rename_set_data_pre (NautilusFileUndoInfoBatchRename *self,
+                                                       GList                           *old_files);
+void nautilus_file_undo_info_batch_rename_set_data_post (NautilusFileUndoInfoBatchRename *self,
+                                                        GList                           *new_files);
+
 /* trash */
 #define NAUTILUS_TYPE_FILE_UNDO_INFO_TRASH         (nautilus_file_undo_info_trash_get_type ())
 #define NAUTILUS_FILE_UNDO_INFO_TRASH(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), 
NAUTILUS_TYPE_FILE_UNDO_INFO_TRASH, NautilusFileUndoInfoTrash))
diff --git a/src/nautilus-file.c b/src/nautilus-file.c
index e3cbdfe..928b90b 100644
--- a/src/nautilus-file.c
+++ b/src/nautilus-file.c
@@ -1647,15 +1647,37 @@ nautilus_file_operation_new (NautilusFile *file,
 static void
 nautilus_file_operation_remove (NautilusFileOperation *op)
 {
+        GList *l;
+        NautilusFile *file;
+
        op->file->details->operations_in_progress = g_list_remove
                (op->file->details->operations_in_progress, op);
+
+        if (g_list_length (op->files) != 0) {
+                for (l = op->files; l != NULL; l = l->next) {
+                        file = NAUTILUS_FILE (l->data);
+                        file->details->operations_in_progress = g_list_remove
+                                (file->details->operations_in_progress, op);
+                }
+        }
 }
 
 void
 nautilus_file_operation_free (NautilusFileOperation *op)
 {
+        NautilusFile *file;
+        GList *l;
+
        nautilus_file_operation_remove (op);
-       nautilus_file_unref (op->file);
+
+        if (g_list_length (op->files) == 0)
+                nautilus_file_unref (op->file);
+        else
+                for (l = op->files; l != NULL; l = l->next) {
+                        file = NAUTILUS_FILE (l->data);
+                        nautilus_file_unref (file);
+                }
+
        g_object_unref (op->cancellable);
        if (op->free_data) {
                op->free_data (op->data);
@@ -1668,6 +1690,21 @@ nautilus_file_operation_free (NautilusFileOperation *op)
 
        g_free (op);
 }
+typedef struct {
+        NautilusDirectory *directory;
+        GList *files;
+} SelectionData;
+
+static gboolean
+change_selection_callback (gpointer user_data)
+{
+        SelectionData *data;
+
+        data = user_data;
+        nautilus_directory_emit_change_selection (data->directory, data->files);
+
+        return FALSE;
+}
 
 void
 nautilus_file_operation_complete (NautilusFileOperation *op,
@@ -1678,8 +1715,20 @@ nautilus_file_operation_complete (NautilusFileOperation *op,
         * This makes it easier for some clients who see the "reverting"
         * as "changing back".
         */
+        SelectionData *data;
+
        nautilus_file_operation_remove (op);
-       nautilus_file_changed (op->file);
+
+        if (g_list_length (op->files) == 0) {
+                nautilus_file_changed (op->file);
+        } else {
+                data = g_malloc (sizeof (SelectionData*));
+                data->directory = op->file->details->directory;
+                data->files = op->files;
+
+                g_idle_add (change_selection_callback, data);
+        }
+
        if (op->callback) {
                (* op->callback) (op->file, result_file, error, op->callback_data);
        }
@@ -1752,7 +1801,16 @@ rename_get_info_callback (GObject *source_object,
                
                g_object_unref (new_info);
        }
-       nautilus_file_operation_complete (op, NULL, error);
+
+        op->renamed_files++;
+
+        if (op->renamed_files + op->skipped_files == g_list_length (op->files)) {
+                nautilus_file_operation_complete (op, NULL, error);
+        }
+
+        if (g_list_length (op->files) == 0)
+                nautilus_file_operation_complete (op, NULL, error);
+
        if (error) {
                g_error_free (error);
        }
@@ -1778,7 +1836,6 @@ rename_callback (GObject *source_object,
                        nautilus_file_undo_info_rename_set_data_post (NAUTILUS_FILE_UNDO_INFO_RENAME 
(op->undo_info),
                                                                      new_file);
                }
-
                g_file_query_info_async (new_file,
                                         NAUTILUS_FILE_DEFAULT_ATTRIBUTES,
                                         0,
@@ -1811,6 +1868,170 @@ nautilus_file_rename (NautilusFile                  *file,
                                                                  callback_data);
 }
 
+static void
+real_batch_rename (GList                         *files,
+                   GList                         *new_names,
+                   NautilusFileOperationCallback  callback,
+                   gpointer                       callback_data)
+{
+        GList *l1, *l2, *old_files, *new_files;
+        NautilusFileOperation *op;
+        GFile *location;
+        GString *new_name;
+        NautilusFile *file;
+        GError *error;
+        GFile *new_file;
+        gboolean is_renameable_desktop_file, success, name_changed;
+        gchar *uri, *desktop_name, *old_name;
+
+        error = NULL;
+        old_files = NULL;
+        new_files = NULL;
+
+        /* Set up a batch renaming operation. */
+        op = nautilus_file_operation_new (files->data, callback, callback_data);
+        op->files = files;
+        op->renamed_files = 0;
+        op->skipped_files = 0;
+
+        for (l1 = files->next; l1 != NULL; l1 = l1->next) {
+                file = NAUTILUS_FILE (l1->data);
+
+                file->details->operations_in_progress = g_list_prepend 
(file->details->operations_in_progress,
+                                                                        op);
+        }
+
+        for (l1 = files, l2 = new_names; l1 != NULL && l2 != NULL; l1 = l1->next, l2 = l2->next) {
+                file = NAUTILUS_FILE (l1->data);
+                new_name = l2->data;
+
+                location = nautilus_file_get_location (file);
+                old_files = g_list_append (old_files, location);
+
+                is_renameable_desktop_file =
+                        is_desktop_file (file) && can_rename_desktop_file (file);
+
+                /* Test the name-hasn't-changed */
+                if (!is_renameable_desktop_file && name_is (file, new_name->str)) {
+                        new_file = nautilus_file_get_location (file);
+                        new_files = g_list_append (new_files, new_file);
+                        op->skipped_files++;
+
+                        continue;
+                }
+
+                /* Can't rename a file that's already gone.
+                 * We need to check this here because there may be a new
+                 * file with the same name.
+                 */
+                if (nautilus_file_is_gone (file)) {
+                        nautilus_file_changed (file);
+
+                        new_file = nautilus_file_get_location (file);
+                        new_files = g_list_append (new_files, new_file);
+                        op->skipped_files++;
+
+
+                        continue;
+                }
+
+                /* Self-owned files can't be renamed */
+                if (nautilus_file_is_self_owned (file)) {
+                        nautilus_file_changed (file);
+
+                        new_file = nautilus_file_get_location (file);
+                        new_files = g_list_append (new_files, new_file);
+                        op->skipped_files++;
+
+                        continue;
+                }
+
+                if (is_renameable_desktop_file) {
+                        /* Don't actually change the name if the new name is the same.
+                         * This helps for the vfolder method where this can happen and
+                         * we want to minimize actual changes
+                         */
+                        uri = nautilus_file_get_uri (file);
+                        old_name = nautilus_link_local_get_text (uri);
+                        if (old_name != NULL && strcmp (new_name->str, old_name) == 0) {
+                                success = TRUE;
+                                name_changed = FALSE;
+                        } else {
+                                success = nautilus_link_local_set_text (uri, new_name->str);
+                                name_changed = TRUE;
+                        }
+
+                        g_free (old_name);
+                        g_free (uri);
+
+                        if (!success) {
+                                new_file = nautilus_file_get_location (file);
+                                new_files = g_list_append (new_files, new_file);
+                                op->skipped_files++;
+
+                                continue;
+                        }
+
+                        new_name = g_string_append (new_name, ".desktop");
+                        desktop_name = g_strdelimit (new_name->str, "/", '-');
+                        new_name = g_string_insert (new_name, 0, desktop_name);
+
+                        g_free (desktop_name);
+
+                        if (name_is (file, new_name->str)) {
+                                if (name_changed) {
+                                        nautilus_file_invalidate_attributes (file,
+                                                                             NAUTILUS_FILE_ATTRIBUTE_INFO |
+                                                                             
NAUTILUS_FILE_ATTRIBUTE_LINK_INFO);
+                                }
+
+                                new_file = nautilus_file_get_location (file);
+                                new_files = g_list_append (new_files, new_file);
+                                op->skipped_files++;
+
+                                continue;
+                        }
+                }
+
+                g_assert (G_IS_FILE (location));
+
+                /* Do the renaming. */
+                new_file = g_file_set_display_name (location,
+                                                    new_name->str,
+                                                    op->cancellable,
+                                                    &error);
+
+                new_files = g_list_append (new_files, new_file);
+
+                op->file = file;
+                g_file_query_info_async (new_file,
+                                         NAUTILUS_FILE_DEFAULT_ATTRIBUTES,
+                                         0,
+                                         G_PRIORITY_DEFAULT,
+                                         op->cancellable,
+                                         rename_get_info_callback,
+                                         op);
+
+                if (error != NULL) {
+                        g_error_free (error);
+                        error = NULL;
+                }
+        }
+
+        /* Tell the undo manager a batch rename is taking place */
+        if (!nautilus_file_undo_manager_is_operating ()) {
+                op->undo_info = nautilus_file_undo_info_batch_rename_new (g_list_length (new_files));
+
+                nautilus_file_undo_info_batch_rename_set_data_pre (NAUTILUS_FILE_UNDO_INFO_BATCH_RENAME 
(op->undo_info),
+                                                                   old_files);
+
+                nautilus_file_undo_info_batch_rename_set_data_post (NAUTILUS_FILE_UNDO_INFO_BATCH_RENAME 
(op->undo_info),
+                                                                    new_files);
+
+                nautilus_file_undo_manager_set_action (op->undo_info);
+        }
+}
+
 gboolean
 nautilus_file_rename_handle_file_gone (NautilusFile                  *file,
                                        NautilusFileOperationCallback  callback,
@@ -1819,7 +2040,7 @@ nautilus_file_rename_handle_file_gone (NautilusFile                  *file,
        GError *error;
 
        if (nautilus_file_is_gone (file)) {
-               /* Claim that something changed even if the rename
+               /* Claim that something changed even if the rename
                 * failed. This makes it easier for some clients who
                 * see the "reverting" to the old name as "changing
                 * back".
@@ -1835,6 +2056,18 @@ nautilus_file_rename_handle_file_gone (NautilusFile                  *file,
   return FALSE;
 }
 
+void
+nautilus_file_batch_rename (GList                         *files,
+                            GList                         *new_names,
+                            NautilusFileOperationCallback  callback,
+                            gpointer                       callback_data)
+{
+        real_batch_rename (files,
+                           new_names,
+                           callback,
+                           callback_data);
+}
+
 static void
 real_rename (NautilusFile                  *file,
              const char                    *new_name,
diff --git a/src/nautilus-file.h b/src/nautilus-file.h
index 1050575..3c65a53 100644
--- a/src/nautilus-file.h
+++ b/src/nautilus-file.h
@@ -324,6 +324,10 @@ void                    nautilus_file_rename                            (Nautilu
                                                                         const char                     
*new_name,
                                                                         NautilusFileOperationCallback   
callback,
                                                                         gpointer                        
callback_data);
+void                    nautilus_file_batch_rename                      (GList                          
*files,
+                                                                         GList                          
*new_names,
+                                                                         NautilusFileOperationCallback   
callback,
+                                                                         gpointer                        
callback_data);
 void                    nautilus_file_cancel                            (NautilusFile                   
*file,
                                                                         NautilusFileOperationCallback   
callback,
                                                                         gpointer                        
callback_data);
diff --git a/src/nautilus-files-view.c b/src/nautilus-files-view.c
index 3f5ef6b..2abaa31 100644
--- a/src/nautilus-files-view.c
+++ b/src/nautilus-files-view.c
@@ -3928,6 +3928,18 @@ files_changed_callback (NautilusDirectory *directory,
 }
 
 static void
+change_selection_callback (NautilusDirectory *directory,
+                           GList             *selection,
+                           gpointer           callback_data)
+{
+        NautilusFilesView *view;
+
+        view = NAUTILUS_FILES_VIEW (callback_data);
+
+        nautilus_view_set_selection (view, selection);
+}
+
+static void
 done_loading_callback (NautilusDirectory *directory,
                        gpointer           callback_data)
 {
@@ -3944,7 +3956,6 @@ done_loading_callback (NautilusDirectory *directory,
                  */
                 unschedule_display_of_pending_files (view);
                 schedule_timeout_display_of_pending_files (view, UPDATE_INTERVAL_MIN);
-
                 remove_loading_floating_bar (view);
         }
         nautilus_profile_end (NULL);
@@ -4000,6 +4011,9 @@ nautilus_files_view_add_subdirectory (NautilusFilesView *view,
         g_signal_connect
                 (directory, "files-changed",
                  G_CALLBACK (files_changed_callback), view);
+        g_signal_connect
+                (directory, "change-selection",
+                 G_CALLBACK (change_selection_callback), view);
 
         view->details->subdirectory_list = g_list_prepend (
                                                            view->details->subdirectory_list, directory);
@@ -7208,6 +7222,11 @@ finish_loading (NautilusFilesView *view)
                 (view->details->model, "files-changed",
                  G_CALLBACK (files_changed_callback), view);
 
+        g_signal_connect (view->details->model,
+                          "change-selection",
+                          G_CALLBACK (change_selection_callback),
+                          view);
+
         nautilus_profile_end (NULL);
 }
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]