[nautilus/wip/csoriano/file-operations-rename: 1/2] Implement batch renaming
- From: Carlos Soriano Sánchez <csoriano src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [nautilus/wip/csoriano/file-operations-rename: 1/2] Implement batch renaming
- Date: Thu, 25 Aug 2016 13:50:18 +0000 (UTC)
commit faea7235010370838a30cd5dc9946361bee717ff
Author: Alexandru Pandelea <alexandru pandelea gmail com>
Date: Tue Aug 23 00:18:54 2016 +0300
Implement batch renaming
https://bugzilla.gnome.org/show_bug.cgi?id=768311
src/Makefile.am | 4 +
src/nautilus-batch-rename-dialog.c | 2496 ++++++++++++++++++++++
src/nautilus-batch-rename-dialog.h | 84 +
src/nautilus-batch-rename-utilities.c | 985 +++++++++
src/nautilus-batch-rename-utilities.h | 63 +
src/nautilus-file-private.h | 3 +
src/nautilus-file-undo-operations.c | 183 ++
src/nautilus-file-undo-operations.h | 29 +
src/nautilus-file-utilities.c | 16 +
src/nautilus-file-utilities.h | 2 +
src/nautilus-file.c | 429 +++-
src/nautilus-file.h | 4 +
src/nautilus-files-view.c | 26 +-
src/resources/css/Adwaita.css | 15 +
src/resources/nautilus.gresource.xml | 1 +
src/resources/ui/nautilus-batch-rename-dialog.ui | 495 +++++
16 files changed, 4738 insertions(+), 97 deletions(-)
---
diff --git a/src/Makefile.am b/src/Makefile.am
index c925f41..99edf8f 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -161,6 +161,8 @@ nautilus_no_main_sources = \
gtk/nautilusgtkplacesviewrowprivate.h \
nautilus-application.c \
nautilus-application.h \
+ nautilus-batch-rename-dialog.c \
+ nautilus-batch-rename-dialog.h \
nautilus-bookmark-list.c \
nautilus-bookmark-list.h \
nautilus-canvas-view.c \
@@ -214,6 +216,8 @@ nautilus_no_main_sources = \
nautilus-properties-window.h \
nautilus-query-editor.c \
nautilus-query-editor.h \
+ nautilus-batch-rename-utilities.c \
+ nautilus-batch-rename-utilities.h \
nautilus-search-popover.c \
nautilus-search-popover.h \
nautilus-self-check-functions.c \
diff --git a/src/nautilus-batch-rename-dialog.c b/src/nautilus-batch-rename-dialog.c
new file mode 100644
index 0000000..7faab4c
--- /dev/null
+++ b/src/nautilus-batch-rename-dialog.c
@@ -0,0 +1,2496 @@
+/* nautilus-batch-rename-dialog.c
+ *
+ * Copyright (C) 2016 Alexandru Pandelea <alexandru pandelea gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+
+#include "nautilus-batch-rename-dialog.h"
+#include "nautilus-file.h"
+#include "nautilus-error-reporting.h"
+#include "nautilus-batch-rename-utilities.h"
+
+#include <glib/gprintf.h>
+#include <glib.h>
+#include <string.h>
+#include <glib/gi18n.h>
+
+#define ADD_TEXT_ENTRY_SIZE 550
+#define REPLACE_ENTRY_SIZE 275
+#define TAG_UNAVAILABLE -2
+#define HAVE_CONFLICT 1
+
+struct _NautilusBatchRenameDialog
+{
+ GtkDialog parent;
+
+ GtkWidget *grid;
+ NautilusWindow *window;
+
+ GtkWidget *cancel_button;
+ GtkWidget *original_name_listbox;
+ GtkWidget *arrow_listbox;
+ GtkWidget *result_listbox;
+ GtkWidget *name_entry;
+ GtkWidget *rename_button;
+ GtkWidget *find_entry;
+ GtkWidget *mode_stack;
+ GtkWidget *replace_entry;
+ GtkWidget *format_mode_button;
+ GtkWidget *replace_mode_button;
+ GtkWidget *add_button;
+ GtkWidget *add_popover;
+ GtkWidget *numbering_order_label;
+ GtkWidget *numbering_label;
+ GtkWidget *scrolled_window;
+ GtkWidget *numbering_order_popover;
+ GtkWidget *numbering_order_button;
+ GtkWidget *conflict_box;
+ GtkWidget *conflict_label;
+ GtkWidget *conflict_down;
+ GtkWidget *conflict_up;
+
+ GList *original_name_listbox_rows;
+ GList *arrow_listbox_rows;
+ GList *result_listbox_rows;
+ GList *listbox_labels_new;
+ GList *listbox_labels_old;
+ GList *listbox_icons;
+ GtkSizeGroup *size_group;
+
+ GList *selection;
+ GList *new_names;
+ NautilusBatchRenameDialogMode mode;
+ NautilusDirectory *directory;
+
+ GActionGroup *action_group;
+
+ GMenu *numbering_order_menu;
+ GMenu *add_tag_menu;
+
+ GHashTable *create_date;
+ GList *selection_metadata;
+
+ /* check if all files in selection have the same parent */
+ gboolean same_parent;
+ /* the index of the currently selected conflict */
+ gint selected_conflict;
+ /* total conflicts number */
+ gint conflicts_number;
+
+ gint checked_parents;
+ GList *duplicates;
+ GList *distinct_parents;
+ GTask *conflicts_task;
+ GCancellable *conflict_cancellable;
+ gboolean checking_conflicts;
+
+ /* this hash table has information about the status
+ * of all tags: availability, if it's currently used
+ * and position */
+ GHashTable *tag_info_table;
+
+ GtkWidget *preselected_row1;
+ GtkWidget *preselected_row2;
+
+ gint row_height;
+ gboolean rename_clicked;
+};
+
+typedef struct
+{
+ gboolean available;
+ gboolean set;
+ gint position;
+} TagData;
+
+static void update_display_text (NautilusBatchRenameDialog *dialog);
+
+G_DEFINE_TYPE (NautilusBatchRenameDialog, nautilus_batch_rename_dialog, GTK_TYPE_DIALOG);
+
+static void
+add_numbering_order (GSimpleAction *action,
+ GVariant *value,
+ gpointer user_data)
+{
+ NautilusBatchRenameDialog *dialog;
+ const gchar *target_name;
+
+ dialog = NAUTILUS_BATCH_RENAME_DIALOG (user_data);
+
+ target_name = g_variant_get_string (value, NULL);
+
+ if (g_strcmp0 (target_name, "name-ascending") == 0) {
+ gtk_label_set_label (GTK_LABEL (dialog->numbering_order_label),
+ _("Original name (Ascending)"));
+ dialog->selection = nautilus_batch_rename_dialog_sort (dialog->selection,
+ ORIGINAL_ASCENDING,
+ NULL);
+ }
+
+ if (g_strcmp0 (target_name, "name-descending") == 0) {
+ gtk_label_set_label (GTK_LABEL (dialog->numbering_order_label),
+ _("Original name (Descending)"));
+ dialog->selection = nautilus_batch_rename_dialog_sort (dialog->selection,
+ ORIGINAL_DESCENDING,
+ NULL);
+ }
+
+ if (g_strcmp0 (target_name, "first-modified") == 0) {
+ gtk_label_set_label (GTK_LABEL (dialog->numbering_order_label),
+ _("First Modified"));
+ dialog->selection = nautilus_batch_rename_dialog_sort (dialog->selection,
+ FIRST_MODIFIED,
+ NULL);
+ }
+
+ if (g_strcmp0 (target_name, "last-modified") == 0) {
+ gtk_label_set_label (GTK_LABEL (dialog->numbering_order_label),
+ _("Last Modified"));
+ dialog->selection = nautilus_batch_rename_dialog_sort (dialog->selection,
+ LAST_MODIFIED,
+ NULL);
+ }
+
+ if (g_strcmp0 (target_name, "first-created") == 0) {
+ gtk_label_set_label (GTK_LABEL (dialog->numbering_order_label),
+ _("First Created"));
+ dialog->selection = nautilus_batch_rename_dialog_sort (dialog->selection,
+ FIRST_CREATED,
+ dialog->create_date);
+ }
+
+ if (g_strcmp0 (target_name, "last-created") == 0) {
+ gtk_label_set_label (GTK_LABEL (dialog->numbering_order_label),
+ _("Last Created"));
+ dialog->selection = nautilus_batch_rename_dialog_sort (dialog->selection,
+ LAST_CREATED,
+ dialog->create_date);
+ }
+
+ g_simple_action_set_state (action, value);
+
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->numbering_order_button), FALSE);
+
+ update_display_text (dialog);
+}
+
+static void
+add_original_file_name_tag (GSimpleAction *action,
+ GVariant *value,
+ gpointer user_data)
+{
+ NautilusBatchRenameDialog *dialog;
+ gint cursor_position;
+
+ dialog = NAUTILUS_BATCH_RENAME_DIALOG (user_data);
+
+ g_object_get (dialog->name_entry, "cursor-position", &cursor_position, NULL);
+
+ gtk_editable_insert_text (GTK_EDITABLE (dialog->name_entry),
+ ORIGINAL_FILE_NAME,
+ strlen (ORIGINAL_FILE_NAME),
+ &cursor_position);
+
+ gtk_editable_set_position (GTK_EDITABLE (dialog->name_entry), cursor_position);
+
+ gtk_entry_grab_focus_without_selecting (GTK_ENTRY (dialog->name_entry));
+
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
+}
+
+static void
+disable_action (NautilusBatchRenameDialog *dialog,
+ gchar *action_name)
+{
+ GAction *action;
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group),
+ action_name);
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
+}
+
+static void
+add_metadata_tag (GSimpleAction *action,
+ GVariant *value,
+ gpointer user_data)
+{
+ NautilusBatchRenameDialog *dialog;
+ const gchar *action_name;
+ gint cursor_position;
+ TagData *tag_data;
+
+ dialog = NAUTILUS_BATCH_RENAME_DIALOG (user_data);
+
+ action_name = g_action_get_name (G_ACTION (action));
+ g_object_get (dialog->name_entry, "cursor-position", &cursor_position, NULL);
+
+ if (g_strrstr (action_name, "creation-date")) {
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, CREATION_DATE);
+ tag_data->available = TRUE;
+ tag_data->set = FALSE;
+
+ gtk_editable_insert_text (GTK_EDITABLE (dialog->name_entry),
+ CREATION_DATE,
+ strlen (CREATION_DATE),
+ &cursor_position);
+ gtk_editable_set_position (GTK_EDITABLE (dialog->name_entry), cursor_position);
+ disable_action (dialog, "add-creation-date-tag");
+ }
+
+ if (g_strrstr (action_name, "equipment")) {
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, CAMERA_MODEL);
+ tag_data->available = TRUE;
+ tag_data->set = FALSE;
+
+ gtk_editable_insert_text (GTK_EDITABLE (dialog->name_entry),
+ CAMERA_MODEL,
+ strlen (CAMERA_MODEL),
+ &cursor_position);
+ gtk_editable_set_position (GTK_EDITABLE (dialog->name_entry), cursor_position);
+ disable_action (dialog, "add-equipment-tag");
+ }
+
+ if (g_strrstr (action_name, "season")) {
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, SEASON_NUMBER);
+ tag_data->available = TRUE;
+ tag_data->set = FALSE;
+
+ gtk_editable_insert_text (GTK_EDITABLE (dialog->name_entry),
+ SEASON_NUMBER,
+ strlen (SEASON_NUMBER),
+ &cursor_position);
+ gtk_editable_set_position (GTK_EDITABLE (dialog->name_entry), cursor_position);
+ disable_action (dialog, "add-season-tag");
+ }
+
+ if (g_strrstr (action_name, "episode")) {
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, EPISODE_NUMBER);
+ tag_data->available = TRUE;
+ tag_data->set = FALSE;
+
+ gtk_editable_insert_text (GTK_EDITABLE (dialog->name_entry),
+ EPISODE_NUMBER,
+ strlen (EPISODE_NUMBER),
+ &cursor_position);
+ gtk_editable_set_position (GTK_EDITABLE (dialog->name_entry), cursor_position);
+ disable_action (dialog, "add-episode-tag");
+ }
+
+ if (g_strrstr (action_name, "track")) {
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, TRACK_NUMBER);
+ tag_data->available = TRUE;
+ tag_data->set = FALSE;
+
+ gtk_editable_insert_text (GTK_EDITABLE (dialog->name_entry),
+ TRACK_NUMBER,
+ strlen (TRACK_NUMBER),
+ &cursor_position);
+ gtk_editable_set_position (GTK_EDITABLE (dialog->name_entry), cursor_position);
+ disable_action (dialog, "add-track-number-tag");
+ }
+
+ if (g_strrstr (action_name, "artist")) {
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, ARTIST_NAME);
+ tag_data->available = TRUE;
+ tag_data->set = FALSE;
+
+ gtk_editable_insert_text (GTK_EDITABLE (dialog->name_entry),
+ ARTIST_NAME,
+ strlen (ARTIST_NAME),
+ &cursor_position);
+ gtk_editable_set_position (GTK_EDITABLE (dialog->name_entry), cursor_position);
+ disable_action (dialog, "add-artist-name-tag");
+ }
+
+ if (g_strrstr (action_name, "title")) {
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, TITLE);
+ tag_data->available = TRUE;
+ tag_data->set = FALSE;
+
+ gtk_editable_insert_text (GTK_EDITABLE (dialog->name_entry),
+ TITLE,
+ strlen (TITLE),
+ &cursor_position);
+ gtk_editable_set_position (GTK_EDITABLE (dialog->name_entry), cursor_position);
+ disable_action (dialog, "add-title-tag");
+ }
+
+ gtk_entry_grab_focus_without_selecting (GTK_ENTRY (dialog->name_entry));
+}
+
+static void
+add_numbering_tag (GSimpleAction *action,
+ GVariant *value,
+ gpointer user_data)
+{
+ NautilusBatchRenameDialog *dialog;
+ const gchar *action_name;
+ gint cursor_position;
+ GAction *add_numbering_action;
+
+ dialog = NAUTILUS_BATCH_RENAME_DIALOG (user_data);
+
+ action_name = g_action_get_name (G_ACTION (action));
+ g_object_get (dialog->name_entry, "cursor-position", &cursor_position, NULL);
+
+ if (g_strrstr (action_name, "zero")) {
+ gtk_editable_insert_text (GTK_EDITABLE (dialog->name_entry),
+ NUMBERING,
+ strlen (NUMBERING),
+ &cursor_position);
+ gtk_editable_set_position (GTK_EDITABLE (dialog->name_entry), cursor_position);
+ }
+
+ if (g_strrstr (action_name, "one")) {
+ gtk_editable_insert_text (GTK_EDITABLE (dialog->name_entry),
+ NUMBERING0,
+ strlen (NUMBERING0),
+ &cursor_position);
+ gtk_editable_set_position (GTK_EDITABLE (dialog->name_entry), cursor_position);
+ }
+
+ if (g_strrstr (action_name, "two")) {
+ gtk_editable_insert_text (GTK_EDITABLE (dialog->name_entry),
+ NUMBERING00,
+ strlen (NUMBERING00),
+ &cursor_position);
+ gtk_editable_set_position (GTK_EDITABLE (dialog->name_entry), cursor_position);
+ }
+
+ add_numbering_action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group),
+ "add-numbering-tag-zero");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (add_numbering_action), FALSE);
+ add_numbering_action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group),
+ "add-numbering-tag-one");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (add_numbering_action), FALSE);
+
+ add_numbering_action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group),
+ "add-numbering-tag-two");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (add_numbering_action), FALSE);
+
+ gtk_entry_grab_focus_without_selecting (GTK_ENTRY (dialog->name_entry));
+}
+
+const GActionEntry dialog_entries[] = {
+ { "numbering-order-changed", NULL, "s", "'name-ascending'", add_numbering_order },
+ { "add-original-file-name-tag", add_original_file_name_tag },
+ { "add-numbering-tag-zero", add_numbering_tag },
+ { "add-numbering-tag-one", add_numbering_tag },
+ { "add-numbering-tag-two", add_numbering_tag },
+ { "add-creation-date-tag", add_metadata_tag },
+ { "add-equipment-tag", add_metadata_tag },
+ { "add-season-tag", add_metadata_tag },
+ { "add-episode-tag", add_metadata_tag },
+ { "add-video-album-tag", add_metadata_tag },
+ { "add-track-number-tag", add_metadata_tag },
+ { "add-artist-name-tag", add_metadata_tag },
+ { "add-title-tag", add_metadata_tag },
+
+};
+
+static void
+row_selected (GtkListBox *box,
+ GtkListBoxRow *listbox_row,
+ gpointer user_data)
+{
+ NautilusBatchRenameDialog *dialog;
+ GtkListBoxRow *row;
+ gint index;
+
+ if (!GTK_IS_LIST_BOX_ROW (listbox_row))
+ return;
+
+ dialog = NAUTILUS_BATCH_RENAME_DIALOG (user_data);
+ index = gtk_list_box_row_get_index (listbox_row);
+
+ if (GTK_WIDGET (box) == dialog->original_name_listbox) {
+ row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (dialog->arrow_listbox), index);
+ gtk_list_box_select_row (GTK_LIST_BOX (dialog->arrow_listbox),
+ row);
+ row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (dialog->result_listbox), index);
+ gtk_list_box_select_row (GTK_LIST_BOX (dialog->result_listbox),
+ row);
+ }
+
+ if (GTK_WIDGET (box) == dialog->arrow_listbox) {
+ row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (dialog->original_name_listbox), index);
+ gtk_list_box_select_row (GTK_LIST_BOX (dialog->original_name_listbox),
+ row);
+ row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (dialog->result_listbox), index);
+ gtk_list_box_select_row (GTK_LIST_BOX (dialog->result_listbox),
+ row);
+ }
+
+ if (GTK_WIDGET (box) == dialog->result_listbox) {
+ row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (dialog->arrow_listbox), index);
+ gtk_list_box_select_row (GTK_LIST_BOX (dialog->arrow_listbox),
+ row);
+ row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (dialog->original_name_listbox), index);
+ gtk_list_box_select_row (GTK_LIST_BOX (dialog->original_name_listbox),
+ row);
+ }
+}
+
+gint compare_int (gconstpointer a,
+ gconstpointer b)
+{
+ int *number1 = (int*) a;
+ int *number2 = (int*) b;
+
+ return *number1 - *number2;
+}
+
+/* This function splits the entry text into a list of regular text and tags.
+ * For instance, "[1, 2, 3]Paris[Creation date]" would result in:
+ * "[1, 2, 3]", "Paris", "[Creation date]" */
+static GList*
+split_entry_text (NautilusBatchRenameDialog *dialog,
+ gchar *entry_text)
+{
+ GString *string;
+ GString *tag;
+ GArray *tag_positions;
+ gint tags;
+ gint i;
+ gint tag_end_position;
+ GList *result = NULL;
+ TagData *tag_data;
+
+ tags = 0;
+ tag_end_position = 0;
+ tag_positions = g_array_new (FALSE, FALSE, sizeof (gint));
+
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, NUMBERING);
+ if (tag_data->set) {
+ g_array_append_val (tag_positions, tag_data->position);
+ tags++;
+ }
+
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, NUMBERING0);
+ if (tag_data->set) {
+ g_array_append_val (tag_positions, tag_data->position);
+ tags++;
+ }
+
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, NUMBERING00);
+ if (tag_data->set) {
+ g_array_append_val (tag_positions, tag_data->position);
+ tags++;
+ }
+
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, ORIGINAL_FILE_NAME);
+ if (tag_data->set) {
+ g_array_append_val (tag_positions, tag_data->position);
+ tags++;
+ }
+
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, CREATION_DATE);
+ if (tag_data->set) {
+ g_array_append_val (tag_positions, tag_data->position);
+ tags++;
+ }
+
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, CAMERA_MODEL);
+ if (tag_data->set) {
+ g_array_append_val (tag_positions, tag_data->position);
+ tags++;
+ }
+
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, SEASON_NUMBER);
+ if (tag_data->set) {
+ g_array_append_val (tag_positions, tag_data->position);
+ tags++;
+ }
+
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, EPISODE_NUMBER);
+ if (tag_data->set) {
+ g_array_append_val (tag_positions, tag_data->position);
+ tags++;
+ }
+
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, TRACK_NUMBER);
+ if (tag_data->set) {
+ g_array_append_val (tag_positions, tag_data->position);
+ tags++;
+ }
+
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, ARTIST_NAME);
+ if (tag_data->set) {
+ g_array_append_val (tag_positions, tag_data->position);
+ tags++;
+ }
+
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, TITLE);
+ if (tag_data->set) {
+ g_array_append_val (tag_positions, tag_data->position);
+ tags++;
+ }
+
+ g_array_sort (tag_positions, compare_int);
+
+ for (i = 0; i < tags; i++) {
+ tag = g_string_new ("");
+
+ string = g_string_new ("");
+
+ string = g_string_append_len (string,
+ entry_text + tag_end_position,
+ g_array_index (tag_positions, gint, i) - tag_end_position);
+
+ if (g_strcmp0 (string->str, ""))
+ result = g_list_prepend (result, string);
+
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, ORIGINAL_FILE_NAME);
+ if (g_array_index (tag_positions, gint, i) == tag_data->position && tag_data->set) {
+ tag_end_position = g_array_index (tag_positions, gint, i) +
+ strlen (ORIGINAL_FILE_NAME);
+ tag = g_string_append (tag, ORIGINAL_FILE_NAME);
+ }
+
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, NUMBERING);
+ if (g_array_index (tag_positions, gint, i) == tag_data->position && tag_data->set) {
+ tag_end_position = g_array_index (tag_positions, gint, i) +
+ strlen (NUMBERING);
+ tag = g_string_append (tag, NUMBERING);
+ }
+
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, NUMBERING0);
+ if (g_array_index (tag_positions, gint, i) == tag_data->position && tag_data->set) {
+ tag_end_position = g_array_index (tag_positions, gint, i) +
+ strlen (NUMBERING0);
+ tag = g_string_append (tag, NUMBERING0);
+ }
+
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, NUMBERING00);
+ if (g_array_index (tag_positions, gint, i) == tag_data->position && tag_data->set) {
+ tag_end_position = g_array_index (tag_positions, gint, i) +
+ strlen (NUMBERING00);
+ tag = g_string_append (tag, NUMBERING00);
+ }
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, CREATION_DATE);
+ if (g_array_index (tag_positions, gint, i) == tag_data->position && tag_data->set) {
+ tag_end_position = g_array_index (tag_positions, gint, i) +
+ strlen (CREATION_DATE);
+ tag = g_string_append (tag, CREATION_DATE);
+ }
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, CAMERA_MODEL);
+ if (g_array_index (tag_positions, gint, i) == tag_data->position && tag_data->set) {
+ tag_end_position = g_array_index (tag_positions, gint, i) +
+ strlen (CAMERA_MODEL);
+ tag = g_string_append (tag, CAMERA_MODEL);
+ }
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, SEASON_NUMBER);
+ if (g_array_index (tag_positions, gint, i) == tag_data->position && tag_data->set) {
+ tag_end_position = g_array_index (tag_positions, gint, i) +
+ strlen (SEASON_NUMBER);
+ tag = g_string_append (tag, SEASON_NUMBER);
+ }
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, EPISODE_NUMBER);
+ if (g_array_index (tag_positions, gint, i) == tag_data->position && tag_data->set) {
+ tag_end_position = g_array_index (tag_positions, gint, i) +
+ strlen (EPISODE_NUMBER);
+ tag = g_string_append (tag, EPISODE_NUMBER);
+ }
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, TRACK_NUMBER);
+ if (g_array_index (tag_positions, gint, i) == tag_data->position && tag_data->set) {
+ tag_end_position = g_array_index (tag_positions, gint, i) +
+ strlen (TRACK_NUMBER);
+ tag = g_string_append (tag, TRACK_NUMBER);
+ }
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, ARTIST_NAME);
+ if (g_array_index (tag_positions, gint, i) == tag_data->position && tag_data->set) {
+ tag_end_position = g_array_index (tag_positions, gint, i) +
+ strlen (ARTIST_NAME);
+ tag = g_string_append (tag, ARTIST_NAME);
+ }
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, TITLE);
+ if (g_array_index (tag_positions, gint, i) == tag_data->position && tag_data->set) {
+ tag_end_position = g_array_index (tag_positions, gint, i) +
+ strlen (TITLE);
+ tag = g_string_append (tag, TITLE);
+ }
+
+ result = g_list_prepend (result, tag);
+ }
+ string = g_string_new ("");
+ string = g_string_append (string, entry_text + tag_end_position);
+
+ if (g_strcmp0 (string->str, ""))
+ result = g_list_prepend (result, string);
+
+ result = g_list_reverse (result);
+
+ g_array_free (tag_positions, TRUE);
+ return result;
+}
+
+static GList*
+batch_rename_dialog_get_new_names (NautilusBatchRenameDialog *dialog)
+{
+ GList *result = NULL;
+ GList *selection;
+ GList *tags_list;
+ g_autofree gchar *entry_text;
+ g_autofree gchar *replace_text;
+
+ selection = dialog->selection;
+ tags_list = NULL;
+
+ if (dialog->mode == NAUTILUS_BATCH_RENAME_DIALOG_REPLACE)
+ entry_text = g_strdup (gtk_entry_get_text (GTK_ENTRY (dialog->find_entry)));
+ else
+ entry_text = g_strdup (gtk_entry_get_text (GTK_ENTRY (dialog->name_entry)));
+
+ replace_text = g_strdup (gtk_entry_get_text (GTK_ENTRY (dialog->replace_entry)));
+
+ if (dialog->mode == NAUTILUS_BATCH_RENAME_DIALOG_REPLACE) {
+ result = batch_rename_dialog_get_new_names_list (dialog->mode,
+ selection,
+ NULL,
+ NULL,
+ entry_text,
+ replace_text);
+ } else {
+ tags_list = split_entry_text (dialog, entry_text);
+
+ result = batch_rename_dialog_get_new_names_list (dialog->mode,
+ selection,
+ tags_list,
+ dialog->selection_metadata,
+ entry_text,
+ replace_text);
+ g_list_free_full (tags_list, string_free);
+ }
+
+ result = g_list_reverse (result);
+
+ return result;
+}
+
+static void
+begin_batch_rename_dialog (NautilusBatchRenameDialog *dialog,
+ GList *new_names)
+{
+ GList *l1;
+ GList *l2;
+ GList *l3;
+ gchar *file_name;
+ gchar *old_file_name;
+ GString *new_file_name;
+ GList *new_name;
+ NautilusFile *file;
+
+ for (l1 = new_names, l2 = dialog->selection; l1 != NULL && l2 != NULL; l1 = l1->next, l2 = l2->next)
{
+ old_file_name = nautilus_file_get_name (NAUTILUS_FILE (l2->data));
+ new_file_name = l1->data;
+
+ for (l3 = dialog->selection; l3 != NULL; l3 = l3->next) {
+ file_name = nautilus_file_get_name (NAUTILUS_FILE (l3->data));
+ if (l3 != l2 && g_strcmp0 (file_name, new_file_name->str) == 0) {
+ file = NAUTILUS_FILE (l3->data);
+ new_name = l1->data;
+
+ dialog->selection = g_list_remove_link (dialog->selection, l3);
+ new_names = g_list_remove_link (new_names, l1);
+
+ dialog->selection = g_list_prepend (dialog->selection, file);
+ new_names = g_list_prepend (new_names, new_name);
+
+ g_free (file_name);
+
+ break;
+ }
+
+ g_free (file_name);
+ }
+
+ g_free (old_file_name);
+ }
+
+ /* do the actual rename here */
+ nautilus_file_batch_rename (dialog->selection, new_names, NULL, NULL);
+
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (dialog->window)), NULL);
+}
+
+static void
+listbox_header_func (GtkListBoxRow *row,
+ GtkListBoxRow *before,
+ NautilusBatchRenameDialog *dialog)
+{
+ gboolean show_separator;
+
+ show_separator = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row),
+ "show-separator"));
+
+ if (show_separator)
+ {
+ GtkWidget *separator;
+
+ separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
+ gtk_widget_show (separator);
+
+ gtk_list_box_row_set_header (row, separator);
+ }
+}
+
+/* This is manually done instead of using GtkSizeGroup because of the complexity of
+ * the later.*/
+static void
+update_rows_height (NautilusBatchRenameDialog *dialog)
+{
+ GList *l;
+ gint minimum_height;
+ gint natural_height;
+ gint new_maximum_height;
+
+ new_maximum_height = -1;
+
+ /* check if maximum height has changed */
+ for (l = dialog->listbox_labels_new; l != NULL; l = l->next) {
+ gtk_widget_get_preferred_height (GTK_WIDGET (l->data),
+ &minimum_height,
+ &natural_height);
+
+ if (minimum_height > new_maximum_height) {
+ new_maximum_height = minimum_height;
+ }
+ }
+
+ for (l = dialog->listbox_labels_old; l != NULL; l = l->next) {
+ gtk_widget_get_preferred_height (GTK_WIDGET (l->data),
+ &minimum_height,
+ &natural_height);
+
+ if (minimum_height > new_maximum_height) {
+ new_maximum_height = minimum_height;
+ }
+ }
+
+ for (l = dialog->listbox_icons; l != NULL; l = l->next) {
+ gtk_widget_get_preferred_height (GTK_WIDGET (l->data),
+ &minimum_height,
+ &natural_height);
+
+ if (minimum_height > new_maximum_height) {
+ new_maximum_height = minimum_height;
+ }
+ }
+
+ if (new_maximum_height != dialog->row_height) {
+ dialog->row_height = new_maximum_height;
+
+ for (l = dialog->listbox_icons; l != NULL; l = l->next) {
+ g_object_set (G_OBJECT (l->data), "height-request", dialog->row_height, NULL);
+ }
+
+ for (l = dialog->listbox_labels_new; l != NULL; l = l->next) {
+ g_object_set (G_OBJECT (l->data), "height-request", dialog->row_height, NULL);
+ }
+
+ for (l = dialog->listbox_labels_old; l != NULL; l = l->next) {
+ g_object_set (G_OBJECT (l->data), "height-request", dialog->row_height, NULL);
+ }
+ }
+}
+
+static GtkWidget*
+create_original_name_row_for_label (NautilusBatchRenameDialog *dialog,
+ const gchar *old_text,
+ gboolean show_separator)
+{
+ GtkWidget *row;
+ GtkWidget *label_old;
+
+ row = gtk_list_box_row_new ();
+
+ g_object_set_data (G_OBJECT (row), "show-separator", GINT_TO_POINTER (show_separator));
+
+ label_old = g_object_new (GTK_TYPE_LABEL,
+ "label",old_text,
+ "hexpand", TRUE,
+ "xalign", 0.0,
+ "margin-start", 6,
+ NULL);
+
+ gtk_label_set_ellipsize (GTK_LABEL (label_old), PANGO_ELLIPSIZE_END);
+
+ dialog->listbox_labels_old = g_list_prepend (dialog->listbox_labels_old, label_old);
+
+ gtk_container_add (GTK_CONTAINER (row), label_old);
+ gtk_widget_show_all (row);
+
+ return row;
+}
+
+static GtkWidget*
+create_result_row_for_label (NautilusBatchRenameDialog *dialog,
+ const gchar *new_text,
+ gboolean show_separator)
+{
+ GtkWidget *row;
+ GtkWidget *label_new;
+
+ row = gtk_list_box_row_new ();
+
+ g_object_set_data (G_OBJECT (row), "show-separator", GINT_TO_POINTER (show_separator));
+
+ label_new = g_object_new (GTK_TYPE_LABEL,
+ "label",new_text,
+ "hexpand", TRUE,
+ "xalign", 0.0,
+ "margin-start", 6,
+ NULL);
+
+ gtk_label_set_ellipsize (GTK_LABEL (label_new), PANGO_ELLIPSIZE_END);
+
+ dialog->listbox_labels_new = g_list_prepend (dialog->listbox_labels_new, label_new);
+
+ gtk_container_add (GTK_CONTAINER (row), label_new);
+ gtk_widget_show_all (row);
+
+ return row;
+}
+
+static GtkWidget*
+create_arrow_row_for_label (NautilusBatchRenameDialog *dialog,
+ gboolean show_separator)
+{
+ GtkWidget *row;
+ GtkWidget *icon;
+
+ row = gtk_list_box_row_new ();
+
+ g_object_set_data (G_OBJECT (row), "show-separator", GINT_TO_POINTER (show_separator));
+
+ icon = g_object_new (GTK_TYPE_LABEL,
+ "label","→",
+ "hexpand", FALSE,
+ "xalign", 1.0,
+ "margin-start", 6,
+ NULL);
+
+ dialog->listbox_icons = g_list_prepend (dialog->listbox_icons, icon);
+
+ gtk_container_add (GTK_CONTAINER (row), icon);
+ gtk_widget_show_all (row);
+
+ return row;
+}
+
+static void
+batch_rename_dialog_on_response (NautilusBatchRenameDialog *dialog,
+ gint response_id,
+ gpointer user_data)
+{
+ if (response_id == GTK_RESPONSE_OK) {
+ /* wait for checking conflicts to finish, to be sure that
+ * the rename can actually take place */
+ if (dialog->checking_conflicts) {
+ dialog->rename_clicked = TRUE;
+ return;
+ }
+
+ if (!gtk_widget_is_sensitive (dialog->rename_button))
+ return;
+
+ GdkCursor *cursor;
+ GdkDisplay *display;
+
+ display = gtk_widget_get_display (GTK_WIDGET (dialog->window));
+ cursor = gdk_cursor_new_from_name (display, "progress");
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (dialog->window)),
+ cursor);
+ g_object_unref (cursor);
+
+ display = gtk_widget_get_display (GTK_WIDGET (dialog));
+ cursor = gdk_cursor_new_from_name (display, "progress");
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (dialog)),
+ cursor);
+ g_object_unref (cursor);
+
+ gtk_widget_hide (GTK_WIDGET (dialog));
+ begin_batch_rename_dialog (dialog, dialog->new_names);
+ }
+
+ if (dialog->conflict_cancellable)
+ g_cancellable_cancel (dialog->conflict_cancellable);
+
+ gtk_widget_destroy (GTK_WIDGET (dialog));
+}
+
+static void
+fill_display_listbox (NautilusBatchRenameDialog *dialog)
+{
+ GtkWidget *row;
+ GList *l1;
+ GList *l2;
+ NautilusFile *file;
+ GString *new_name;
+ gchar *name;
+
+ dialog->original_name_listbox_rows = NULL;
+ dialog->arrow_listbox_rows = NULL;
+ dialog->result_listbox_rows = NULL;
+
+ gtk_size_group_add_widget (dialog->size_group, dialog->result_listbox);
+ gtk_size_group_add_widget (dialog->size_group, dialog->original_name_listbox);
+
+ /* add rows to a list so that they can be removed when the renaming
+ * result changes */
+ 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;
+
+ name = nautilus_file_get_name (file);
+ row = create_original_name_row_for_label (dialog, name, TRUE);
+ gtk_container_add (GTK_CONTAINER (dialog->original_name_listbox), row);
+ dialog->original_name_listbox_rows = g_list_prepend (dialog->original_name_listbox_rows,
+ row);
+
+ row = create_arrow_row_for_label (dialog, TRUE);
+ gtk_container_add (GTK_CONTAINER (dialog->arrow_listbox), row);
+ dialog->arrow_listbox_rows = g_list_prepend (dialog->arrow_listbox_rows,
+ row);
+
+ row = create_result_row_for_label (dialog, new_name->str, TRUE);
+ gtk_container_add (GTK_CONTAINER (dialog->result_listbox), row);
+ dialog->result_listbox_rows = g_list_prepend (dialog->result_listbox_rows,
+ row);
+
+ g_free (name);
+ }
+
+ dialog->original_name_listbox_rows = g_list_reverse (dialog->original_name_listbox_rows);
+ dialog->arrow_listbox_rows = g_list_reverse (dialog->arrow_listbox_rows);
+ dialog->result_listbox_rows = g_list_reverse (dialog->result_listbox_rows);
+ dialog->listbox_labels_old = g_list_reverse (dialog->listbox_labels_old);
+ dialog->listbox_labels_new = g_list_reverse (dialog->listbox_labels_new);
+ dialog->listbox_icons = g_list_reverse (dialog->listbox_icons);
+}
+
+static void
+select_nth_conflict (NautilusBatchRenameDialog *dialog)
+{
+ GList *l;
+ GString *file_name;
+ GString *display_text;
+ GString *new_name;
+ gint nth_conflict_index;
+ gint nth_conflict;
+ gint name_occurences;
+ GtkAdjustment *adjustment;
+ GtkAllocation allocation;
+ ConflictData *data;
+
+ nth_conflict = dialog->selected_conflict;
+ l = g_list_nth (dialog->duplicates, nth_conflict);
+ data = l->data;
+
+ /* the conflict that has to be selected */
+ file_name = g_string_new (data->name);
+ display_text = g_string_new ("");
+
+ nth_conflict_index = data->index;
+
+ l = g_list_nth (dialog->original_name_listbox_rows, nth_conflict_index);
+ gtk_list_box_select_row (GTK_LIST_BOX (dialog->original_name_listbox),
+ l->data);
+
+ l = g_list_nth (dialog->arrow_listbox_rows, nth_conflict_index);
+ gtk_list_box_select_row (GTK_LIST_BOX (dialog->arrow_listbox),
+ l->data);
+
+ l = g_list_nth (dialog->result_listbox_rows, nth_conflict_index);
+ gtk_list_box_select_row (GTK_LIST_BOX (dialog->result_listbox),
+ l->data);
+
+ /* 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) * nth_conflict_index);
+
+ name_occurences = 0;
+ for (l = dialog->new_names; l != NULL; l = l->next) {
+ new_name = l->data;
+ if (g_string_equal (new_name, file_name))
+ name_occurences++;
+ }
+ if (name_occurences > 1)
+ g_string_append_printf (display_text,
+ _("\"%s\" would not be a unique new name"),
+ file_name->str);
+ else
+ g_string_append_printf (display_text,
+ _("\"%s\" would conflict with an existing file."),
+ file_name->str);
+
+ gtk_label_set_label (GTK_LABEL (dialog->conflict_label),
+ display_text->str);
+
+ g_string_free (file_name, TRUE);
+}
+
+static void
+select_next_conflict_down (NautilusBatchRenameDialog *dialog)
+{
+ dialog->selected_conflict++;
+
+ if (dialog->selected_conflict == 1)
+ gtk_widget_set_sensitive (dialog->conflict_up, TRUE);
+
+ if (dialog->selected_conflict == dialog->conflicts_number - 1)
+ gtk_widget_set_sensitive (dialog->conflict_down, FALSE);
+
+ select_nth_conflict (dialog);
+}
+
+static void
+select_next_conflict_up (NautilusBatchRenameDialog *dialog)
+{
+ dialog->selected_conflict--;
+
+ if (dialog->selected_conflict == 0)
+ gtk_widget_set_sensitive (dialog->conflict_up, FALSE);
+
+ if (dialog->selected_conflict == dialog->conflicts_number - 2)
+ gtk_widget_set_sensitive (dialog->conflict_down, TRUE);
+
+ select_nth_conflict (dialog);
+}
+
+static void
+update_conflict_row_background (NautilusBatchRenameDialog *dialog)
+{
+ GList *l1;
+ GList *l2;
+ GList *l3;
+ GList *l;
+ gint index;
+ GtkStyleContext *context;
+ ConflictData *data;
+
+ index = 0;
+
+ for (l1 = dialog->original_name_listbox_rows,
+ l2 = dialog->arrow_listbox_rows,
+ l3 = dialog->result_listbox_rows;
+ l1 != NULL && l2 != NULL && l3 != NULL;
+ l1 = l1->next, l2 = l2->next, l3 = l3->next) {
+ context = gtk_widget_get_style_context (GTK_WIDGET (l1->data));
+
+ if (gtk_style_context_has_class (context, "conflict-row")) {
+ gtk_style_context_remove_class (context, "conflict-row");
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (l2->data));
+ gtk_style_context_remove_class (context, "conflict-row");
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (l3->data));
+ gtk_style_context_remove_class (context, "conflict-row");
+
+ }
+
+ for (l = dialog->duplicates; l != NULL; l = l->next) {
+ data = l->data;
+
+ if (data->index == index) {
+ context = gtk_widget_get_style_context (GTK_WIDGET (l1->data));
+ gtk_style_context_add_class (context, "conflict-row");
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (l2->data));
+ gtk_style_context_add_class (context, "conflict-row");
+
+ context = gtk_widget_get_style_context (GTK_WIDGET (l3->data));
+ gtk_style_context_add_class (context, "conflict-row");
+ }
+ }
+
+ index++;
+ }
+}
+
+static void
+update_listbox (NautilusBatchRenameDialog *dialog)
+{
+ GList *l1;
+ GList *l2;
+ NautilusFile *file;
+ gchar *old_name;
+ GtkLabel *label;
+ GString *new_name;
+
+ 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);
+
+ if (dialog->mode == NAUTILUS_BATCH_RENAME_DIALOG_FORMAT) {
+ gtk_label_set_label (label, old_name);
+ } else {
+ new_name = batch_rename_dialog_replace_label_text (old_name,
+ gtk_entry_get_text (GTK_ENTRY
(dialog->find_entry)));
+ gtk_label_set_markup (GTK_LABEL (label), new_name->str);
+
+ g_string_free (new_name, TRUE);
+ }
+
+ g_free (old_name);
+ }
+
+ update_rows_height (dialog);
+
+ /* check if there are name conflicts and display them if they exist */
+ if (dialog->duplicates != NULL) {
+ update_conflict_row_background (dialog);
+
+ gtk_widget_set_sensitive (dialog->rename_button, FALSE);
+
+ gtk_widget_show (dialog->conflict_box);
+
+ dialog->selected_conflict = 0;
+ dialog->conflicts_number = g_list_length (dialog->duplicates);
+
+ select_nth_conflict (dialog);
+
+ gtk_widget_set_sensitive (dialog->conflict_up, FALSE);
+
+ if (g_list_length (dialog->duplicates) == 1)
+ gtk_widget_set_sensitive (dialog->conflict_down, FALSE);
+ else
+ gtk_widget_set_sensitive (dialog->conflict_down, TRUE);
+ } else {
+ gtk_widget_hide (dialog->conflict_box);
+
+ /* re-enable the rename button if there are no more name conflicts */
+ if (dialog->duplicates == NULL && !gtk_widget_is_sensitive (dialog->rename_button)) {
+ update_conflict_row_background (dialog);
+ gtk_widget_set_sensitive (dialog->rename_button, TRUE);
+ }
+ }
+
+ /* if the rename button was clicked and there's no conflict, then start renaming */
+ if (dialog->rename_clicked && dialog->duplicates == NULL) {
+ batch_rename_dialog_on_response (dialog, GTK_RESPONSE_OK, NULL);
+ }
+
+ if (dialog->rename_clicked && dialog->duplicates != NULL) {
+ dialog->rename_clicked = FALSE;
+ }
+}
+
+
+void
+check_conflict_for_file (NautilusBatchRenameDialog *dialog,
+ NautilusDirectory *directory,
+ GList *files)
+{
+ gchar *current_directory;
+ gchar *parent_uri;
+ gchar *table_parent_uri;
+ gchar *name;
+ NautilusFile *file;
+ GString *new_name;
+ GString *file_name;
+ GList *l1, *l2;
+ GHashTable *directory_files_table;
+ GHashTable *new_names_table;
+ GHashTable *names_conflicts_table;
+ gboolean exists;
+ gboolean have_conflict;
+ ConflictData *data;
+
+ current_directory = nautilus_directory_get_uri (directory);
+
+ directory_files_table = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ NULL);
+ new_names_table = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_free);
+ names_conflicts_table = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_free);
+
+ for (l1 = dialog->new_names, l2 = dialog->selection;
+ l1 != NULL && l2 != NULL;
+ l1 = l1->next, l2 = l2->next) {
+ new_name = l1->data;
+ file = NAUTILUS_FILE (l2->data);
+ parent_uri = nautilus_file_get_parent_uri (file);
+
+ table_parent_uri = g_hash_table_lookup (new_names_table, new_name->str);
+
+ if (g_strcmp0 (parent_uri, current_directory) == 0)
+ g_hash_table_insert (new_names_table,
+ g_strdup (new_name->str),
+ nautilus_file_get_parent_uri (file));
+
+ if (table_parent_uri != NULL && g_strcmp0 (current_directory, parent_uri) == 0) {
+ g_hash_table_insert (names_conflicts_table,
+ g_strdup (new_name->str),
+ nautilus_file_get_parent_uri (file));
+ }
+
+ g_free (parent_uri);
+ }
+
+ for (l1 = files; l1 != NULL; l1 = l1->next) {
+ file = NAUTILUS_FILE (l1->data);
+ g_hash_table_insert (directory_files_table,
+ nautilus_file_get_name (file),
+ GINT_TO_POINTER (HAVE_CONFLICT));
+ }
+
+ for (l1 = dialog->selection, l2 = dialog->new_names; l1 != NULL && l2 != NULL; l1 = l1->next, l2 =
l2->next) {
+ file = NAUTILUS_FILE (l1->data);
+
+ file_name = g_string_new ("");
+ name = nautilus_file_get_name (file);
+ g_string_append (file_name, name);
+ g_free (name);
+
+ parent_uri = nautilus_file_get_parent_uri (file);
+
+ new_name = l2->data;
+
+ have_conflict = FALSE;
+
+ /* check for duplicate only if the parent of the current file is
+ * the current directory and the name of the file has changed */
+ if (g_strcmp0 (parent_uri, current_directory) == 0 &&
+ !g_string_equal (new_name, file_name)) {
+ exists = GPOINTER_TO_INT (g_hash_table_lookup (directory_files_table,
new_name->str));
+
+ if (exists == HAVE_CONFLICT &&
+ !file_name_conflicts_with_results (dialog->selection, dialog->new_names,
new_name, parent_uri)) {
+ data = g_new (ConflictData, 1);
+ data->name = g_strdup (new_name->str);
+ data->index = g_list_index (dialog->selection, l1->data);
+ dialog->duplicates = g_list_prepend (dialog->duplicates,
+ data);
+
+ have_conflict = TRUE;
+ }
+ }
+ if (!have_conflict) {
+ table_parent_uri = g_hash_table_lookup (names_conflicts_table, new_name->str);
+
+ if (table_parent_uri != NULL && g_strcmp0 (nautilus_file_get_parent_uri (file),
current_directory) == 0) {
+ data = g_new (ConflictData, 1);
+ data->name = g_strdup (new_name->str);
+ data->index = g_list_index (dialog->selection, l1->data);
+ dialog->duplicates = g_list_prepend (dialog->duplicates,
+ data);
+
+ have_conflict = TRUE;
+ }
+ }
+
+ g_string_free (file_name, TRUE);
+ g_free (parent_uri);
+ }
+
+ /* check if this is the last call of the callback. Update
+ * 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);
+
+ dialog->checking_conflicts = FALSE;
+
+ update_listbox (dialog);
+ }
+
+ dialog->checked_parents++;
+
+ g_free (current_directory);
+ g_hash_table_destroy (directory_files_table);
+ g_hash_table_destroy (new_names_table);
+ g_hash_table_destroy (names_conflicts_table);
+}
+
+static void
+file_names_list_has_duplicates_callback (GObject *object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ NautilusBatchRenameDialog *dialog;
+
+ dialog = NAUTILUS_BATCH_RENAME_DIALOG (object);
+
+ if (g_cancellable_is_cancelled (dialog->conflict_cancellable))
+ return;
+
+ if (dialog->same_parent)
+ update_listbox (dialog);
+}
+
+static void
+on_call_when_ready (NautilusDirectory *directory,
+ GList *files,
+ gpointer callback_data)
+{
+ check_conflict_for_file (NAUTILUS_BATCH_RENAME_DIALOG (callback_data),
+ directory,
+ files);
+}
+
+static void
+file_names_list_has_duplicates_async_thread (GTask *task,
+ gpointer object,
+ gpointer task_data,
+ GCancellable *cancellable)
+{
+ NautilusBatchRenameDialog *dialog;
+ GList *new_names;
+ GList *directory_files;
+ GList *l1;
+ GList *l2;
+ NautilusFile *file;
+ GString *file_name;
+ GString *new_name;
+ NautilusDirectory *parent;
+ gboolean have_conflict;
+ gboolean hash_table_insertion;
+ gchar *name;
+ GHashTable *directory_names_table;
+ GHashTable *new_names_table;
+ GHashTable *names_conflicts_table;
+ gint exists;
+ ConflictData *data;
+
+ dialog = NAUTILUS_BATCH_RENAME_DIALOG (object);
+
+ dialog->duplicates = NULL;
+
+ if (g_cancellable_is_cancelled (cancellable))
+ return;
+
+ g_return_if_fail (g_list_length (dialog->new_names) == g_list_length (dialog->selection));
+
+ /* If the batch rename is launched in a search, then for each file we have to check for
+ * conflicts with each file in the file's parent directory */
+ if (dialog->distinct_parents != NULL) {
+ for (l1 = dialog->distinct_parents; l1 != NULL; l1 = l1->next) {
+ if (g_cancellable_is_cancelled (cancellable))
+ return;
+
+ parent = nautilus_directory_get_by_uri (l1->data);
+
+ nautilus_directory_call_when_ready (parent,
+ NAUTILUS_FILE_ATTRIBUTE_INFO,
+ TRUE,
+ on_call_when_ready,
+ dialog);
+ }
+
+ g_task_return_pointer (task, object, NULL);
+ return;
+ }
+
+ new_names = batch_rename_dialog_get_new_names (dialog);
+
+ directory_names_table = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ NULL);
+ new_names_table = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ NULL);
+ names_conflicts_table = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ NULL);
+
+ directory_files = nautilus_directory_get_file_list (dialog->directory);
+
+ for (l1 = new_names; l1 != NULL; l1 = l1->next) {
+ new_name = l1->data;
+ hash_table_insertion = g_hash_table_insert (new_names_table,
+ g_strdup (new_name->str),
+ GINT_TO_POINTER (HAVE_CONFLICT));
+
+ if (!hash_table_insertion) {
+ g_hash_table_insert (names_conflicts_table,
+ g_strdup (new_name->str),
+ GINT_TO_POINTER (HAVE_CONFLICT));
+ }
+ }
+
+ for (l1 = directory_files; l1 != NULL; l1 = l1->next) {
+ file = NAUTILUS_FILE (l1->data);
+ g_hash_table_insert (directory_names_table,
+ nautilus_file_get_name (file),
+ GINT_TO_POINTER (HAVE_CONFLICT));
+ }
+
+ for (l1 = new_names, l2 = dialog->selection; l1 != NULL && l2 != NULL; l1 = l1->next, l2 = l2->next)
{
+ if (g_cancellable_is_cancelled (cancellable)) {
+ g_list_free_full (dialog->duplicates, conflict_data_free);
+ break;
+ }
+
+ file = NAUTILUS_FILE (l2->data);
+ new_name = l1->data;
+
+ have_conflict = FALSE;
+
+ name = nautilus_file_get_name (file);
+ file_name = g_string_new (name);
+
+ g_free (name);
+
+ /* check for duplicate only if the name has changed */
+ if (!g_string_equal (new_name, file_name)) {
+ /* check with already existing files */
+ exists = GPOINTER_TO_INT (g_hash_table_lookup (directory_names_table,
new_name->str));
+
+ if (exists == HAVE_CONFLICT &&
+ !file_name_conflicts_with_results (dialog->selection, new_names, new_name,
NULL)) {
+ data = g_new (ConflictData, 1);
+ data->name = g_strdup (new_name->str);
+ data->index = g_list_index (dialog->selection, l2->data);
+ dialog->duplicates = g_list_prepend (dialog->duplicates,
+ data);
+ have_conflict = TRUE;
+ }
+
+ /* check with files that will result from the batch rename, unless
+ * this file already has a conflict */
+ if (!have_conflict) {
+ exists = GPOINTER_TO_INT (g_hash_table_lookup (names_conflicts_table,
new_name->str));
+
+ if (exists == HAVE_CONFLICT) {
+ data = g_new (ConflictData, 1);
+ data->name = g_strdup (new_name->str);
+ data->index = g_list_index (dialog->selection, l2->data);
+ dialog->duplicates = g_list_prepend (dialog->duplicates,
+ data);
+ }
+ }
+ }
+
+ g_string_free (file_name, TRUE);
+ }
+
+ g_hash_table_destroy (directory_names_table);
+ g_hash_table_destroy (new_names_table);
+ g_hash_table_destroy (names_conflicts_table);
+ nautilus_file_list_free (directory_files);
+ g_list_free_full (new_names, string_free);
+
+ dialog->duplicates = g_list_reverse (dialog->duplicates);
+
+ dialog->checking_conflicts = FALSE;
+
+ g_task_return_pointer (task, object, NULL);
+
+}
+
+static void
+file_names_list_has_duplicates_async (NautilusBatchRenameDialog *dialog,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ if (dialog->checking_conflicts == TRUE)
+ g_cancellable_cancel (dialog->conflict_cancellable);
+
+ dialog->conflict_cancellable = g_cancellable_new ();
+
+ dialog->checking_conflicts = TRUE;
+ dialog->conflicts_task = g_task_new (dialog, dialog->conflict_cancellable, callback, user_data);
+
+ g_task_set_priority (dialog->conflicts_task, G_PRIORITY_DEFAULT);
+ g_task_run_in_thread (dialog->conflicts_task, file_names_list_has_duplicates_async_thread);
+
+ g_object_unref (dialog->conflicts_task);
+}
+
+static void
+check_if_tag_is_used (NautilusBatchRenameDialog *dialog,
+ gchar *tag_name,
+ gchar *action_name)
+{
+ GString *entry_text;
+ GAction *action;
+ TagData *tag_data;
+
+ entry_text = g_string_new (gtk_entry_get_text (GTK_ENTRY (dialog->name_entry)));
+
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, tag_name);
+
+ if (g_strrstr (entry_text->str, tag_name) && tag_data->set == FALSE) {
+ action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group),
+ action_name);
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
+ }
+
+ if (g_strrstr (entry_text->str, tag_name) == NULL) {
+ action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group),
+ action_name);
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE);
+
+ tag_data->set = FALSE;
+ }
+
+ if (g_strrstr (entry_text->str, tag_name)) {
+ tag_data->position = g_strrstr (entry_text->str, tag_name) -
+ entry_text->str;
+ tag_data->set = TRUE;
+ }
+
+ g_string_free (entry_text, TRUE);
+}
+
+static void
+check_numbering_tags (NautilusBatchRenameDialog *dialog)
+{
+ GString *entry_text;
+ GAction *add_numbering_action;
+ TagData *tag_data;
+ TagData *tag_data0;
+ TagData *tag_data00;
+
+ entry_text = g_string_new (gtk_entry_get_text (GTK_ENTRY (dialog->name_entry)));
+
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, NUMBERING);
+ tag_data0 = g_hash_table_lookup (dialog->tag_info_table, NUMBERING0);
+ tag_data00 = g_hash_table_lookup (dialog->tag_info_table, NUMBERING00);
+
+ if ((g_strrstr (entry_text->str, NUMBERING) ||
+ g_strrstr (entry_text->str, NUMBERING0) ||
+ g_strrstr (entry_text->str, NUMBERING00)) &&
+ (!tag_data->set && !tag_data0->set && !tag_data00->set)) {
+ add_numbering_action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group),
+ "add-numbering-tag-zero");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (add_numbering_action), FALSE);
+
+ add_numbering_action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group),
+ "add-numbering-tag-one");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (add_numbering_action), FALSE);
+
+ add_numbering_action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group),
+ "add-numbering-tag-two");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (add_numbering_action), FALSE);
+
+ if (g_strrstr (entry_text->str, NUMBERING))
+ tag_data->set = TRUE;
+ if (g_strrstr (entry_text->str, NUMBERING0))
+ tag_data0->set = TRUE;
+ if (g_strrstr (entry_text->str, NUMBERING00))
+ tag_data00->set = TRUE;
+ }
+
+ if (g_strrstr (entry_text->str, NUMBERING) == NULL &&
+ g_strrstr (entry_text->str, NUMBERING0) == NULL &&
+ g_strrstr (entry_text->str, NUMBERING00) == NULL &&
+ (tag_data->set || tag_data0->set || tag_data00->set)) {
+ add_numbering_action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group),
+ "add-numbering-tag-zero");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (add_numbering_action), TRUE);
+
+ add_numbering_action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group),
+ "add-numbering-tag-one");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (add_numbering_action), TRUE);
+
+ add_numbering_action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group),
+ "add-numbering-tag-two");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (add_numbering_action), TRUE);
+
+ tag_data->set = FALSE;
+ tag_data0->set = FALSE;
+ tag_data00->set = FALSE;
+ }
+
+ if (g_strrstr (entry_text->str, NUMBERING)) {
+ tag_data->position = g_strrstr (entry_text->str, NUMBERING) - entry_text->str;
+ }
+
+ if (g_strrstr (entry_text->str, NUMBERING0)) {
+ tag_data0->position = g_strrstr (entry_text->str, NUMBERING0) - entry_text->str;
+ }
+ if (g_strrstr (entry_text->str, NUMBERING00)) {
+ tag_data00->position = g_strrstr (entry_text->str, NUMBERING00) - entry_text->str;
+ }
+ g_string_free (entry_text, TRUE);
+}
+
+static void
+update_tags (NautilusBatchRenameDialog *dialog)
+{
+ TagData *tag_data;
+
+ check_if_tag_is_used (dialog,
+ ORIGINAL_FILE_NAME,
+ "add-original-file-name-tag");
+
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, CREATION_DATE);
+ if (tag_data->available)
+ check_if_tag_is_used (dialog,
+ CREATION_DATE,
+ "add-creation-date-tag");
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, CAMERA_MODEL);
+ if (tag_data->available)
+ check_if_tag_is_used (dialog,
+ CAMERA_MODEL,
+ "add-equipment-tag");
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, SEASON_NUMBER);
+ if (tag_data->available)
+ check_if_tag_is_used (dialog,
+ SEASON_NUMBER,
+ "add-season-tag");
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, EPISODE_NUMBER);
+ if (tag_data->available)
+ check_if_tag_is_used (dialog,
+ EPISODE_NUMBER,
+ "add-episode-tag");
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, TRACK_NUMBER);
+ if (tag_data->available)
+ check_if_tag_is_used (dialog,
+ TRACK_NUMBER,
+ "add-track-number-tag");
+
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, ARTIST_NAME);
+ if (tag_data->available)
+ check_if_tag_is_used (dialog,
+ ARTIST_NAME,
+ "add-artist-name-tag");
+
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, TITLE);
+ if (tag_data->available)
+ check_if_tag_is_used (dialog,
+ TITLE,
+ "add-title-tag");
+
+ check_numbering_tags (dialog);
+}
+
+static gboolean
+have_unallowed_character (NautilusBatchRenameDialog *dialog)
+{
+ const gchar *entry_text;
+ gboolean have_unallowed_character;
+
+ have_unallowed_character = FALSE;
+
+ if (dialog->mode == NAUTILUS_BATCH_RENAME_DIALOG_FORMAT) {
+ entry_text = gtk_entry_get_text (GTK_ENTRY (dialog->name_entry));
+
+ if (strstr (entry_text, "/") != NULL) {
+ have_unallowed_character = TRUE;
+ }
+ } else {
+ entry_text = gtk_entry_get_text (GTK_ENTRY (dialog->replace_entry));
+
+ if (strstr (entry_text, "/") != NULL) {
+ have_unallowed_character = TRUE;
+ }
+ }
+
+ if (have_unallowed_character) {
+ gtk_label_set_label (GTK_LABEL (dialog->conflict_label),
+ "\"/\" is an unallowed character");
+
+ gtk_widget_set_sensitive (dialog->rename_button, FALSE);
+ gtk_widget_set_sensitive (dialog->conflict_down, FALSE);
+ gtk_widget_set_sensitive (dialog->conflict_up, FALSE);
+
+ gtk_widget_show (dialog->conflict_box);
+
+ return TRUE;
+ } else {
+ gtk_widget_hide (dialog->conflict_box);
+
+ return FALSE;
+ }
+}
+
+static void
+file_names_widget_entry_on_changed (NautilusBatchRenameDialog *dialog)
+{
+ TagData *tag_data;
+ TagData *tag_data0;
+ TagData *tag_data00;
+
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, NUMBERING);
+ tag_data0 = g_hash_table_lookup (dialog->tag_info_table, NUMBERING0);
+ tag_data00 = g_hash_table_lookup (dialog->tag_info_table, NUMBERING00);
+
+ if (dialog->conflict_cancellable != NULL)
+ g_cancellable_cancel (dialog->conflict_cancellable);
+
+ if(dialog->selection == NULL)
+ return;
+
+ if (dialog->duplicates != NULL) {
+ g_list_free_full (dialog->duplicates, conflict_data_free);
+ dialog->duplicates = NULL;
+ }
+
+ if (have_unallowed_character (dialog))
+ return;
+
+ if (dialog->new_names != NULL)
+ g_list_free_full (dialog->new_names, string_free);
+
+ update_tags (dialog);
+
+ if (!tag_data->set && !tag_data0->set && !tag_data00->set) {
+ gtk_label_set_label (GTK_LABEL (dialog->numbering_label), "");
+ gtk_widget_hide (dialog->numbering_order_button);
+ } else {
+ gtk_label_set_label (GTK_LABEL (dialog->numbering_label), _("Automatic Numbering Order"));
+ gtk_widget_show (dialog->numbering_order_button);
+ }
+
+ dialog->new_names = batch_rename_dialog_get_new_names (dialog);
+ dialog->checked_parents = 0;
+
+ file_names_list_has_duplicates_async (dialog,
+ file_names_list_has_duplicates_callback,
+ NULL);
+}
+
+static void
+update_display_text (NautilusBatchRenameDialog *dialog)
+{
+ file_names_widget_entry_on_changed (dialog);
+}
+
+static void
+batch_rename_dialog_mode_changed (NautilusBatchRenameDialog *dialog)
+{
+ if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->format_mode_button))) {
+ gtk_stack_set_visible_child_name (GTK_STACK (dialog->mode_stack), "format");
+
+ dialog->mode = NAUTILUS_BATCH_RENAME_DIALOG_FORMAT;
+
+ gtk_entry_grab_focus_without_selecting (GTK_ENTRY (dialog->name_entry));
+
+ } else {
+ gtk_stack_set_visible_child_name (GTK_STACK (dialog->mode_stack), "replace");
+
+ dialog->mode = NAUTILUS_BATCH_RENAME_DIALOG_REPLACE;
+
+ gtk_entry_grab_focus_without_selecting (GTK_ENTRY (dialog->find_entry));
+ }
+
+ update_display_text (dialog);
+
+}
+
+static void
+add_button_clicked (NautilusBatchRenameDialog *dialog)
+{
+ if (gtk_widget_is_visible (dialog->add_popover))
+ gtk_widget_set_visible (dialog->add_popover, FALSE);
+ else
+ gtk_widget_set_visible (dialog->add_popover, TRUE);
+}
+
+static void
+add_popover_closed (NautilusBatchRenameDialog *dialog)
+{
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->add_button), FALSE);
+}
+
+static void
+numbering_order_button_clicked (NautilusBatchRenameDialog *dialog)
+{
+ if (gtk_widget_is_visible (dialog->numbering_order_popover))
+ gtk_widget_set_visible (dialog->numbering_order_popover, FALSE);
+ else
+ gtk_widget_set_visible (dialog->numbering_order_popover, TRUE);
+}
+
+static void
+numbering_order_popover_closed (NautilusBatchRenameDialog *dialog)
+{
+ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->numbering_order_button), FALSE);
+}
+
+void
+nautilus_batch_rename_dialog_query_finished (NautilusBatchRenameDialog *dialog,
+ GHashTable *hash_table,
+ GList *selection_metadata)
+{
+ GMenuItem *first_created;
+ GMenuItem *last_created;
+ FileMetadata *metadata;
+ TagData *tag_data;
+
+ /* for files with no metadata */
+ if (hash_table != NULL && g_hash_table_size (hash_table) == 0) {
+ g_hash_table_destroy (hash_table);
+
+ hash_table = NULL;
+ }
+
+ if (hash_table == NULL)
+ dialog->create_date = NULL;
+ else
+ dialog->create_date = hash_table;
+
+ if (dialog->create_date != NULL) {
+ first_created = g_menu_item_new ("First Created",
+ "dialog.numbering-order-changed('first-created')");
+
+ g_menu_append_item (dialog->numbering_order_menu, first_created);
+
+ last_created = g_menu_item_new ("Last Created",
+ "dialog.numbering-order-changed('last-created')");
+
+ g_menu_append_item (dialog->numbering_order_menu, last_created);
+
+ }
+
+ dialog->selection_metadata = selection_metadata;
+ metadata = selection_metadata->data;
+
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, CREATION_DATE);
+ if (metadata->creation_date == NULL || g_strcmp0 (metadata->creation_date->str, "") == 0) {
+ disable_action (dialog, "add-creation-date-tag");
+ tag_data->available = FALSE;
+ } else {
+ tag_data->set = FALSE;
+ }
+
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, CAMERA_MODEL);
+ if (metadata->equipment == NULL || g_strcmp0 (metadata->equipment->str, "") == 0) {
+ disable_action (dialog, "add-equipment-tag");
+ tag_data->available = FALSE;
+ } else {
+ tag_data->set = FALSE;
+ }
+
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, SEASON_NUMBER);
+ if (metadata->season == NULL || g_strcmp0 (metadata->season->str, "") == 0) {
+ disable_action (dialog, "add-season-tag");
+ tag_data->available = FALSE;
+ } else {
+ tag_data->set = FALSE;
+ }
+
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, EPISODE_NUMBER);
+ if (metadata->episode_number == NULL || g_strcmp0 (metadata->episode_number->str, "") == 0) {
+ disable_action (dialog, "add-episode-tag");
+ tag_data->available = FALSE;
+ } else {
+ tag_data->set = FALSE;
+ }
+
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, TRACK_NUMBER);
+ if (metadata->track_number == NULL || g_strcmp0 (metadata->track_number->str, "") == 0) {
+ disable_action (dialog, "add-track-number-tag");
+ tag_data->available = FALSE;
+ } else {
+ tag_data->set = FALSE;
+ }
+
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, ARTIST_NAME);
+ if (metadata->artist_name == NULL || g_strcmp0 (metadata->artist_name->str, "") == 0) {
+ disable_action (dialog, "add-artist-name-tag");
+ tag_data->available = FALSE;
+ } else {
+ tag_data->set = FALSE;
+ }
+
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, TITLE);
+ if (metadata->title == NULL || g_strcmp0 (metadata->title->str, "") == 0) {
+ disable_action (dialog, "add-title-tag");
+ tag_data->available = FALSE;
+ } else {
+ tag_data->set = FALSE;
+ }
+}
+
+static void
+update_row_shadowing (GtkWidget *row,
+ gboolean shown)
+{
+ GtkStyleContext *context;
+ GtkStateFlags flags;
+
+ if (!GTK_IS_LIST_BOX_ROW (row))
+ return;
+
+ context = gtk_widget_get_style_context (row);
+ flags = gtk_style_context_get_state (context);
+
+ if (shown)
+ flags |= GTK_STATE_PRELIGHT;
+ else
+ flags &= ~GTK_STATE_PRELIGHT;
+
+ gtk_style_context_set_state (context, flags);
+
+}
+
+static gboolean
+on_leave_event (GtkWidget *widget,
+ GdkEventMotion *event,
+ gpointer user_data)
+{
+ NautilusBatchRenameDialog *dialog;
+
+ dialog = NAUTILUS_BATCH_RENAME_DIALOG (user_data);
+
+ update_row_shadowing (dialog->preselected_row1, FALSE);
+ update_row_shadowing (dialog->preselected_row2, FALSE);
+
+ dialog->preselected_row1 = NULL;
+ dialog->preselected_row2 = NULL;
+
+ return FALSE;
+}
+
+static gboolean
+on_motion (GtkWidget *widget,
+ GdkEventMotion *event,
+ gpointer user_data)
+{
+ GtkListBoxRow *row;
+ NautilusBatchRenameDialog *dialog;
+
+ dialog = NAUTILUS_BATCH_RENAME_DIALOG (user_data);
+
+ if (dialog->preselected_row1 && dialog->preselected_row2) {
+ update_row_shadowing (dialog->preselected_row1, FALSE);
+ update_row_shadowing (dialog->preselected_row2, FALSE);
+ }
+
+ if (widget == dialog->result_listbox) {
+ row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (dialog->original_name_listbox), event->y);
+ update_row_shadowing (GTK_WIDGET (row), TRUE);
+ dialog->preselected_row1 = GTK_WIDGET (row);
+
+ row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (dialog->arrow_listbox), event->y);
+ update_row_shadowing (GTK_WIDGET (row), TRUE);
+ dialog->preselected_row2 = GTK_WIDGET (row);
+
+ }
+
+ if (widget == dialog->arrow_listbox) {
+ row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (dialog->original_name_listbox), event->y);
+ update_row_shadowing (GTK_WIDGET (row), TRUE);
+ dialog->preselected_row1 = GTK_WIDGET (row);
+
+ row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (dialog->result_listbox), event->y);
+ update_row_shadowing (GTK_WIDGET (row), TRUE);
+ dialog->preselected_row2 = GTK_WIDGET (row);
+ }
+
+ if (widget == dialog->original_name_listbox) {
+ row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (dialog->result_listbox), event->y);
+ update_row_shadowing (GTK_WIDGET (row), TRUE);
+ dialog->preselected_row1 = GTK_WIDGET (row);
+
+ row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (dialog->arrow_listbox), event->y);
+ update_row_shadowing (GTK_WIDGET (row), TRUE);
+ dialog->preselected_row2 = GTK_WIDGET (row);
+ }
+
+ return FALSE;
+}
+
+static void
+nautilus_batch_rename_dialog_initialize_actions (NautilusBatchRenameDialog *dialog)
+{
+ GAction *action;
+
+ dialog->action_group = G_ACTION_GROUP (g_simple_action_group_new ());
+
+ g_action_map_add_action_entries (G_ACTION_MAP (dialog->action_group),
+ dialog_entries,
+ G_N_ELEMENTS (dialog_entries),
+ dialog);
+ gtk_widget_insert_action_group (GTK_WIDGET (dialog),
+ "dialog",
+ G_ACTION_GROUP (dialog->action_group));
+
+ action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group),
+ "add-original-file-name-tag");
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
+
+ check_metadata_for_selection (dialog, dialog->selection);
+}
+
+static void
+file_names_widget_on_activate (NautilusBatchRenameDialog *dialog)
+{
+ batch_rename_dialog_on_response (dialog, GTK_RESPONSE_OK, NULL);
+}
+
+static gboolean
+remove_tag (NautilusBatchRenameDialog *dialog,
+ gchar *tag_name,
+ gchar *keyval_name,
+ gboolean is_modifier)
+{
+ TagData *tag_data;
+ gint cursor_position;
+ GString *new_entry_text;
+ GString *entry_text;
+
+ g_object_get (dialog->name_entry, "cursor-position", &cursor_position, NULL);
+
+ tag_data = g_hash_table_lookup (dialog->tag_info_table, tag_name);
+
+ entry_text = g_string_new (gtk_entry_get_text (GTK_ENTRY (dialog->name_entry)));
+
+ if (gtk_editable_get_selection_bounds (GTK_EDITABLE (dialog->name_entry), NULL, NULL) &&
+ cursor_position == tag_data->position + strlen (tag_name) &&
+ g_strcmp0(keyval_name, "BackSpace") == 0)
+ return FALSE;
+
+ if (gtk_editable_get_selection_bounds (GTK_EDITABLE (dialog->name_entry), NULL, NULL) &&
+ cursor_position == tag_data->position &&
+ g_strcmp0(keyval_name, "Delete") == 0)
+ return FALSE;
+
+ if (g_strcmp0(keyval_name, "BackSpace") == 0 && tag_data->set) {
+ if (cursor_position > tag_data->position &&
+ cursor_position <= tag_data->position + strlen (tag_name)) {
+ new_entry_text = g_string_new ("");
+ new_entry_text = g_string_append_len (new_entry_text,
+ entry_text->str,
+ tag_data->position);
+ new_entry_text = g_string_append (new_entry_text,
+ entry_text->str + tag_data->position + strlen
(tag_name));
+
+ gtk_entry_set_text (GTK_ENTRY (dialog->name_entry), new_entry_text->str);
+ gtk_editable_set_position (GTK_EDITABLE (dialog->name_entry), tag_data->position);
+
+ tag_data->set = FALSE;
+
+ g_string_free (new_entry_text, TRUE);
+ g_string_free (entry_text, TRUE);
+
+ return TRUE;
+ }
+ }
+
+ if (g_strcmp0(keyval_name, "Delete") == 0 && tag_data->set) {
+ if (cursor_position >= tag_data->position &&
+ cursor_position < tag_data->position + strlen (tag_name)) {
+ new_entry_text = g_string_new ("");
+ new_entry_text = g_string_append_len (new_entry_text,
+ entry_text->str,
+ tag_data->position);
+ new_entry_text = g_string_append (new_entry_text,
+ entry_text->str + tag_data->position + strlen
(tag_name));
+
+ gtk_entry_set_text (GTK_ENTRY (dialog->name_entry), new_entry_text->str);
+ gtk_editable_set_position (GTK_EDITABLE (dialog->name_entry), tag_data->position);
+
+ tag_data->set = FALSE;
+
+ g_string_free (new_entry_text, TRUE);
+ g_string_free (entry_text, TRUE);
+
+ return TRUE;
+ }
+ }
+
+ if (!is_modifier && tag_data->set &&
+ g_strcmp0(keyval_name, "Left") != 0 &&
+ g_strcmp0(keyval_name, "Right") != 0 &&
+ g_strcmp0(keyval_name, "Return") != 0) {
+ if (cursor_position > tag_data->position &&
+ cursor_position < tag_data->position + strlen (tag_name)) {
+ new_entry_text = g_string_new ("");
+ new_entry_text = g_string_append_len (new_entry_text,
+ entry_text->str,
+ tag_data->position);
+ new_entry_text = g_string_append (new_entry_text,
+ entry_text->str + tag_data->position + strlen
(tag_name));
+
+ gtk_entry_set_text (GTK_ENTRY (dialog->name_entry), new_entry_text->str);
+ gtk_editable_set_position (GTK_EDITABLE (dialog->name_entry), tag_data->position);
+
+ tag_data->set = FALSE;
+
+ g_string_free (new_entry_text, TRUE);
+ g_string_free (entry_text, TRUE);
+
+ return TRUE;
+ }
+ }
+
+ return FALSE;
+}
+
+static gboolean
+on_key_press_event (GtkWidget *widget,
+ GdkEventKey *event,
+ gpointer user_data)
+{
+ NautilusBatchRenameDialog *dialog;
+ gchar* keyval_name;
+ GdkEvent *gdk_event;
+
+ gdk_event = (GdkEvent *) event;
+
+ dialog = NAUTILUS_BATCH_RENAME_DIALOG (user_data);
+
+ keyval_name = gdk_keyval_name (event->keyval);
+
+ if (remove_tag (dialog, ORIGINAL_FILE_NAME, keyval_name, gdk_event->key.is_modifier))
+ return TRUE;
+
+ if (remove_tag (dialog, NUMBERING, keyval_name, gdk_event->key.is_modifier))
+ return TRUE;
+
+ if (remove_tag (dialog, NUMBERING0, keyval_name, gdk_event->key.is_modifier))
+ return TRUE;
+
+ if (remove_tag (dialog, NUMBERING00, keyval_name, gdk_event->key.is_modifier))
+ return TRUE;
+
+ if (remove_tag (dialog, CAMERA_MODEL, keyval_name, gdk_event->key.is_modifier))
+ return TRUE;
+
+ if (remove_tag (dialog, CREATION_DATE, keyval_name, gdk_event->key.is_modifier))
+ return TRUE;
+
+ if (remove_tag (dialog, SEASON_NUMBER, keyval_name, gdk_event->key.is_modifier))
+ return TRUE;
+
+ if (remove_tag (dialog, EPISODE_NUMBER, keyval_name, gdk_event->key.is_modifier))
+ return TRUE;
+
+ if (remove_tag (dialog, TRACK_NUMBER, keyval_name, gdk_event->key.is_modifier))
+ return TRUE;
+
+ if (remove_tag (dialog, ARTIST_NAME, keyval_name, gdk_event->key.is_modifier))
+ return TRUE;
+
+ if (remove_tag (dialog, TITLE, keyval_name, gdk_event->key.is_modifier))
+ return TRUE;
+
+ return FALSE;
+}
+
+static void
+nautilus_batch_rename_dialog_finalize (GObject *object)
+{
+ NautilusBatchRenameDialog *dialog;
+ GList *l;
+
+ dialog = NAUTILUS_BATCH_RENAME_DIALOG (object);
+
+ if (dialog->checking_conflicts) {
+ g_cancellable_cancel (dialog->conflict_cancellable);
+ g_object_unref (dialog->conflict_cancellable);
+ }
+
+ g_list_free (dialog->original_name_listbox_rows);
+ g_list_free (dialog->arrow_listbox_rows);
+ g_list_free (dialog->result_listbox_rows);
+ g_list_free (dialog->listbox_labels_new);
+ g_list_free (dialog->listbox_labels_old);
+ g_list_free (dialog->listbox_icons);
+
+ for (l = dialog->selection_metadata; l != NULL; l = l->next) {
+ FileMetadata *metadata;
+
+ metadata = l->data;
+
+ if (metadata->file_name != NULL)
+ g_string_free (metadata->file_name, TRUE);
+ if (metadata->creation_date != NULL)
+ g_string_free (metadata->creation_date, TRUE);
+ if (metadata->equipment != NULL)
+ g_string_free (metadata->equipment, TRUE);
+ if (metadata->season != NULL)
+ g_string_free (metadata->season, TRUE);
+ if (metadata->episode_number != NULL)
+ g_string_free (metadata->episode_number, TRUE);
+ if (metadata->track_number != NULL)
+ g_string_free (metadata->track_number, TRUE);
+ if (metadata->artist_name != NULL)
+ g_string_free (metadata->artist_name, TRUE);
+ }
+
+ if (dialog->create_date != NULL)
+ 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, conflict_data_free);
+
+ G_OBJECT_CLASS (nautilus_batch_rename_dialog_parent_class)->finalize (object);
+}
+
+static void
+nautilus_batch_rename_dialog_class_init (NautilusBatchRenameDialogClass *klass)
+{
+ GtkWidgetClass *widget_class;
+ GObjectClass *oclass;
+
+ widget_class = GTK_WIDGET_CLASS (klass);
+ oclass = G_OBJECT_CLASS (klass);
+
+ oclass->finalize = nautilus_batch_rename_dialog_finalize;
+
+ gtk_widget_class_set_template_from_resource (widget_class,
"/org/gnome/nautilus/ui/nautilus-batch-rename-dialog.ui");
+
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, grid);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, cancel_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog,
original_name_listbox);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, arrow_listbox);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, result_listbox);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, name_entry);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, rename_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, find_entry);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, replace_entry);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, mode_stack);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, replace_mode_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, format_mode_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, add_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, add_popover);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog,
numbering_order_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, scrolled_window);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog,
numbering_order_popover);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog,
numbering_order_button);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, numbering_order_menu);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, conflict_box);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, conflict_label);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, conflict_up);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, conflict_down);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, add_tag_menu);
+ gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, numbering_label);
+
+ gtk_widget_class_bind_template_callback (widget_class, file_names_widget_entry_on_changed);
+ gtk_widget_class_bind_template_callback (widget_class, file_names_widget_on_activate);
+ gtk_widget_class_bind_template_callback (widget_class, batch_rename_dialog_mode_changed);
+ gtk_widget_class_bind_template_callback (widget_class, add_button_clicked);
+ gtk_widget_class_bind_template_callback (widget_class, add_popover_closed);
+ gtk_widget_class_bind_template_callback (widget_class, numbering_order_button_clicked);
+ gtk_widget_class_bind_template_callback (widget_class, numbering_order_popover_closed);
+ gtk_widget_class_bind_template_callback (widget_class, select_next_conflict_up);
+ gtk_widget_class_bind_template_callback (widget_class, select_next_conflict_down);
+ gtk_widget_class_bind_template_callback (widget_class, batch_rename_dialog_on_response);
+ gtk_widget_class_bind_template_callback (widget_class, on_key_press_event);
+}
+
+GtkWidget*
+nautilus_batch_rename_dialog_new (GList *selection,
+ NautilusDirectory *directory,
+ NautilusWindow *window)
+{
+ NautilusBatchRenameDialog *dialog;
+ gint files_number;
+ GList *l;
+ GString *dialog_title;
+
+ dialog = g_object_new (NAUTILUS_TYPE_BATCH_RENAME_DIALOG, "use-header-bar", TRUE, NULL);
+
+ dialog->selection = selection;
+ dialog->directory = directory;
+ dialog->window = window;
+
+ gtk_window_set_transient_for (GTK_WINDOW (dialog),
+ GTK_WINDOW (window));
+
+ files_number = 0;
+
+ for (l = dialog->selection; l != NULL; l = l->next)
+ files_number++;
+
+ dialog_title = g_string_new ("");
+ g_string_append_printf (dialog_title, "Renaming %d files", files_number);
+ gtk_window_set_title (GTK_WINDOW (dialog), dialog_title->str);
+
+ nautilus_batch_rename_dialog_initialize_actions (dialog);
+
+ dialog->same_parent = !NAUTILUS_IS_SEARCH_DIRECTORY (directory);
+
+ if (!dialog->same_parent)
+ dialog->distinct_parents = distinct_file_parents (dialog->selection);
+ else
+ dialog->distinct_parents = NULL;
+
+ update_display_text (dialog);
+
+ fill_display_listbox (dialog);
+
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (window)), NULL);
+
+ g_string_free (dialog_title, TRUE);
+
+ return GTK_WIDGET (dialog);
+}
+
+static void
+nautilus_batch_rename_dialog_init (NautilusBatchRenameDialog *self)
+{
+ TagData *tag_data;
+
+ gtk_widget_init_template (GTK_WIDGET (self));
+
+ gtk_list_box_set_header_func (GTK_LIST_BOX (self->original_name_listbox),
+ (GtkListBoxUpdateHeaderFunc) listbox_header_func,
+ self,
+ NULL);
+ gtk_list_box_set_header_func (GTK_LIST_BOX (self->arrow_listbox),
+ (GtkListBoxUpdateHeaderFunc) listbox_header_func,
+ self,
+ NULL);
+ gtk_list_box_set_header_func (GTK_LIST_BOX (self->result_listbox),
+ (GtkListBoxUpdateHeaderFunc) listbox_header_func,
+ self,
+ NULL);
+
+
+ self->mode = NAUTILUS_BATCH_RENAME_DIALOG_FORMAT;
+
+ gtk_popover_bind_model (GTK_POPOVER (self->numbering_order_popover),
+ G_MENU_MODEL (self->numbering_order_menu),
+ NULL);
+ gtk_popover_bind_model (GTK_POPOVER (self->add_popover),
+ G_MENU_MODEL (self->add_tag_menu),
+ NULL);
+
+ gtk_label_set_ellipsize (GTK_LABEL (self->conflict_label), PANGO_ELLIPSIZE_END);
+ gtk_label_set_max_width_chars (GTK_LABEL (self->conflict_label), 1);
+
+ self->duplicates = NULL;
+ self->new_names = NULL;
+
+ self->checking_conflicts = FALSE;
+
+ self->rename_clicked = FALSE;
+
+
+ self->tag_info_table = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ (GDestroyNotify) g_free);
+ tag_data = g_new (TagData, 1);
+ tag_data->available = TRUE;
+ tag_data->set = TRUE;
+ tag_data->position = 0;
+ g_hash_table_insert (self->tag_info_table, g_strdup (ORIGINAL_FILE_NAME), tag_data);
+
+ tag_data = g_new (TagData, 1);
+ tag_data->available = TRUE;
+ tag_data->set = FALSE;
+ tag_data->position = 0;
+ g_hash_table_insert (self->tag_info_table, g_strdup (NUMBERING), tag_data);
+
+ tag_data = g_new (TagData, 1);
+ tag_data->available = TRUE;
+ tag_data->set = FALSE;
+ tag_data->position = 0;
+ g_hash_table_insert (self->tag_info_table, g_strdup (NUMBERING0), tag_data);
+
+ tag_data = g_new (TagData, 1);
+ tag_data->available = TRUE;
+ tag_data->set = FALSE;
+ tag_data->position = 0;
+ g_hash_table_insert (self->tag_info_table, g_strdup (NUMBERING00), tag_data);
+
+ tag_data = g_new (TagData, 1);
+ tag_data->available = FALSE;
+ tag_data->set = FALSE;
+ tag_data->position = 0;
+ g_hash_table_insert (self->tag_info_table, g_strdup (CREATION_DATE), tag_data);
+
+ tag_data = g_new (TagData, 1);
+ tag_data->available = FALSE;
+ tag_data->set = FALSE;
+ tag_data->position = 0;
+ g_hash_table_insert (self->tag_info_table, g_strdup (CAMERA_MODEL), tag_data);
+
+ tag_data = g_new (TagData, 1);
+ tag_data->available = FALSE;
+ tag_data->set = FALSE;
+ tag_data->position = 0;
+ g_hash_table_insert (self->tag_info_table, g_strdup (SEASON_NUMBER), tag_data);
+
+ tag_data = g_new (TagData, 1);
+ tag_data->available = FALSE;
+ tag_data->set = FALSE;
+ tag_data->position = 0;
+ g_hash_table_insert (self->tag_info_table, g_strdup (EPISODE_NUMBER), tag_data);
+
+ tag_data = g_new (TagData, 1);
+ tag_data->available = FALSE;
+ tag_data->set = FALSE;
+ tag_data->position = 0;
+ g_hash_table_insert (self->tag_info_table, g_strdup (TRACK_NUMBER), tag_data);
+
+ tag_data = g_new (TagData, 1);
+ tag_data->available = FALSE;
+ tag_data->set = FALSE;
+ tag_data->position = 0;
+ g_hash_table_insert (self->tag_info_table, g_strdup (ARTIST_NAME), tag_data);
+
+ tag_data = g_new (TagData, 1);
+ tag_data->available = FALSE;
+ tag_data->set = FALSE;
+ tag_data->position = 0;
+ g_hash_table_insert (self->tag_info_table, g_strdup (TITLE), tag_data);
+
+ gtk_entry_set_text (GTK_ENTRY (self->name_entry),ORIGINAL_FILE_NAME);
+
+ self->row_height = -1;
+
+ g_signal_connect (self->original_name_listbox, "row-selected", G_CALLBACK (row_selected), self);
+ g_signal_connect (self->arrow_listbox, "row-selected", G_CALLBACK (row_selected), self);
+ g_signal_connect (self->result_listbox, "row-selected", G_CALLBACK (row_selected), self);
+
+ self->size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
+
+ g_signal_connect (self->original_name_listbox,
+ "motion-notify-event",
+ G_CALLBACK (on_motion),
+ self);
+ g_signal_connect (self->result_listbox,
+ "motion-notify-event",
+ G_CALLBACK (on_motion),
+ self);
+ g_signal_connect (self->arrow_listbox,
+ "motion-notify-event",
+ G_CALLBACK (on_motion),
+ self);
+
+ g_signal_connect (self->original_name_listbox,
+ "leave-notify-event",
+ G_CALLBACK (on_leave_event),
+ self);
+ g_signal_connect (self->result_listbox,
+ "leave-notify-event",
+ G_CALLBACK (on_leave_event),
+ self);
+ g_signal_connect (self->arrow_listbox,
+ "leave-notify-event",
+ G_CALLBACK (on_leave_event),
+ self);
+}
\ No newline at end of file
diff --git a/src/nautilus-batch-rename-dialog.h b/src/nautilus-batch-rename-dialog.h
new file mode 100644
index 0000000..c9dd45a
--- /dev/null
+++ b/src/nautilus-batch-rename-dialog.h
@@ -0,0 +1,84 @@
+
+#ifndef NAUTILUS_BATCH_RENAME_DIALOG_H
+#define NAUTILUS_BATCH_RENAME_DIALOG_H
+
+#include <glib.h>
+#include <glib/gprintf.h>
+#include <gtk/gtk.h>
+#include "nautilus-files-view.h"
+
+G_BEGIN_DECLS
+
+#define ORIGINAL_FILE_NAME "[Original file name]"
+#define NUMBERING "[1, 2, 3]"
+#define NUMBERING0 "[01, 02, 03]"
+#define NUMBERING00 "[001, 002, 003]"
+#define CAMERA_MODEL "[Camera model]"
+#define CREATION_DATE "[Creation date]"
+#define SEASON_NUMBER "[Season number]"
+#define EPISODE_NUMBER "[Episode number]"
+#define TRACK_NUMBER "[Track number]"
+#define ARTIST_NAME "[Artist name]"
+#define TITLE "[Title]"
+
+typedef enum {
+ NAUTILUS_BATCH_RENAME_DIALOG_APPEND = 0,
+ NAUTILUS_BATCH_RENAME_DIALOG_PREPEND = 1,
+ NAUTILUS_BATCH_RENAME_DIALOG_REPLACE = 2,
+ NAUTILUS_BATCH_RENAME_DIALOG_FORMAT = 3,
+} NautilusBatchRenameDialogMode;
+
+typedef enum {
+ ORIGINAL_ASCENDING = 0,
+ ORIGINAL_DESCENDING = 1,
+ FIRST_MODIFIED = 2,
+ LAST_MODIFIED = 3,
+ FIRST_CREATED = 4,
+ LAST_CREATED = 5,
+} SortingMode;
+
+typedef struct
+{
+ gchar *name;
+ gint index;
+} ConflictData;
+
+typedef struct {
+ GString *file_name;
+
+ /* Photo */
+ GString *creation_date;
+ GString *equipment;
+
+ /* Video */
+ GString *season;
+ GString *episode_number;
+
+ /* Music */
+ GString *track_number;
+ GString *artist_name;
+ GString *title;
+} FileMetadata;
+
+#define NAUTILUS_TYPE_BATCH_RENAME_DIALOG (nautilus_batch_rename_dialog_get_type())
+
+G_DECLARE_FINAL_TYPE (NautilusBatchRenameDialog, nautilus_batch_rename_dialog, NAUTILUS,
BATCH_RENAME_DIALOG, GtkDialog);
+
+GtkWidget* nautilus_batch_rename_dialog_new (GList *selection,
+ NautilusDirectory *directory,
+ NautilusWindow *window);
+
+void nautilus_batch_rename_dialog_query_finished (NautilusBatchRenameDialog *dialog,
+ GHashTable *hash_table,
+ GList
*selection_metadata);
+
+void check_conflict_for_file (NautilusBatchRenameDialog *dialog,
+ NautilusDirectory *directory,
+ GList *files);
+
+gint compare_int (gconstpointer a,
+ gconstpointer b);
+
+G_END_DECLS
+
+#endif
\ No newline at end of file
diff --git a/src/nautilus-batch-rename-utilities.c b/src/nautilus-batch-rename-utilities.c
new file mode 100644
index 0000000..a1f59bf
--- /dev/null
+++ b/src/nautilus-batch-rename-utilities.c
@@ -0,0 +1,985 @@
+/* nautilus-batch-rename-utilities.c
+ *
+ * Copyright (C) 2016 Alexandru Pandelea <alexandru pandelea gmail com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "nautilus-batch-rename-dialog.h"
+#include "nautilus-batch-rename-utilities.h"
+#include "nautilus-file.h"
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <string.h>
+#include <stdarg.h>
+#include <eel/eel-vfs-extensions.h>
+
+#define HAVE_CONFLICT 1
+
+typedef struct {
+ NautilusFile *file;
+ gint position;
+} CreateDateElem;
+
+typedef struct {
+ NautilusBatchRenameDialog *dialog;
+ GHashTable *hash_table;
+
+ GList *selection_metadata;
+
+ gboolean have_creation_date;
+ gboolean have_equipment;
+ gboolean have_season;
+ gboolean have_episode_number;
+ gboolean have_track_number;
+ gboolean have_artist_name;
+ gboolean have_title;
+} QueryData;
+
+static void cursor_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data);
+
+void
+string_free (gpointer mem)
+{
+ if (mem != NULL)
+ g_string_free (mem, TRUE);
+}
+
+void
+conflict_data_free (gpointer mem)
+{
+ ConflictData *data = mem;
+
+ g_free (data->name);
+ g_free (data);
+}
+
+static GString*
+batch_rename_dialog_replace (gchar *string,
+ gchar *substring,
+ gchar *replacement)
+{
+ GString *new_string;
+ gchar **splitted_string;
+ gint i, n_splits;
+
+ new_string = g_string_new ("");
+
+ if (substring == NULL || replacement == NULL) {
+ g_string_append (new_string, string);
+
+ return new_string;
+ }
+
+ if (g_utf8_strlen (substring, -1) == 0) {
+ g_string_append (new_string, string);
+
+ return new_string;
+ }
+
+ splitted_string = g_strsplit (string, substring, -1);
+ if (splitted_string == NULL) {
+ g_string_append (new_string, string);
+
+ return new_string;
+ }
+
+ n_splits = g_strv_length (splitted_string);
+
+ for (i = 0; i < n_splits; i++) {
+ g_string_append (new_string, splitted_string[i]);
+
+ if (i != n_splits - 1)
+ g_string_append (new_string, replacement);
+ }
+
+ g_strfreev (splitted_string);
+
+ return new_string;
+}
+
+GString*
+batch_rename_dialog_replace_label_text (gchar *string,
+ const gchar *substring)
+{
+ GString *new_string;
+ gchar **splitted_string;
+ gchar *token;
+ gint i, n_splits;
+
+ new_string = g_string_new ("");
+
+ if (substring == NULL || g_strcmp0 (substring, "") == 0) {
+ token = g_markup_escape_text (string, strlen (string));
+ new_string = g_string_append (new_string, token);
+ g_free (token);
+
+ return new_string;
+ }
+
+ splitted_string = g_strsplit (string, substring, -1);
+ if (splitted_string == NULL) {
+ token = g_markup_escape_text (string, strlen (string));
+ new_string = g_string_append (new_string, token);
+ g_free (token);
+
+ return new_string;
+ }
+
+ n_splits = g_strv_length (splitted_string);
+
+ for (i = 0; i < n_splits; i++) {
+ token = g_markup_escape_text (splitted_string[i], strlen (splitted_string[i]));
+ new_string = g_string_append (new_string, token);
+
+ g_free (token);
+
+ if (i != n_splits - 1) {
+ token = g_markup_escape_text (substring, strlen (substring));
+ g_string_append_printf (new_string,
+ "<span background=\'#f57900\' color='white'>%s</span>",
+ token);
+
+ g_free (token);
+ }
+ }
+
+ g_strfreev (splitted_string);
+
+ return new_string;
+}
+
+static gchar*
+get_metadata (GList *selection_metadata,
+ gchar *file_name,
+ gchar *metadata)
+{
+ GList *l;
+ FileMetadata *file_metadata;
+
+ for (l = selection_metadata; l != NULL; l = l->next) {
+ file_metadata = l->data;
+ if (g_strcmp0 (file_name, file_metadata->file_name->str) == 0) {
+ if (g_strcmp0 (metadata, "creation_date") == 0 &&
+ file_metadata->creation_date != NULL &&
+ file_metadata->creation_date->len != 0)
+ return file_metadata->creation_date->str;
+
+ if (g_strcmp0 (metadata, "equipment") == 0 &&
+ file_metadata->equipment != NULL &&
+ file_metadata->equipment->len != 0)
+ return file_metadata->equipment->str;
+
+ if (g_strcmp0 (metadata, "season") == 0 &&
+ file_metadata->season != NULL &&
+ file_metadata->season->len != 0)
+ return file_metadata->season->str;
+
+ if (g_strcmp0 (metadata, "episode_number") == 0 &&
+ file_metadata->episode_number != NULL &&
+ file_metadata->episode_number->len != 0)
+ return file_metadata->episode_number->str;
+
+ if (g_strcmp0 (metadata, "track_number") == 0 &&
+ file_metadata->track_number != NULL &&
+ file_metadata->track_number->len != 0)
+ return file_metadata->track_number->str;
+
+ if (g_strcmp0 (metadata, "artist_name") == 0 &&
+ file_metadata->artist_name != NULL &&
+ file_metadata->artist_name->len != 0)
+ return file_metadata->artist_name->str;
+
+ if (g_strcmp0 (metadata, "title") == 0 &&
+ file_metadata->title != NULL &&
+ file_metadata->title->len != 0)
+ return file_metadata->title->str;
+ }
+ }
+
+ return NULL;
+}
+
+static GString*
+batch_rename_dialog_format (NautilusFile *file,
+ GList *tags_list,
+ GList *selection_metadata,
+ gint count)
+{
+ GDateTime *datetime;
+ GList *l;
+ GString *tag;
+ GString *new_name;
+ GString *create_date;
+ gboolean added_tag;
+ g_autofree gchar *file_name;
+ g_autofree gchar *extension;
+ gchar *metadata;
+ gchar **splitted_date;
+ gchar *base_name;
+ gchar *date;
+
+ file_name = nautilus_file_get_display_name (file);
+ extension = nautilus_file_get_extension (file);
+
+ new_name = g_string_new ("");
+
+ for (l = tags_list; l != NULL; l = l->next) {
+ tag = l->data;
+ added_tag = FALSE;
+
+ if (!added_tag && g_strcmp0 (tag->str, ORIGINAL_FILE_NAME) == 0) {
+ base_name = eel_filename_strip_extension (file_name);
+
+ new_name = g_string_append (new_name, base_name);
+
+ added_tag = TRUE;
+ g_free (base_name);
+ }
+
+ if (!added_tag && g_strcmp0 (tag->str, NUMBERING) == 0) {
+ g_string_append_printf (new_name, "%d", count);
+ added_tag = TRUE;
+ }
+
+ if (!added_tag && g_strcmp0 (tag->str, NUMBERING0) == 0) {
+ if (count < 10) {
+ g_string_append_printf (new_name, "0%d", count);
+ } else {
+ g_string_append_printf (new_name, "%d", count);
+ }
+
+ added_tag = TRUE;
+ }
+
+ if (!added_tag && g_strcmp0 (tag->str, NUMBERING00) == 0) {
+ if (count < 10) {
+ g_string_append_printf (new_name, "00%d", count);
+ } else {
+ if (count < 100) {
+ g_string_append_printf (new_name, "0%d", count);
+ } else {
+ g_string_append_printf (new_name, "%d", count);
+ }
+ }
+
+ added_tag = TRUE;
+ }
+
+ if (!added_tag && g_strcmp0 (tag->str, CAMERA_MODEL) == 0) {
+ metadata = get_metadata (selection_metadata, file_name, "equipment");
+
+ if (metadata != NULL) {
+ new_name = g_string_append (new_name, metadata);
+ added_tag = TRUE;
+ }
+ }
+
+ if (!added_tag && g_strcmp0 (tag->str, CREATION_DATE) == 0) {
+ metadata = get_metadata (selection_metadata, file_name, "creation_date");
+
+ if (metadata != NULL) {
+ splitted_date = g_strsplit_set (metadata, "T:-Z", -1);
+
+ datetime = g_date_time_new_local (atoi (splitted_date[0]),
+ atoi (splitted_date[1]),
+ atoi (splitted_date[2]),
+ atoi (splitted_date[3]),
+ atoi (splitted_date[4]),
+ atoi (splitted_date[5]));
+
+ date = g_date_time_format (datetime, "%x");
+
+ if (strstr (date, "/") != NULL) {
+ create_date = batch_rename_dialog_replace (date, "/", "-");
+ new_name = g_string_append (new_name, create_date->str);
+
+ g_string_free (create_date, TRUE);
+ } else {
+ new_name = g_string_append (new_name, date);
+ }
+
+ added_tag = TRUE;
+
+ g_free (date);
+ g_strfreev (splitted_date);
+ }
+ }
+
+ if (!added_tag && g_strcmp0 (tag->str, SEASON_NUMBER) == 0) {
+ metadata = get_metadata (selection_metadata, file_name, "season");
+
+ if (metadata != NULL) {
+ new_name = g_string_append (new_name, metadata);
+ added_tag = TRUE;
+ }
+ }
+
+ if (!added_tag && g_strcmp0 (tag->str, EPISODE_NUMBER) == 0) {
+ metadata = get_metadata (selection_metadata, file_name, "episode_number");
+
+ if (metadata != NULL) {
+ new_name = g_string_append (new_name, metadata);
+ added_tag = TRUE;
+ }
+ }
+
+ if (!added_tag && g_strcmp0 (tag->str, TRACK_NUMBER) == 0) {
+ metadata = get_metadata (selection_metadata, file_name, "track_number");
+
+ if (metadata != NULL) {
+ new_name = g_string_append (new_name, metadata);
+ added_tag = TRUE;
+ }
+ }
+
+ if (!added_tag && g_strcmp0 (tag->str, ARTIST_NAME) == 0) {
+ metadata = get_metadata (selection_metadata, file_name, "artist_name");
+
+ if (metadata != NULL) {
+ new_name = g_string_append (new_name, metadata);
+ added_tag = TRUE;
+ }
+ }
+
+ if (!added_tag && g_strcmp0 (tag->str, TITLE) == 0) {
+ metadata = get_metadata (selection_metadata, file_name, "title");
+
+ if (metadata != NULL) {
+ new_name = g_string_append (new_name, metadata);
+ added_tag = TRUE;
+ }
+ }
+
+ if (!added_tag)
+ new_name = g_string_append (new_name, tag->str);
+ }
+
+ if (g_strcmp0 (new_name->str, "") == 0) {
+ new_name = g_string_append (new_name, file_name);
+ } else {
+ if (extension != NULL)
+ new_name = g_string_append (new_name, extension);
+ }
+
+ return new_name;
+}
+
+GList*
+batch_rename_dialog_get_new_names_list (NautilusBatchRenameDialogMode mode,
+ GList *selection,
+ GList *tags_list,
+ GList *selection_metadata,
+ gchar *entry_text,
+ gchar *replace_text)
+{
+ GList *l;
+ GList *result;
+ GString *file_name;
+ GString *new_name;
+ NautilusFile *file;
+ gchar *name;
+ gint count;
+
+ result = NULL;
+ count = 1;
+ file_name = g_string_new ("");
+
+ for (l = selection; l != NULL; l = l->next) {
+ file = NAUTILUS_FILE (l->data);
+
+ file_name = g_string_new ("");
+ name = nautilus_file_get_name (file);
+ g_string_append (file_name, name);
+
+ /* get the new name here and add it to the list*/
+ if (mode == NAUTILUS_BATCH_RENAME_DIALOG_FORMAT) {
+ new_name = batch_rename_dialog_format (file,
+ tags_list,
+ selection_metadata,
+ count++);
+ result = g_list_prepend (result, new_name);
+ }
+
+ if (mode == NAUTILUS_BATCH_RENAME_DIALOG_REPLACE) {
+ new_name = batch_rename_dialog_replace (file_name->str,
+ entry_text,
+ replace_text);
+ result = g_list_prepend (result, new_name);
+ }
+
+ g_string_free (file_name, TRUE);
+ g_free (name);
+ }
+
+ return result;
+}
+
+/* There is a case that a new name for a file conflicts with an existing file name
+ * in the directory but it's not a problem because the file in the directory that
+ * conflicts is part of the batch renaming selection and it's going to change the name anyway. */
+gboolean
+file_name_conflicts_with_results (GList *selection,
+ GList *new_names,
+ GString *old_name,
+ gchar *parent_uri)
+{
+ GList *l1;
+ GList *l2;
+ NautilusFile *selection_file;
+ gchar *name1;
+ GString *new_name;
+ gchar *selection_parent_uri;
+
+ for (l1 = selection, l2 = new_names; l1 != NULL && l2 != NULL; l1 = l1->next, l2 = l2->next) {
+ selection_file = NAUTILUS_FILE (l1->data);
+ name1 = nautilus_file_get_name (selection_file);
+
+ selection_parent_uri = nautilus_file_get_parent_uri (selection_file);
+
+ if (g_strcmp0 (name1, old_name->str) == 0) {
+ new_name = l2->data;
+
+ /* if the name didn't change, then there's a conflict */
+ if (g_string_equal (old_name, new_name) &&
+ (parent_uri == NULL || g_strcmp0 (parent_uri, selection_parent_uri) == 0))
+ return FALSE;
+
+
+ /* if this file exists and it changed it's name, then there's no
+ * conflict */
+ return TRUE;
+ }
+
+ g_free (selection_parent_uri);
+ }
+
+ /* such a file doesn't exist so there actually is a conflict */
+ return FALSE;
+}
+
+gint
+compare_files_by_name_ascending (gconstpointer a,
+ gconstpointer b)
+{
+ NautilusFile *file1;
+ NautilusFile *file2;
+
+ file1 = NAUTILUS_FILE (a);
+ file2 = NAUTILUS_FILE (b);
+
+ return nautilus_file_compare_for_sort (file1,file2,
+ NAUTILUS_FILE_SORT_BY_DISPLAY_NAME,
+ FALSE, FALSE);
+}
+
+gint
+compare_files_by_name_descending (gconstpointer a,
+ gconstpointer b)
+{
+ NautilusFile *file1;
+ NautilusFile *file2;
+
+ file1 = NAUTILUS_FILE (a);
+ file2 = NAUTILUS_FILE (b);
+
+ return nautilus_file_compare_for_sort (file1,file2,
+ NAUTILUS_FILE_SORT_BY_DISPLAY_NAME,
+ FALSE, TRUE);
+}
+
+gint
+compare_files_by_first_modified (gconstpointer a,
+ gconstpointer b)
+{
+ NautilusFile *file1;
+ NautilusFile *file2;
+
+ file1 = NAUTILUS_FILE (a);
+ file2 = NAUTILUS_FILE (b);
+
+ return nautilus_file_compare_for_sort (file1,file2,
+ NAUTILUS_FILE_SORT_BY_MTIME,
+ FALSE, FALSE);
+}
+
+gint
+compare_files_by_last_modified (gconstpointer a,
+ gconstpointer b)
+{
+ NautilusFile *file1;
+ NautilusFile *file2;
+
+ file1 = NAUTILUS_FILE (a);
+ file2 = NAUTILUS_FILE (b);
+
+ return nautilus_file_compare_for_sort (file1,file2,
+ NAUTILUS_FILE_SORT_BY_MTIME,
+ FALSE, TRUE);
+}
+
+gint
+compare_files_by_first_created (gconstpointer a,
+ gconstpointer b)
+{
+ CreateDateElem *elem1;
+ CreateDateElem *elem2;
+
+ elem1 = (CreateDateElem*) a;
+ elem2 = (CreateDateElem*) b;
+
+ return elem1->position - elem2->position;
+}
+
+gint
+compare_files_by_last_created (gconstpointer a,
+ gconstpointer b)
+{
+ CreateDateElem *elem1;
+ CreateDateElem *elem2;
+
+ elem1 = (CreateDateElem*) a;
+ elem2 = (CreateDateElem*) b;
+
+ return elem2->position - elem1->position;
+}
+
+GList*
+nautilus_batch_rename_dialog_sort (GList *selection,
+ SortingMode mode,
+ GHashTable *creation_date_table)
+{
+ GList *l,*l2;
+ NautilusFile *file;
+ GList *create_date_list;
+ GList *create_date_list_sorted;
+ gchar *name;
+
+ if (mode == ORIGINAL_ASCENDING)
+ return g_list_sort (selection, compare_files_by_name_ascending);
+
+ if (mode == ORIGINAL_DESCENDING) {
+ return g_list_sort (selection, compare_files_by_name_descending);
+ }
+
+ if (mode == FIRST_MODIFIED) {
+ return g_list_sort (selection, compare_files_by_first_modified);
+ }
+
+ if (mode == LAST_MODIFIED) {
+ return g_list_sort (selection, compare_files_by_last_modified);
+ }
+
+ if (mode == FIRST_CREATED || mode == LAST_CREATED) {
+ create_date_list = NULL;
+
+ for (l = selection; l != NULL; l = l->next) {
+ CreateDateElem *elem;
+ elem = g_new (CreateDateElem, 1);
+
+ file = NAUTILUS_FILE (l->data);
+
+ name = nautilus_file_get_name (file);
+ elem->file = file;
+ elem->position = GPOINTER_TO_INT (g_hash_table_lookup (creation_date_table, name));
+ g_free (name);
+
+ create_date_list = g_list_prepend (create_date_list, elem);
+ }
+
+ if (mode == FIRST_CREATED)
+ create_date_list_sorted = g_list_sort (create_date_list,
+ compare_files_by_first_created);
+ else
+ create_date_list_sorted = g_list_sort (create_date_list,
+ compare_files_by_last_created);
+
+ for (l = selection, l2 = create_date_list_sorted; l2 != NULL; l = l->next, l2 = l2->next) {
+ CreateDateElem *elem = l2->data;
+ l->data = elem->file;
+ }
+
+ g_list_free_full (create_date_list, g_free);
+ }
+
+ return selection;
+}
+
+static void
+cursor_next (QueryData *data,
+ TrackerSparqlCursor *cursor)
+{
+ tracker_sparql_cursor_next_async (cursor,
+ NULL,
+ cursor_callback,
+ data);
+}
+
+static void
+cursor_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GHashTable *hash_table;
+ TrackerSparqlCursor *cursor;
+ gboolean success;
+ QueryData *data;
+ GError *error;
+ GList *l;
+ FileMetadata *metadata;
+ FileMetadata *metadata_clear;
+ const gchar *file_name;
+ const gchar *creation_date;
+ const gchar *equipment;
+ const gchar *season_number;
+ const gchar *episode_number;
+ const gchar *track_number;
+ const gchar *artist_name;
+ const gchar *title;
+
+ error = NULL;
+ metadata = NULL;
+
+ cursor = TRACKER_SPARQL_CURSOR (object);
+ data = user_data;
+ hash_table = data->hash_table;
+
+ success = tracker_sparql_cursor_next_finish (cursor, result, &error);
+ if (!success) {
+ g_clear_error (&error);
+ g_clear_object (&cursor);
+
+ nautilus_batch_rename_dialog_query_finished (data->dialog, data->hash_table,
data->selection_metadata);
+
+ return;
+ }
+
+ creation_date = tracker_sparql_cursor_get_string (cursor, 1, NULL);
+ equipment = tracker_sparql_cursor_get_string (cursor, 2, NULL);
+ season_number = tracker_sparql_cursor_get_string (cursor, 3, NULL);
+ episode_number = tracker_sparql_cursor_get_string (cursor, 4, NULL);
+ track_number = tracker_sparql_cursor_get_string (cursor, 5, NULL);
+ artist_name = tracker_sparql_cursor_get_string (cursor, 6, NULL);
+ title = tracker_sparql_cursor_get_string (cursor, 7, NULL);
+
+ /* creation date used for sorting criteria */
+ if (creation_date == NULL) {
+ if (hash_table != NULL)
+ g_hash_table_destroy (hash_table);
+
+ data->hash_table = NULL;
+ data->have_creation_date = FALSE;
+ } else {
+ if (data->have_creation_date){
+ g_hash_table_insert (hash_table,
+ g_strdup (tracker_sparql_cursor_get_string (cursor, 0, NULL)),
+ GINT_TO_POINTER (g_hash_table_size (hash_table)));
+ }
+ }
+ file_name = tracker_sparql_cursor_get_string (cursor, 0, NULL);
+ for (l = data->selection_metadata; l != NULL; l = l->next) {
+ metadata = l->data;
+
+ if (g_strcmp0 (file_name, metadata->file_name->str) == 0)
+ break;
+ }
+
+ /* Metadata to be used in file name
+ * creation date */
+ if (data->have_creation_date) {
+ if (creation_date == NULL) {
+ data->have_creation_date = FALSE;
+
+ for (l = data->selection_metadata; l != NULL; l = l->next) {
+ metadata_clear = l->data;
+
+ g_string_free (metadata_clear->creation_date, TRUE);
+ metadata_clear->creation_date = NULL;
+ }
+ } else {
+ g_string_append (metadata->creation_date,
+ creation_date);
+ }
+ }
+
+ /* equipment */
+ if (data->have_equipment) {
+ if (equipment == NULL) {
+ data->have_equipment = FALSE;
+
+ for (l = data->selection_metadata; l != NULL; l = l->next) {
+ metadata_clear = l->data;
+
+ g_string_free (metadata_clear->equipment, TRUE);
+ metadata_clear->equipment = NULL;
+ }
+ } else {
+ g_string_append (metadata->equipment,
+ equipment);
+ }
+ }
+
+ /* season number */
+ if (data->have_season) {
+ if (season_number == NULL) {
+ data->have_season = FALSE;
+
+ for (l = data->selection_metadata; l != NULL; l = l->next) {
+ metadata_clear = l->data;
+
+ g_string_free (metadata_clear->season, TRUE);
+ metadata_clear->season = NULL;
+ }
+ } else {
+ g_string_append (metadata->season,
+ season_number);
+ }
+ }
+
+ /* episode number */
+ if (data->have_episode_number) {
+ if (episode_number == NULL) {
+ data->have_episode_number = FALSE;
+
+ for (l = data->selection_metadata; l != NULL; l = l->next) {
+ metadata_clear = l->data;
+
+ g_string_free (metadata_clear->episode_number, TRUE);
+ metadata_clear->episode_number = NULL;
+ }
+ } else {
+ g_string_append (metadata->episode_number,
+ episode_number);
+ }
+ }
+
+ /* track number */
+ if (data->have_track_number) {
+ if (track_number == NULL) {
+ data->have_track_number = FALSE;
+ for (l = data->selection_metadata; l != NULL; l = l->next) {
+ metadata_clear = l->data;
+
+ g_string_free (metadata_clear->track_number, TRUE);
+ metadata_clear->track_number = NULL;
+ }
+ } else {
+ g_string_append (metadata->track_number,
+ track_number);
+ }
+ }
+
+ /* artist name */
+ if (data->have_artist_name) {
+ if (artist_name == NULL) {
+ data->have_artist_name = FALSE;
+
+ for (l = data->selection_metadata; l != NULL; l = l->next) {
+ metadata_clear = l->data;
+
+ g_string_free (metadata_clear->artist_name, TRUE);
+ metadata_clear->artist_name = NULL;
+ }
+ } else {
+ g_string_append (metadata->artist_name,
+ artist_name);
+ }
+ }
+
+ /* title */
+ if (data->have_title) {
+ if (title == NULL) {
+ data->have_title = FALSE;
+
+ for (l = data->selection_metadata; l != NULL; l = l->next) {
+ metadata_clear = l->data;
+
+ g_string_free (metadata_clear->title, TRUE);
+ metadata_clear->title = NULL;
+ }
+ } else {
+ g_string_append (metadata->title,
+ title);
+ }
+ }
+
+ /* Get next */
+ cursor_next (data, cursor);
+}
+
+static void
+batch_rename_dialog_query_callback (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ TrackerSparqlConnection *connection;
+ TrackerSparqlCursor *cursor;
+ QueryData *data;
+ GError *error;
+
+ error = NULL;
+
+ connection = TRACKER_SPARQL_CONNECTION (object);
+ data = user_data;
+
+ cursor = tracker_sparql_connection_query_finish (connection,
+ result,
+ &error);
+
+ if (error != NULL) {
+ g_error_free (error);
+
+ nautilus_batch_rename_dialog_query_finished (data->dialog,
+ data->hash_table,
+ data->selection_metadata);
+ } else {
+ cursor_next (data, cursor);
+ }
+}
+
+void
+check_metadata_for_selection (NautilusBatchRenameDialog *dialog,
+ GList *selection)
+{
+ TrackerSparqlConnection *connection;
+ GString *query;
+ GHashTable *hash_table;
+ GList *l;
+ NautilusFile *file;
+ GError *error;
+ QueryData *data;
+ gchar *file_name;
+ FileMetadata *metadata;
+ GList *selection_metadata;
+
+ error = NULL;
+ selection_metadata = NULL;
+
+ query = g_string_new ("SELECT "
+ "nfo:fileName(?file) "
+ "nie:contentCreated(?file) "
+ "nfo:model(nfo:equipment(?file)) "
+ "nmm:season(?file) "
+ "nmm:episodeNumber(?file) "
+ "nmm:trackNumber(?file) "
+ "nmm:artistName(nmm:performer(?file)) "
+ "nie:title(?file) "
+ "WHERE { ?file a nfo:FileDataObject. ");
+
+ g_string_append_printf (query,
+ "FILTER(tracker:uri-is-parent('%s', nie:url(?file))) ",
+ nautilus_file_get_parent_uri (NAUTILUS_FILE (selection->data)));
+
+ for (l = selection; l != NULL; l = l->next) {
+ file = NAUTILUS_FILE (l->data);
+ file_name = nautilus_file_get_name (file);
+
+ if (l == selection)
+ g_string_append_printf (query,
+ "FILTER (nfo:fileName(?file) = '%s' ",
+ file_name);
+ else
+ g_string_append_printf (query,
+ "|| nfo:fileName(?file) = '%s' ",
+ file_name);
+
+ metadata = g_new (FileMetadata, 1);
+ metadata->file_name = g_string_new (file_name);
+ metadata->creation_date = g_string_new ("");
+ metadata->equipment = g_string_new ("");
+ metadata->season = g_string_new ("");
+ metadata->episode_number = g_string_new ("");
+ metadata->track_number = g_string_new ("");
+ metadata->artist_name = g_string_new ("");
+ metadata->title = g_string_new ("");
+
+ selection_metadata = g_list_append (selection_metadata, metadata);
+
+ g_free (file_name);
+ }
+
+ g_string_append (query, ")} ORDER BY ASC(nie:contentCreated(?file))");
+
+ connection = tracker_sparql_connection_get (NULL, &error);
+ if (!connection) {
+ g_error_free (error);
+
+ return;
+ }
+
+ hash_table = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ (GDestroyNotify) g_free,
+ NULL);
+
+ data = g_new (QueryData, 1);
+ data->hash_table = hash_table;
+ data->dialog = dialog;
+ data->selection_metadata = selection_metadata;
+
+ data->have_season = TRUE;
+ data->have_creation_date = TRUE;
+ data->have_artist_name = TRUE;
+ data->have_track_number = TRUE;
+ data->have_equipment = TRUE;
+ data->have_episode_number = TRUE;
+ data->have_title = TRUE;
+
+ /* Make an asynchronous query to the store */
+ tracker_sparql_connection_query_async (connection,
+ query->str,
+ NULL,
+ batch_rename_dialog_query_callback,
+ data);
+
+ g_object_unref (connection);
+ g_string_free (query, TRUE);
+}
+
+GList*
+distinct_file_parents (GList *selection)
+{
+ GList *result;
+ GList *l1;
+ GList *l2;
+ NautilusFile *file;
+ gboolean exists;
+ gchar *parent_uri;
+
+ result = NULL;
+
+ for (l1 = selection; l1 != NULL; l1 = l1->next) {
+ exists = FALSE;
+
+ file = NAUTILUS_FILE (l1->data);
+ parent_uri = nautilus_file_get_parent_uri (file);
+
+ for (l2 = result; l2 != NULL; l2 = l2->next)
+ if (g_strcmp0 (parent_uri, l2->data) == 0) {
+ exists = TRUE;
+ break;
+ }
+
+ if (!exists) {
+ result = g_list_prepend (result, parent_uri);
+ } else {
+ g_free (parent_uri);
+ }
+ }
+
+ return result;
+}
\ No newline at end of file
diff --git a/src/nautilus-batch-rename-utilities.h b/src/nautilus-batch-rename-utilities.h
new file mode 100644
index 0000000..5520890
--- /dev/null
+++ b/src/nautilus-batch-rename-utilities.h
@@ -0,0 +1,63 @@
+#ifndef NAUTILUS_BATCH_RENAME_UTILITIES_H
+#define NAUTILUS_BATCH_RENAME_UTILITIES_H
+
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+#include <tracker-sparql.h>
+
+GList* batch_rename_dialog_get_new_names_list (NautilusBatchRenameDialogMode mode,
+ GList *selection,
+ GList *tags_list,
+ GList *selection_metadata,
+ gchar *entry_text,
+ gchar *replace_text);
+
+GList* file_names_list_has_duplicates (NautilusBatchRenameDialog *dialog,
+ NautilusDirectory *model,
+ GList *names,
+ GList *selection,
+ GList *parents_list,
+ GCancellable *cancellable);
+
+GList* nautilus_batch_rename_dialog_sort (GList *selection,
+ SortingMode mode,
+ GHashTable *creation_date_table);
+
+gint compare_files_by_last_modified (gconstpointer a,
+ gconstpointer b);
+
+gint compare_files_by_first_modified (gconstpointer a,
+ gconstpointer b);
+
+gint compare_files_by_name_descending (gconstpointer a,
+ gconstpointer b);
+
+gint compare_files_by_name_ascending (gconstpointer a,
+ gconstpointer b);
+
+gint compare_files_by_first_created (gconstpointer a,
+ gconstpointer b);
+
+gint compare_files_by_last_created (gconstpointer a,
+ gconstpointer b);
+
+void check_metadata_for_selection (NautilusBatchRenameDialog *dialog,
+ GList *selection);
+
+gboolean selection_has_single_parent (GList *selection);
+
+void string_free (gpointer mem);
+
+void conflict_data_free (gpointer mem);
+
+GList* distinct_file_parents (GList *selection);
+
+gboolean file_name_conflicts_with_results (GList *selection,
+ GList *new_names,
+ GString *old_name,
+ gchar *parent_uri);
+
+GString* batch_rename_dialog_replace_label_text (gchar *string,
+ const gchar *substr);
+
+#endif /* NAUTILUS_BATCH_RENAME_UTILITIES_H */
\ No newline at end of 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 e880547..7edefcb 100644
--- a/src/nautilus-file-undo-operations.c
+++ b/src/nautilus-file-undo-operations.c
@@ -994,6 +994,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 9402469..630443f 100644
--- a/src/nautilus-file-undo-operations.h
+++ b/src/nautilus-file-undo-operations.h
@@ -34,6 +34,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,
@@ -188,6 +189,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-utilities.c b/src/nautilus-file-utilities.c
index 262f485..6ed4127 100644
--- a/src/nautilus-file-utilities.c
+++ b/src/nautilus-file-utilities.c
@@ -1221,3 +1221,19 @@ nautilus_ensure_extension_points (void)
}
#endif /* !NAUTILUS_OMIT_SELF_CHECK */
+
+gboolean
+nautilus_file_can_rename_files (GList *files)
+{
+ GList *l;
+ NautilusFile *file;
+
+ for (l = files; l != NULL; l = l->next) {
+ file = NAUTILUS_FILE (l->data);
+
+ if (!nautilus_file_can_rename (file))
+ return FALSE;
+ }
+
+ return TRUE;
+}
diff --git a/src/nautilus-file-utilities.h b/src/nautilus-file-utilities.h
index 4b26a8c..c4d863f 100644
--- a/src/nautilus-file-utilities.h
+++ b/src/nautilus-file-utilities.h
@@ -119,4 +119,6 @@ char * nautilus_get_common_filename_prefix_from_filenames (GList *filename_list,
void nautilus_ensure_extension_points (void);
void nautilus_ensure_extension_builtins (void);
+gboolean nautilus_file_can_rename_files (GList *files);
+
#endif /* NAUTILUS_FILE_UTILITIES_H */
diff --git a/src/nautilus-file.c b/src/nautilus-file.c
index 60e2f9b..d01a0e5 100644
--- a/src/nautilus-file.c
+++ b/src/nautilus-file.c
@@ -1648,15 +1648,36 @@ 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);
+
+
+ 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 (op->files == NULL)
+ 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);
@@ -1680,7 +1701,10 @@ nautilus_file_operation_complete (NautilusFileOperation *op,
* as "changing back".
*/
nautilus_file_operation_remove (op);
- nautilus_file_changed (op->file);
+
+ if (op->files == NULL)
+ nautilus_file_changed (op->file);
+
if (op->callback) {
(* op->callback) (op->file, result_file, error, op->callback_data);
}
@@ -1759,6 +1783,86 @@ rename_get_info_callback (GObject *source_object,
}
}
+typedef struct {
+ NautilusFileOperation *op;
+ NautilusFile *file;
+} BatchRenameData;
+
+static void
+batch_rename_get_info_callback (GObject *source_object,
+ GAsyncResult *res,
+ gpointer callback_data)
+{
+ NautilusFileOperation *op;
+ NautilusDirectory *directory;
+ NautilusFile *existing_file;
+ char *old_uri;
+ char *new_uri;
+ const char *new_name;
+ GFileInfo *new_info;
+ GError *error;
+ BatchRenameData *data;
+
+ data = callback_data;
+
+ op = data->op;
+ op->file = data->file;
+
+ error = NULL;
+ new_info = g_file_query_info_finish (G_FILE (source_object), res, &error);
+ if (new_info != NULL) {
+ old_uri = nautilus_file_get_uri (op->file);
+
+ new_name = g_file_info_get_name (new_info);
+
+ directory = op->file->details->directory;
+
+ /* If there was another file by the same name in this
+ * directory and it is not the same file that we are
+ * renaming, mark it gone.
+ */
+ existing_file = nautilus_directory_find_file_by_name (directory, new_name);
+ if (existing_file != NULL && existing_file != op->file) {
+ nautilus_file_mark_gone (existing_file);
+ nautilus_file_changed (existing_file);
+ }
+
+ update_info_and_name (op->file, new_info);
+
+ new_uri = nautilus_file_get_uri (op->file);
+ nautilus_directory_moved (old_uri, new_uri);
+ g_free (new_uri);
+ g_free (old_uri);
+
+ /* the rename could have affected the display name if e.g.
+ * we're in a vfolder where the name comes from a desktop file
+ * and a rename affects the contents of the desktop file.
+ */
+ if (op->file->details->got_custom_display_name) {
+ nautilus_file_invalidate_attributes (op->file,
+ NAUTILUS_FILE_ATTRIBUTE_INFO |
+ NAUTILUS_FILE_ATTRIBUTE_LINK_INFO);
+ }
+
+ g_object_unref (new_info);
+ }
+
+ op->renamed_files++;
+
+ if (op->renamed_files + op->skipped_files == g_list_length (op->files)) {
+ nautilus_file_operation_complete (op, NULL, error);
+ }
+
+ if (op->files == NULL)
+ nautilus_file_operation_complete (op, NULL, error);
+
+ g_free (data);
+
+ if (error) {
+ g_error_free (error);
+ }
+}
+
static void
rename_callback (GObject *source_object,
GAsyncResult *res,
@@ -1779,7 +1883,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,
@@ -1812,6 +1915,216 @@ nautilus_file_rename (NautilusFile *file,
callback_data);
}
+static gchar*
+nautilus_file_can_rename_file (NautilusFile *file,
+ const char *new_name,
+ NautilusFileOperationCallback callback,
+ gpointer callback_data)
+{
+ GError *error;
+ gboolean is_renameable_desktop_file;
+ gboolean success;
+ gboolean name_changed;
+ gchar *new_file_name;
+ gchar *uri;
+ gchar *old_name;
+
+ is_renameable_desktop_file =
+ is_desktop_file (file) && can_rename_desktop_file (file);
+
+ /* Return an error for incoming names containing path separators.
+ * But not for .desktop files as '/' are allowed for them */
+ if (strstr (new_name, "/") != NULL && !is_renameable_desktop_file) {
+ error = g_error_new (G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
+ _("Slashes are not allowed in filenames"));
+ if (callback != NULL)
+ (* callback) (file, NULL, error, callback_data);
+ g_error_free (error);
+ return NULL;
+ }
+
+ /* 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_rename_handle_file_gone (file, callback, callback_data)) {
+ return NULL;
+ }
+
+ /* Test the name-hasn't-changed case explicitly, for two reasons.
+ * (1) rename returns an error if new & old are same.
+ * (2) We don't want to send file-changed signal if nothing changed.
+ */
+ if (!is_renameable_desktop_file &&
+ name_is (file, new_name)) {
+ if (callback != NULL)
+ (* callback) (file, NULL, NULL, callback_data);
+ return NULL;
+ }
+
+ /* Self-owned files can't be renamed. Test the name-not-actually-changing
+ * case before this case.
+ */
+ if (nautilus_file_is_self_owned (file)) {
+ /* 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".
+ */
+ nautilus_file_changed (file);
+ error = g_error_new (G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
+ _("Toplevel files cannot be renamed"));
+
+ if (callback != NULL)
+ (* callback) (file, NULL, error, callback_data);
+ g_error_free (error);
+
+ return NULL;
+ }
+
+ 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, old_name) == 0) {
+ success = TRUE;
+ name_changed = FALSE;
+ } else {
+ success = nautilus_link_local_set_text (uri, new_name);
+ name_changed = TRUE;
+ }
+ g_free (old_name);
+ g_free (uri);
+
+ if (!success) {
+ error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
+ _("Probably the content of the file is an invalid desktop file
format"));
+ if (callback != NULL)
+ (* callback) (file, NULL, error, callback_data);
+ g_error_free (error);
+ return NULL;
+ }
+ new_file_name = g_strdup_printf ("%s.desktop", new_name);
+ new_file_name = g_strdelimit (new_file_name, "/", '-');
+
+ if (name_is (file, new_file_name)) {
+ if (name_changed) {
+ nautilus_file_invalidate_attributes (file,
+ NAUTILUS_FILE_ATTRIBUTE_INFO |
+ NAUTILUS_FILE_ATTRIBUTE_LINK_INFO);
+ }
+
+ if (callback != NULL)
+ (* callback) (file, NULL, NULL, callback_data);
+ g_free (new_file_name);
+ return NULL;
+ }
+ } else {
+ new_file_name = g_strdup (new_name);
+ }
+
+ return new_file_name;
+}
+
+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;
+ gchar *new_file_name;
+ GString *new_name;
+ NautilusFile *file;
+ GError *error;
+ GFile *new_file;
+ BatchRenameData *data;
+
+ 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);
+
+ new_file_name = nautilus_file_can_rename_file (file,
+ new_name->str,
+ callback,
+ callback_data);
+
+ if (new_file_name == NULL) {
+ op->skipped_files++;
+
+ new_file = nautilus_file_get_location (file);
+ new_files = g_list_append (new_files, new_file);
+
+ continue;
+ }
+
+ g_assert (G_IS_FILE (location));
+
+ /* Do the renaming. */
+ new_file = g_file_set_display_name (location,
+ new_file_name,
+ op->cancellable,
+ &error);
+
+ data = g_new0 (BatchRenameData, 1);
+ data->op = op;
+ data->file = file;
+
+ new_files = g_list_append (new_files, new_file);
+
+ g_file_query_info_async (new_file,
+ NAUTILUS_FILE_DEFAULT_ATTRIBUTES,
+ 0,
+ G_PRIORITY_DEFAULT,
+ op->cancellable,
+ batch_rename_get_info_callback,
+ data);
+
+ 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,
@@ -1820,7 +2133,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".
@@ -1836,6 +2149,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,
@@ -1843,107 +2168,21 @@ real_rename (NautilusFile *file,
gpointer callback_data)
{
NautilusFileOperation *op;
- char *uri;
char *old_name;
char *new_file_name;
- gboolean success, name_changed;
- gboolean is_renameable_desktop_file;
GFile *location;
- GError *error;
g_return_if_fail (NAUTILUS_IS_FILE (file));
g_return_if_fail (new_name != NULL);
g_return_if_fail (callback != NULL);
- is_renameable_desktop_file =
- is_desktop_file (file) && can_rename_desktop_file (file);
-
- /* Return an error for incoming names containing path separators.
- * But not for .desktop files as '/' are allowed for them */
- if (strstr (new_name, "/") != NULL && !is_renameable_desktop_file) {
- error = g_error_new (G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT,
- _("Slashes are not allowed in filenames"));
- (* callback) (file, NULL, error, callback_data);
- g_error_free (error);
- return;
- }
-
- /* 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_rename_handle_file_gone (file, callback, callback_data)) {
- return;
- }
- /* Test the name-hasn't-changed case explicitly, for two reasons.
- * (1) rename returns an error if new & old are same.
- * (2) We don't want to send file-changed signal if nothing changed.
- */
- if (!is_renameable_desktop_file &&
- name_is (file, new_name)) {
- (* callback) (file, NULL, NULL, callback_data);
- return;
- }
+ new_file_name = nautilus_file_can_rename_file (file,
+ new_name,
+ callback,
+ callback_data);
- /* Self-owned files can't be renamed. Test the name-not-actually-changing
- * case before this case.
- */
- if (nautilus_file_is_self_owned (file)) {
- /* 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".
- */
- nautilus_file_changed (file);
- error = g_error_new (G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
- _("Toplevel files cannot be renamed"));
-
- (* callback) (file, NULL, error, callback_data);
- g_error_free (error);
+ if (new_file_name == NULL)
return;
- }
-
- 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, old_name) == 0) {
- success = TRUE;
- name_changed = FALSE;
- } else {
- success = nautilus_link_local_set_text (uri, new_name);
- name_changed = TRUE;
- }
- g_free (old_name);
- g_free (uri);
-
- if (!success) {
- error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
- _("Probably the content of the file is an invalid desktop file
format"));
- (* callback) (file, NULL, error, callback_data);
- g_error_free (error);
- return;
- }
- new_file_name = g_strdup_printf ("%s.desktop", new_name);
- new_file_name = g_strdelimit (new_file_name, "/", '-');
-
- if (name_is (file, new_file_name)) {
- if (name_changed) {
- nautilus_file_invalidate_attributes (file,
- NAUTILUS_FILE_ATTRIBUTE_INFO |
- NAUTILUS_FILE_ATTRIBUTE_LINK_INFO);
- }
-
- (* callback) (file, NULL, NULL, callback_data);
- g_free (new_file_name);
- return;
- }
- } else {
- new_file_name = g_strdup (new_name);
- }
/* Set up a renaming operation. */
op = nautilus_file_operation_new (file, callback, callback_data);
diff --git a/src/nautilus-file.h b/src/nautilus-file.h
index fe226c0..7552dff 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 cd7aa27..b1acfe8 100644
--- a/src/nautilus-files-view.c
+++ b/src/nautilus-files-view.c
@@ -28,6 +28,8 @@
#include "nautilus-files-view.h"
#include "nautilus-application.h"
+#include "nautilus-batch-rename-dialog.h"
+#include "nautilus-batch-rename-utilities.h"
#include "nautilus-error-reporting.h"
#include "nautilus-file-undo-manager.h"
#include "nautilus-floating-bar.h"
@@ -5566,6 +5568,7 @@ real_action_rename (NautilusFilesView *view)
{
NautilusFile *file;
GList *selection;
+ GtkWidget *dialog;
g_assert (NAUTILUS_IS_FILES_VIEW (view));
@@ -5576,6 +5579,21 @@ real_action_rename (NautilusFilesView *view)
if (selection->next != NULL) {
if (have_bulk_rename_tool ()) {
invoke_external_bulk_rename_utility (view, selection);
+ } else {
+ GdkCursor *cursor;
+ GdkDisplay *display;
+
+ display = gtk_widget_get_display (GTK_WIDGET (nautilus_files_view_get_window
(view)));
+ cursor = gdk_cursor_new_from_name (display, "progress");
+ gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET
(nautilus_files_view_get_window (view))),
+ cursor);
+ g_object_unref (cursor);
+
+ dialog = nautilus_batch_rename_dialog_new (nautilus_files_view_get_selection
(NAUTILUS_VIEW (view)),
+ nautilus_files_view_get_model
(view),
+ nautilus_files_view_get_window
(view));
+
+ gtk_widget_show (GTK_WIDGET (dialog));
}
} else {
file = NAUTILUS_FILE (selection->data);
@@ -6625,8 +6643,12 @@ real_update_actions_state (NautilusFilesView *view)
action = g_action_map_lookup_action (G_ACTION_MAP (view_action_group),
"rename");
if (selection_count > 1) {
- g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
- have_bulk_rename_tool ());
+ if (have_bulk_rename_tool())
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
+ have_bulk_rename_tool ());
+ else
+ g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
+ nautilus_file_can_rename_files (selection));
} else {
g_simple_action_set_enabled (G_SIMPLE_ACTION (action),
selection_count == 1 &&
diff --git a/src/resources/css/Adwaita.css b/src/resources/css/Adwaita.css
index bc8e596..3dbe855 100644
--- a/src/resources/css/Adwaita.css
+++ b/src/resources/css/Adwaita.css
@@ -169,3 +169,18 @@
* always allocates at least 1 pixel */
searchbar { border-top: 1px solid @borders; }
.searchbar-container { margin-top: -1px; }
+
+@define-color conflict_bg #fef6b6;
+
+.conflict-row {
+ background: @conflict_bg;
+}
+
+.conflict-row:hover {
+ background-color: shade(@conflict_bg, 0.9);
+}
+
+.conflict-row:selected {
+ background: @theme_selected_bg_color;
+ color: @theme_selected_fg_color;
+}
\ No newline at end of file
diff --git a/src/resources/nautilus.gresource.xml b/src/resources/nautilus.gresource.xml
index 05b9a4d..53682b6 100644
--- a/src/resources/nautilus.gresource.xml
+++ b/src/resources/nautilus.gresource.xml
@@ -18,6 +18,7 @@
<file>ui/nautilus-no-search-results.ui</file>
<file>ui/nautilus-folder-is-empty.ui</file>
<file>gtk/help-overlay.ui</file>
+ <file>ui/nautilus-batch-rename-dialog.ui</file>
<file alias="gtk/ui/nautilusgtkplacesview.ui">../gtk/nautilusgtkplacesview.ui</file>
<file alias="gtk/ui/nautilusgtkplacesviewrow.ui">../gtk/nautilusgtkplacesviewrow.ui</file>
<file alias="icons/thumbnail_frame.png">../../icons/thumbnail_frame.png</file>
diff --git a/src/resources/ui/nautilus-batch-rename-dialog.ui
b/src/resources/ui/nautilus-batch-rename-dialog.ui
new file mode 100644
index 0000000..bacce60
--- /dev/null
+++ b/src/resources/ui/nautilus-batch-rename-dialog.ui
@@ -0,0 +1,495 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <template class="NautilusBatchRenameDialog" parent="GtkDialog">
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="window_position">center-on-parent</property>
+ <property name="destroy_with_parent">True</property>
+ <signal name="response" handler="batch_rename_dialog_on_response"/>
+ <child type="action">
+ <object class="GtkButton" id="cancel_button">
+ <property name="label" translatable="yes">_Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ <child type="action">
+ <object class="GtkButton" id="rename_button">
+ <property name="label" translatable="yes">_Rename</property>
+ <property name="visible">True</property>
+ <property name="use_underline">True</property>
+ <property name="can_default">True</property>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="ok" default="true">rename_button</action-widget>
+ <action-widget response="cancel">cancel_button</action-widget>
+ </action-widgets>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="vbox">
+ <child>
+ <object class="GtkGrid" id="grid">
+ <property name="visible">True</property>
+ <property name="margin">0</property>
+ <property name="row-spacing">6</property>
+ <property name="column-spacing">6</property>
+ <property name="hexpand">True</property>
+ <property name="row-homogeneous">False</property>
+ <property name="column-homogeneous">False</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">horizontal</property>
+ <property name="spacing">15</property>
+ <property name="visible">True</property>
+ <property name="halign">center</property>
+ <property name="margin">20</property>
+ <child>
+ <object class="GtkRadioButton" id="format_mode_button">
+ <property name="label" translatable="yes">_Format mode</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <signal name="toggled" handler="batch_rename_dialog_mode_changed" swapped="yes" />
+ </object>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="replace_mode_button">
+ <property name="label" translatable="yes">Find and replace _text</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <property name="group">format_mode_button</property>
+ <signal name="toggled" handler="batch_rename_dialog_mode_changed" swapped="yes" />
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">3</property>
+ <property name="top-attach">0</property>
+ <property name="width">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkStack" id="mode_stack">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="vhomogeneous">False</property>
+ <property name="hhomogeneous">True</property>
+ <property name="transition_type">crossfade</property>
+ <property name="transition_duration">100</property>
+ <child>
+ <object class="GtkGrid" id="format_stack_child">
+ <property name="visible">True</property>
+ <property name="margin-left">40</property>
+ <property name="margin-right">40</property>
+ <property name="margin-top">0</property>
+ <property name="margin-bottom">10</property>
+ <property name="row-spacing">15</property>
+ <property name="column-spacing">6</property>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">horizontal</property>
+ <property name="spacing">0</property>
+ <property name="visible">True</property>
+ <property name="halign">center</property>
+ <child>
+ <object class="GtkEntry" id="name_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="has_focus">True</property>
+ <property name="width_request">400</property>
+ <property name="hexpand">False</property>
+ <property name="activates-default">True</property>
+ <signal name="changed" handler="file_names_widget_entry_on_changed"
swapped="yes" />
+ <signal name="activate" handler="file_names_widget_on_activate" swapped="yes" />
+ <signal name="key-press-event" handler="on_key_press_event" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkToggleButton" id="add_button">
+ <property name="visible">True</property>
+ <signal name="toggled" handler="add_button_clicked" swapped="yes" />
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="orientation">horizontal</property>
+ <property name="spacing">0</property>
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon-name">list-add-symbolic</property>
+ <property name="icon-size">1</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkLabel">
+ <property name="visible">True</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Add</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <style>
+ <class name="linked"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ <property name="width">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="numbering_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Automatic Numbering Order</property>
+ <property name="can_focus">False</property>
+ <property name="height-request">35</property>
+ <property name="sensitive">False</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ <property name="width">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToggleButton" id="numbering_order_button">
+ <property name="visible">True</property>
+ <signal name="toggled" handler="numbering_order_button_clicked" swapped="yes" />
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="orientation">horizontal</property>
+ <property name="spacing">15</property>
+ <child>
+ <object class="GtkLabel" id="numbering_order_label">
+ <property name="visible">True</property>
+ <property name="width-request">180</property>
+ <property name="xalign">0</property>
+ <property name="label" translatable="yes">Original name
(Ascending)</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImage" id="action_icon">
+ <property name="visible">True</property>
+ <property name="icon-name">pan-down-symbolic</property>
+ <property name="icon-size">1</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">4</property>
+ <property name="top-attach">1</property>
+ <property name="width">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="name">format</property>
+ <property name="title" translatable="yes">Format</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="replace_stack_child">
+ <property name="visible">True</property>
+ <property name="margin-left">40</property>
+ <property name="margin-right">40</property>
+ <property name="margin-top">0</property>
+ <property name="margin-bottom">10</property>
+ <property name="row-spacing">16</property>
+ <property name="column-spacing">6</property>
+ <child>
+ <object class="GtkLabel" id="existing_text_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Existing Text</property>
+ <property name="can_focus">False</property>
+ <property name="sensitive">False</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">0</property>
+ <property name="width">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="find_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="width_request">375</property>
+ <property name="hexpand">False</property>
+ <property name="activates-default">True</property>
+ <signal name="changed" handler="file_names_widget_entry_on_changed" swapped="yes" />
+ <signal name="activate" handler="file_names_widget_on_activate" swapped="yes" />
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">0</property>
+ <property name="width">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="replace_label">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Replace With</property>
+ <property name="can_focus">False</property>
+ <property name="sensitive">False</property>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ <property name="width">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="replace_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="width_request">375</property>
+ <signal name="changed" handler="file_names_widget_entry_on_changed" swapped="yes" />
+ <signal name="activate" handler="file_names_widget_on_activate" swapped="yes" />
+ </object>
+ <packing>
+ <property name="left-attach">1</property>
+ <property name="top-attach">1</property>
+ <property name="width">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="name">replace</property>
+ <property name="title" translatable="yes">Replace</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">1</property>
+ <property name="width">8</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolled_window">
+ <property name="height_request">250</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">False</property>
+ <property name="vexpand">False</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkViewport">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkBox" id="a_box">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkListBox" id="original_name_listbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBox" id="arrow_listbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkListBox" id="result_listbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">2</property>
+ <property name="width">8</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="conflict_box">
+ <property name="orientation">horizontal</property>
+ <property name="spacing">6</property>
+ <property name="visible">False</property>
+ <property name="margin-left">6</property>
+ <child>
+ <object class="GtkLabel" id="conflict_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="hexpand">True</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="pack-type">start</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox">
+ <property name="orientation">horizontal</property>
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkButton" id="conflict_down">
+ <property name="visible">True</property>
+ <property name="relief">none</property>
+ <signal name="clicked" handler="select_next_conflict_down" swapped="yes" />
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon-name">go-down-symbolic</property>
+ <property name="icon-size">1</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="conflict_up">
+ <property name="visible">True</property>
+ <property name="relief">GTK_RELIEF_NONE</property>
+ <signal name="clicked" handler="select_next_conflict_up" swapped="yes" />
+ <child>
+ <object class="GtkImage">
+ <property name="visible">True</property>
+ <property name="icon-name">go-up-symbolic</property>
+ <property name="icon-size">1</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="pack-type">end</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left-attach">0</property>
+ <property name="top-attach">3</property>
+ <property name="width">8</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </template>
+ <menu id="add_tag_menu">
+ <section>
+ <attribute name="label" translatable="yes">Automatic Numbers</attribute>
+ <item>
+ <attribute name="label" translatable="yes">1, 2, 3, 4</attribute>
+ <attribute name="action">dialog.add-numbering-tag-zero</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">01, 02, 03, 04</attribute>
+ <attribute name="action">dialog.add-numbering-tag-one</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">001, 002, 003, 004</attribute>
+ <attribute name="action">dialog.add-numbering-tag-two</attribute>
+ </item>
+ </section>
+ <section>
+ <attribute name="label" translatable="yes">Metadata</attribute>
+ <item>
+ <attribute name="label" translatable="yes">Creation Date</attribute>
+ <attribute name="action">dialog.add-creation-date-tag</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Camera Model</attribute>
+ <attribute name="action">dialog.add-equipment-tag</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Season Number</attribute>
+ <attribute name="action">dialog.add-season-tag</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Episode Number</attribute>
+ <attribute name="action">dialog.add-episode-tag</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Track Number</attribute>
+ <attribute name="action">dialog.add-track-number-tag</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Artist Name</attribute>
+ <attribute name="action">dialog.add-artist-name-tag</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Title</attribute>
+ <attribute name="action">dialog.add-title-tag</attribute>
+ <attribute name="hidden-when">action-disabled</attribute>
+ </item>
+ </section>
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Original File Name</attribute>
+ <attribute name="action">dialog.add-original-file-name-tag</attribute>
+ </item>
+ </section>
+ </menu>
+ <object class="GtkPopover" id="add_popover">
+ <property name="position">bottom</property>
+ <property name="relative-to">add_button</property>
+ <signal name="closed" handler="add_popover_closed" swapped="yes" />
+ </object>
+ <object class="GtkImage" id="done_image">
+ <property name="visible">True</property>
+ <property name="icon_name">object-select-symbolic</property>
+ </object>
+ <menu id="numbering_order_menu">
+ <section>
+ <item>
+ <attribute name="label" translatable="yes">Original name (Ascending) </attribute>
+ <attribute name="action">dialog.numbering-order-changed</attribute>
+ <attribute name="target">name-ascending</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Original name (Descending)</attribute>
+ <attribute name="action">dialog.numbering-order-changed</attribute>
+ <attribute name="target">name-descending</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">First Modified</attribute>
+ <attribute name="action">dialog.numbering-order-changed</attribute>
+ <attribute name="target">first-modified</attribute>
+ </item>
+ <item>
+ <attribute name="label" translatable="yes">Last Modified</attribute>
+ <attribute name="action">dialog.numbering-order-changed</attribute>
+ <attribute name="target">last-modified</attribute>
+ </item>
+ </section>
+ </menu>
+ <object class="GtkPopover" id="numbering_order_popover">
+ <property name="position">bottom</property>
+ <property name="relative-to">numbering_order_button</property>
+ <signal name="closed" handler="numbering_order_popover_closed" swapped="yes" />
+ </object>
+</interface>
\ No newline at end of file
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]