diff --git a/src/Makefile.am b/src/Makefile.am index c925f4129..99edf8fa0 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 000000000..eb76a7387 --- /dev/null +++ b/src/nautilus-batch-rename-dialog.c @@ -0,0 +1,3027 @@ +/* nautilus-batch-rename-dialog.c + * + * Copyright (C) 2016 Alexandru Pandelea + * + * 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 . + */ + +#include + +#include "nautilus-batch-rename-dialog.h" +#include "nautilus-file.h" +#include "nautilus-error-reporting.h" +#include "nautilus-batch-rename-utilities.h" + +#include +#include +#include +#include + +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; + + /* the numbers of characters from the name entry */ + gint name_entry_characters; + gboolean tags_deleted; + gint cursor_position; + gboolean use_manual_cursor_position; +}; + +typedef struct +{ + gboolean available; + gboolean set; + gint position; + gint original_position; + gint new_position; + /* if the tag was just added, then we shouldn't update it's position */ + gboolean just_added; +} 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; + TagData *tag_data; + + dialog = NAUTILUS_BATCH_RENAME_DIALOG (user_data); + + g_object_get (dialog->name_entry, "cursor-position", &cursor_position, NULL); + + tag_data = g_hash_table_lookup (dialog->tag_info_table, ORIGINAL_FILE_NAME); + tag_data->set = TRUE; + tag_data->just_added = TRUE; + tag_data->position = cursor_position; + + gtk_editable_insert_text (GTK_EDITABLE (dialog->name_entry), + ORIGINAL_FILE_NAME, + g_utf8_strlen (ORIGINAL_FILE_NAME, -1), + &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 = TRUE; + tag_data->just_added = TRUE; + tag_data->position = cursor_position; + + gtk_editable_insert_text (GTK_EDITABLE (dialog->name_entry), + CREATION_DATE, + g_utf8_strlen (CREATION_DATE, -1), + &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 = TRUE; + tag_data->just_added = TRUE; + tag_data->position = cursor_position; + + gtk_editable_insert_text (GTK_EDITABLE (dialog->name_entry), + CAMERA_MODEL, + g_utf8_strlen (CAMERA_MODEL, -1), + &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 = TRUE; + tag_data->just_added = TRUE; + tag_data->position = cursor_position; + + gtk_editable_insert_text (GTK_EDITABLE (dialog->name_entry), + SEASON_NUMBER, + g_utf8_strlen (SEASON_NUMBER, -1), + &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 = TRUE; + tag_data->just_added = TRUE; + tag_data->position = cursor_position; + + gtk_editable_insert_text (GTK_EDITABLE (dialog->name_entry), + EPISODE_NUMBER, + g_utf8_strlen (EPISODE_NUMBER, -1), + &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 = TRUE; + tag_data->just_added = TRUE; + tag_data->position = cursor_position; + + gtk_editable_insert_text (GTK_EDITABLE (dialog->name_entry), + TRACK_NUMBER, + g_utf8_strlen (TRACK_NUMBER, -1), + &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 = TRUE; + tag_data->just_added = TRUE; + tag_data->position = cursor_position; + + gtk_editable_insert_text (GTK_EDITABLE (dialog->name_entry), + ARTIST_NAME, + g_utf8_strlen (ARTIST_NAME, -1), + &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 = TRUE; + tag_data->just_added = TRUE; + tag_data->position = cursor_position; + + gtk_editable_insert_text (GTK_EDITABLE (dialog->name_entry), + TITLE, + g_utf8_strlen (TITLE, -1), + &cursor_position); + gtk_editable_set_position (GTK_EDITABLE (dialog->name_entry), cursor_position); + disable_action (dialog, "add-title-tag"); + } + + if (g_strrstr (action_name, "album")) { + tag_data = g_hash_table_lookup (dialog->tag_info_table, ALBUM_NAME); + tag_data->available = TRUE; + tag_data->set = TRUE; + tag_data->just_added = TRUE; + tag_data->position = cursor_position; + + gtk_editable_insert_text (GTK_EDITABLE (dialog->name_entry), + ALBUM_NAME, + g_utf8_strlen (ALBUM_NAME, -1), + &cursor_position); + gtk_editable_set_position (GTK_EDITABLE (dialog->name_entry), cursor_position); + disable_action (dialog, "add-album-name-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; + 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, "zero")) { + tag_data = g_hash_table_lookup (dialog->tag_info_table, NUMBERING); + tag_data->set = TRUE; + tag_data->just_added = TRUE; + tag_data->position = cursor_position; + + gtk_editable_insert_text (GTK_EDITABLE (dialog->name_entry), + NUMBERING, + g_utf8_strlen (NUMBERING, -1), + &cursor_position); + gtk_editable_set_position (GTK_EDITABLE (dialog->name_entry), cursor_position); + } + + if (g_strrstr (action_name, "one")) { + tag_data = g_hash_table_lookup (dialog->tag_info_table, NUMBERING0); + tag_data->set = TRUE; + tag_data->just_added = TRUE; + tag_data->position = cursor_position; + + gtk_editable_insert_text (GTK_EDITABLE (dialog->name_entry), + NUMBERING0, + g_utf8_strlen (NUMBERING0, -1), + &cursor_position); + gtk_editable_set_position (GTK_EDITABLE (dialog->name_entry), cursor_position); + } + + if (g_strrstr (action_name, "two")) { + tag_data = g_hash_table_lookup (dialog->tag_info_table, NUMBERING00); + tag_data->set = TRUE; + tag_data->just_added = TRUE; + tag_data->position = cursor_position; + + gtk_editable_insert_text (GTK_EDITABLE (dialog->name_entry), + NUMBERING00, + g_utf8_strlen (NUMBERING00, -1), + &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 }, + { "add-album-name-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); + } +} + +static gint +compare_tag_position (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 *normal_text; + GString *tag; + GArray *tag_positions; + gint tags; + gint i; + gchar *substring; + 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++; + } + + tag_data = g_hash_table_lookup (dialog->tag_info_table, ALBUM_NAME); + if (tag_data->set) { + g_array_append_val (tag_positions, tag_data->position); + tags++; + } + + g_array_sort (tag_positions, compare_tag_position); + + for (i = 0; i < tags; i++) { + tag = g_string_new (""); + + substring = g_utf8_substring (entry_text, tag_end_position, + g_array_index (tag_positions, gint, i)); + normal_text = g_string_new (substring); + g_free (substring); + + if (g_strcmp0 (normal_text->str, "")) + result = g_list_prepend (result, normal_text); + + tag_data = g_hash_table_lookup (dialog->tag_info_table, ORIGINAL_FILE_NAME); + if (tag_data->set && g_array_index (tag_positions, gint, i) == tag_data->position) { + tag_end_position = g_array_index (tag_positions, gint, i) + + g_utf8_strlen (ORIGINAL_FILE_NAME, -1); + tag = g_string_append (tag, ORIGINAL_FILE_NAME); + } + + tag_data = g_hash_table_lookup (dialog->tag_info_table, NUMBERING); + if (tag_data->set && g_array_index (tag_positions, gint, i) == tag_data->position) { + tag_end_position = g_array_index (tag_positions, gint, i) + + g_utf8_strlen (NUMBERING, -1); + tag = g_string_append (tag, NUMBERING); + } + + tag_data = g_hash_table_lookup (dialog->tag_info_table, NUMBERING0); + if (tag_data->set && g_array_index (tag_positions, gint, i) == tag_data->position) { + tag_end_position = g_array_index (tag_positions, gint, i) + + g_utf8_strlen (NUMBERING0, -1); + tag = g_string_append (tag, NUMBERING0); + } + + tag_data = g_hash_table_lookup (dialog->tag_info_table, NUMBERING00); + if (tag_data->set && g_array_index (tag_positions, gint, i) == tag_data->position) { + tag_end_position = g_array_index (tag_positions, gint, i) + + g_utf8_strlen (NUMBERING00, -1); + tag = g_string_append (tag, NUMBERING00); + } + tag_data = g_hash_table_lookup (dialog->tag_info_table, CREATION_DATE); + if (tag_data->set && g_array_index (tag_positions, gint, i) == tag_data->position) { + tag_end_position = g_array_index (tag_positions, gint, i) + + g_utf8_strlen (CREATION_DATE, -1); + tag = g_string_append (tag, CREATION_DATE); + } + tag_data = g_hash_table_lookup (dialog->tag_info_table, CAMERA_MODEL); + if (tag_data->set && g_array_index (tag_positions, gint, i) == tag_data->position) { + tag_end_position = g_array_index (tag_positions, gint, i) + + g_utf8_strlen (CAMERA_MODEL, -1); + tag = g_string_append (tag, CAMERA_MODEL); + } + tag_data = g_hash_table_lookup (dialog->tag_info_table, SEASON_NUMBER); + if (tag_data->set && g_array_index (tag_positions, gint, i) == tag_data->position) { + tag_end_position = g_array_index (tag_positions, gint, i) + + g_utf8_strlen (SEASON_NUMBER, -1); + tag = g_string_append (tag, SEASON_NUMBER); + } + tag_data = g_hash_table_lookup (dialog->tag_info_table, EPISODE_NUMBER); + if (tag_data->set && g_array_index (tag_positions, gint, i) == tag_data->position) { + tag_end_position = g_array_index (tag_positions, gint, i) + + g_utf8_strlen (EPISODE_NUMBER, -1); + tag = g_string_append (tag, EPISODE_NUMBER); + } + tag_data = g_hash_table_lookup (dialog->tag_info_table, TRACK_NUMBER); + if (tag_data->set && g_array_index (tag_positions, gint, i) == tag_data->position) { + tag_end_position = g_array_index (tag_positions, gint, i) + + g_utf8_strlen (TRACK_NUMBER, -1); + tag = g_string_append (tag, TRACK_NUMBER); + } + tag_data = g_hash_table_lookup (dialog->tag_info_table, ARTIST_NAME); + if (tag_data->set && g_array_index (tag_positions, gint, i) == tag_data->position) { + tag_end_position = g_array_index (tag_positions, gint, i) + + g_utf8_strlen (ARTIST_NAME, -1); + tag = g_string_append (tag, ARTIST_NAME); + } + tag_data = g_hash_table_lookup (dialog->tag_info_table, TITLE); + if (tag_data->set && g_array_index (tag_positions, gint, i) == tag_data->position) { + tag_end_position = g_array_index (tag_positions, gint, i) + + g_utf8_strlen (TITLE, -1); + tag = g_string_append (tag, TITLE); + } + tag_data = g_hash_table_lookup (dialog->tag_info_table, ALBUM_NAME); + if (tag_data->set && g_array_index (tag_positions, gint, i) == tag_data->position) { + tag_end_position = g_array_index (tag_positions, gint, i) + + g_utf8_strlen (ALBUM_NAME, -1); + tag = g_string_append (tag, ALBUM_NAME); + } + + result = g_list_prepend (result, tag); + } + normal_text = g_string_new (g_utf8_offset_to_pointer (entry_text, tag_end_position)); + + if (g_strcmp0 (normal_text->str, "") != 0) + result = g_list_prepend (result, normal_text); + + 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 *text_chunks; + g_autofree gchar *entry_text; + g_autofree gchar *replace_text; + + selection = dialog->selection; + text_chunks = 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 { + text_chunks = split_entry_text (dialog, entry_text); + + result = batch_rename_dialog_get_new_names_list (dialog->mode, + selection, + text_chunks, + dialog->selection_metadata, + entry_text, + replace_text); + g_list_free_full (text_chunks, string_free); + } + + result = g_list_reverse (result); + + return result; +} + +static void +begin_batch_rename (NautilusBatchRenameDialog *dialog, + GList *new_names) +{ + GList *new_names_list; + GList *files; + GList *files2; + GList *new_names_list2; + gchar *file_name; + gchar *old_file_name; + GString *new_file_name; + GString *new_name; + NautilusFile *file; + + /* in the following case: + * file1 -> file2 + * file2 -> file3 + * file2 must be renamed first, so because of that, the list has to be reordered */ + for (new_names_list = new_names, files = dialog->selection; + new_names_list != NULL && files != NULL; + new_names_list = new_names_list->next, files = files->next) { + old_file_name = nautilus_file_get_name (NAUTILUS_FILE (files->data)); + new_file_name = new_names_list->data; + + for (files2 = dialog->selection, new_names_list2 = new_names; + files2 != NULL && new_names_list2 != NULL; + files2 = files2->next, new_names_list2 = new_names_list2->next) { + file_name = nautilus_file_get_name (NAUTILUS_FILE (files2->data)); + if (files2 != files && g_strcmp0 (file_name, new_file_name->str) == 0) { + file = NAUTILUS_FILE (files2->data); + new_name = new_names_list2->data; + + dialog->selection = g_list_remove_link (dialog->selection, files2); + new_names = g_list_remove_link (new_names, new_names_list2); + + 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 computational + * complexity of the later.*/ +static void +update_rows_height (NautilusBatchRenameDialog *dialog) +{ + GList *l; + gint current_row_natural_height; + gint maximum_height; + + 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), + NULL, + ¤t_row_natural_height); + + if (current_row_natural_height > maximum_height) { + maximum_height = current_row_natural_height; + } + } + + for (l = dialog->listbox_labels_old; l != NULL; l = l->next) { + gtk_widget_get_preferred_height (GTK_WIDGET (l->data), + NULL, + ¤t_row_natural_height); + + if (current_row_natural_height > maximum_height) { + maximum_height = current_row_natural_height; + } + } + + for (l = dialog->listbox_icons; l != NULL; l = l->next) { + gtk_widget_get_preferred_height (GTK_WIDGET (l->data), + NULL, + ¤t_row_natural_height); + + if (current_row_natural_height > maximum_height) { + maximum_height = current_row_natural_height; + } + } + + if (maximum_height != dialog->row_height) { + dialog->row_height = 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 = gtk_label_new (old_text); + gtk_label_set_xalign (GTK_LABEL (label_old), 0.0); + gtk_widget_set_hexpand (label_old, TRUE); + gtk_widget_set_margin_start (label_old, 6); + + 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 = gtk_label_new (new_text); + gtk_label_set_xalign (GTK_LABEL (label_new), 0.0); + gtk_widget_set_hexpand (label_new, TRUE); + gtk_widget_set_margin_start (label_new, 6); + + 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 = gtk_label_new ("→"); + gtk_label_set_xalign (GTK_LABEL (icon), 1.0); + gtk_widget_set_hexpand (icon, FALSE); + gtk_widget_set_margin_start (icon, 6); + + 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 +prepare_batch_rename (NautilusBatchRenameDialog *dialog) +{ + GdkCursor *cursor; + GdkDisplay *display; + + /* 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; + + 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->new_names); + + if (dialog->conflict_cancellable) + g_cancellable_cancel (dialog->conflict_cancellable); + + gtk_widget_destroy (GTK_WIDGET (dialog)); +} + +static void +batch_rename_dialog_on_response (NautilusBatchRenameDialog *dialog, + gint response_id, + gpointer user_data) +{ + if (response_id == GTK_RESPONSE_OK) { + prepare_batch_rename (dialog); + } else { + 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); + + 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 *conflict_file_name; + GString *display_text; + GString *new_name; + gint nth_conflict_index; + gint nth_conflict; + gint name_occurences; + GtkAdjustment *adjustment; + GtkAllocation allocation; + ConflictData *conflict_data; + + nth_conflict = dialog->selected_conflict; + l = g_list_nth (dialog->duplicates, nth_conflict); + conflict_data = l->data; + + /* the conflict that has to be selected */ + conflict_file_name = g_string_new (conflict_data->name); + display_text = g_string_new (""); + + nth_conflict_index = conflict_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, conflict_file_name)) + name_occurences++; + } + if (name_occurences > 1) + g_string_append_printf (display_text, + _("\"%s\" would not be a unique new name"), + conflict_file_name->str); + else + g_string_append_printf (display_text, + _("\"%s\" would conflict with an existing file."), + conflict_file_name->str); + + gtk_label_set_label (GTK_LABEL (dialog->conflict_label), + display_text->str); + + g_string_free (conflict_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 *duplicates; + gint index; + GtkStyleContext *context; + ConflictData *conflict_data; + + index = 0; + + duplicates = dialog->duplicates; + + 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"); + + } + + if (duplicates != NULL) { + conflict_data = duplicates->data; + if (conflict_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"); + + duplicates = duplicates->next; + } + } + 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_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) { + prepare_batch_rename (dialog); + } + + if (dialog->rename_clicked && dialog->duplicates != NULL) { + dialog->rename_clicked = FALSE; + } +} + + +void +check_conflict_for_files (NautilusBatchRenameDialog *dialog, + NautilusDirectory *directory, + GList *files) +{ + gchar *current_directory; + gchar *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; + gboolean tag_present; + gboolean same_parent_directory; + ConflictData *conflict_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); + + /* names_conflicts_table is used for knowing which names from the list are not unique, + * so that they can easily be reached when needed */ + 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); + + tag_present = g_hash_table_lookup (new_names_table, new_name->str) != NULL; + same_parent_directory = g_strcmp0 (parent_uri, current_directory) == 0; + + if (same_parent_directory) { + if (!tag_present) { + g_hash_table_insert (new_names_table, + g_strdup (new_name->str), + nautilus_file_get_parent_uri (file)); + } else { + 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 (TRUE)); + } + + for (l1 = dialog->selection, l2 = dialog->new_names; l1 != NULL && l2 != NULL; l1 = l1->next, l2 = l2->next) { + file = NAUTILUS_FILE (l1->data); + + name = nautilus_file_get_name (file); + file_name = g_string_new (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 == TRUE && + !file_name_conflicts_with_results (dialog->selection, dialog->new_names, new_name, parent_uri)) { + conflict_data = g_new (ConflictData, 1); + conflict_data->name = g_strdup (new_name->str); + conflict_data->index = g_list_index (dialog->selection, l1->data); + dialog->duplicates = g_list_prepend (dialog->duplicates, + conflict_data); + + have_conflict = TRUE; + } + } + if (!have_conflict) { + tag_present = g_hash_table_lookup (names_conflicts_table, new_name->str) != NULL; + same_parent_directory = g_strcmp0 (nautilus_file_get_parent_uri (file), current_directory) == 0; + + if (tag_present && same_parent_directory) { + conflict_data = g_new (ConflictData, 1); + conflict_data->name = g_strdup (new_name->str); + conflict_data->index = g_list_index (dialog->selection, l1->data); + dialog->duplicates = g_list_prepend (dialog->duplicates, + conflict_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_files (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 *conflict_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 (TRUE)); + + if (!hash_table_insertion) { + g_hash_table_insert (names_conflicts_table, + g_strdup (new_name->str), + GINT_TO_POINTER (TRUE)); + } + } + + 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 (TRUE)); + } + + 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 == TRUE && + !file_name_conflicts_with_results (dialog->selection, new_names, new_name, NULL)) { + conflict_data = g_new (ConflictData, 1); + conflict_data->name = g_strdup (new_name->str); + conflict_data->index = g_list_index (dialog->selection, l2->data); + dialog->duplicates = g_list_prepend (dialog->duplicates, + conflict_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 == TRUE) { + conflict_data = g_new (ConflictData, 1); + conflict_data->name = g_strdup (new_name->str); + conflict_data->index = g_list_index (dialog->selection, l2->data); + dialog->duplicates = g_list_prepend (dialog->duplicates, + conflict_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_run_in_thread (dialog->conflicts_task, file_names_list_has_duplicates_async_thread); + + g_object_unref (dialog->conflicts_task); +} + +static void +update_tags (NautilusBatchRenameDialog *dialog) +{ + TagData *tag_data; + const gchar *entry_text; + gint character_difference; + gint cursor_position; + + entry_text = gtk_entry_get_text (GTK_ENTRY (dialog->name_entry)); + + if (dialog->use_manual_cursor_position) { + gtk_editable_set_position (GTK_EDITABLE (dialog->name_entry), + dialog->cursor_position); + } + + if (dialog->tags_deleted) { + dialog->tags_deleted = FALSE; + gtk_editable_set_position (GTK_EDITABLE (dialog->name_entry), + g_utf8_strlen (entry_text, -1)); + } + + g_object_get (dialog->name_entry, "cursor-position", &cursor_position, NULL); + + character_difference = g_utf8_strlen (entry_text, -1) - dialog->name_entry_characters; + dialog->name_entry_characters = g_utf8_strlen (entry_text, -1); + + tag_data = g_hash_table_lookup (dialog->tag_info_table, ORIGINAL_FILE_NAME); + if (tag_data->just_added) { + tag_data->just_added = FALSE; + } else { + if (tag_data->set && cursor_position <= tag_data->position) { + tag_data->position += character_difference; + } + } + + tag_data = g_hash_table_lookup (dialog->tag_info_table, CREATION_DATE); + if (tag_data->just_added) { + tag_data->just_added = FALSE; + } else { + if (tag_data->set && cursor_position <= tag_data->position) { + tag_data->position += character_difference; + } + } + + tag_data = g_hash_table_lookup (dialog->tag_info_table, CAMERA_MODEL); + if (tag_data->just_added) { + tag_data->just_added = FALSE; + } else { + if (tag_data->set && cursor_position <= tag_data->position) { + tag_data->position += character_difference; + } + } + + tag_data = g_hash_table_lookup (dialog->tag_info_table, SEASON_NUMBER); + if (tag_data->just_added) { + tag_data->just_added = FALSE; + } else { + if (tag_data->set && cursor_position <= tag_data->position) { + tag_data->position += character_difference; + } + } + + tag_data = g_hash_table_lookup (dialog->tag_info_table, EPISODE_NUMBER); + if (tag_data->just_added) { + tag_data->just_added = FALSE; + } else { + if (tag_data->set && cursor_position <= tag_data->position) { + tag_data->position += character_difference; + } + } + + tag_data = g_hash_table_lookup (dialog->tag_info_table, TRACK_NUMBER); + if (tag_data->just_added) { + tag_data->just_added = FALSE; + } else { + if (tag_data->set && cursor_position <= tag_data->position) { + tag_data->position += character_difference; + } + } + + tag_data = g_hash_table_lookup (dialog->tag_info_table, ARTIST_NAME); + if (tag_data->just_added) { + tag_data->just_added = FALSE; + } else { + if (tag_data->set && cursor_position <= tag_data->position) { + tag_data->position += character_difference; + } + } + + tag_data = g_hash_table_lookup (dialog->tag_info_table, TITLE); + if (tag_data->just_added) { + tag_data->just_added = FALSE; + } else { + if (tag_data->set && cursor_position <= tag_data->position) { + tag_data->position += character_difference; + } + } + + tag_data = g_hash_table_lookup (dialog->tag_info_table, ALBUM_NAME); + if (tag_data->just_added) { + tag_data->just_added = FALSE; + } else { + if (tag_data->set && cursor_position <= tag_data->position) { + tag_data->position += character_difference; + } + } + + tag_data = g_hash_table_lookup (dialog->tag_info_table, NUMBERING); + if (tag_data->just_added) { + tag_data->just_added = FALSE; + } else { + if (tag_data->set && cursor_position <= tag_data->position) { + tag_data->position += character_difference; + } + } + + tag_data = g_hash_table_lookup (dialog->tag_info_table, NUMBERING0); + if (tag_data->just_added) { + tag_data->just_added = FALSE; + } else { + if (tag_data->set && cursor_position <= tag_data->position) { + tag_data->position += character_difference; + } + } + + tag_data = g_hash_table_lookup (dialog->tag_info_table, NUMBERING00); + if (tag_data->just_added) { + tag_data->just_added = FALSE; + } else { + if (tag_data->set && cursor_position <= tag_data->position) { + tag_data->position += character_difference; + } + } +} + +static gboolean +have_unallowed_character (NautilusBatchRenameDialog *dialog) +{ + const gchar *entry_text; + gboolean have_unallowed_character_slash; + gboolean have_unallowed_character_dot; + gboolean have_unallowed_character_dotdot; + + have_unallowed_character_slash = FALSE; + have_unallowed_character_dot = FALSE; + have_unallowed_character_dotdot = FALSE; + + + + if (dialog->mode == NAUTILUS_BATCH_RENAME_DIALOG_FORMAT) { + entry_text = gtk_entry_get_text (GTK_ENTRY (dialog->name_entry)); + + } else { + entry_text = gtk_entry_get_text (GTK_ENTRY (dialog->replace_entry)); + } + + if (strstr (entry_text, "/") != NULL) { + have_unallowed_character_slash = TRUE; + } + + if (g_strcmp0 (entry_text, ".") == 0) { + have_unallowed_character_dot = TRUE; + } + + if (g_strcmp0 (entry_text, "..") == 0) { + have_unallowed_character_dotdot = TRUE; + } + + if (have_unallowed_character_slash) { + gtk_label_set_label (GTK_LABEL (dialog->conflict_label), + "\"/\" is an unallowed character"); + } + + if (have_unallowed_character_dot) { + gtk_label_set_label (GTK_LABEL (dialog->conflict_label), + "\".\" is an unallowed file name"); + } + + if (have_unallowed_character_dotdot) { + gtk_label_set_label (GTK_LABEL (dialog->conflict_label), + "\"..\" is an unallowed file name"); + } + + if (have_unallowed_character_slash || have_unallowed_character_dot || have_unallowed_character_dotdot) { + 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 +update_display_text (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; + + update_tags (dialog); + + if (dialog->new_names != NULL) + g_list_free_full (dialog->new_names, string_free); + + 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 +file_names_widget_entry_on_changed (NautilusBatchRenameDialog *dialog) +{ + update_display_text (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; + } + + tag_data = g_hash_table_lookup (dialog->tag_info_table, ALBUM_NAME); + if (metadata->album_name == NULL || g_strcmp0 (metadata->album_name->str, "") == 0) { + disable_action (dialog, "add-album-name-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) +{ + prepare_batch_rename (dialog); +} + +static gboolean +remove_tag (NautilusBatchRenameDialog *dialog, + gchar *tag_name, + gchar *action_name, + gint keyval, + gboolean is_modifier) +{ + TagData *tag_data; + gint cursor_position; + GString *new_entry_text; + GString *entry_text; + gboolean delete_tag; + GAction *action; + + delete_tag = FALSE; + + 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 (!tag_data->set) + return FALSE; + + if (keyval == GDK_KEY_BackSpace) { + if (cursor_position > tag_data->position && + cursor_position <= tag_data->position + g_utf8_strlen (tag_name, -1)) { + delete_tag = TRUE; + } + } + + if (keyval == GDK_KEY_Delete) { + if (cursor_position >= tag_data->position && + cursor_position < tag_data->position + g_utf8_strlen (tag_name, -1)) { + delete_tag = TRUE; + } + } + + if (!is_modifier && + keyval != GDK_KEY_Left && + keyval != GDK_KEY_Right && + keyval != GDK_KEY_Delete && + keyval != GDK_KEY_Return && + keyval != GDK_KEY_Escape && + keyval != GDK_KEY_Tab && + keyval != GDK_KEY_End && + keyval != GDK_KEY_Home) { + if (cursor_position > tag_data->position && + cursor_position < tag_data->position + g_utf8_strlen (tag_name, -1)) { + delete_tag = TRUE; + } + } + + if (delete_tag) { + action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group), + action_name); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); + + 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, + g_utf8_offset_to_pointer (entry_text->str, + tag_data->position + g_utf8_strlen (tag_name, -1))); + + tag_data->set = FALSE; + dialog->cursor_position = tag_data->position; + dialog->use_manual_cursor_position = TRUE; + 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); + dialog->use_manual_cursor_position = FALSE; + + g_string_free (new_entry_text, TRUE); + g_string_free (entry_text, TRUE); + + return TRUE; + } + + return FALSE; +} + +static GString* +remove_tag_selection (NautilusBatchRenameDialog *dialog, + GString *old_entry_text, + gchar *action_name, + gchar *tag_name, + gint start, + gint end) +{ + TagData *tag_data; + GAction *action; + GString *new_entry_text; + + new_entry_text = NULL; + + tag_data = g_hash_table_lookup (dialog->tag_info_table, tag_name); + + if (tag_data->set && tag_data->original_position < end && + tag_data->original_position + g_utf8_strlen (tag_name, -1) > start) { + new_entry_text = g_string_new (""); + new_entry_text = g_string_append_len (new_entry_text, + old_entry_text->str, + tag_data->new_position); + new_entry_text = g_string_append (new_entry_text, + g_utf8_offset_to_pointer (old_entry_text->str, + tag_data->new_position + g_utf8_strlen (tag_name, -1))); + + 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), TRUE); + } + + if (new_entry_text == NULL) + return g_string_new (old_entry_text->str); + return new_entry_text; +} + +static void +update_tag_position (NautilusBatchRenameDialog *dialog, + gchar *tag_name, + GString *new_entry_text) +{ + TagData *tag_data; + + tag_data = g_hash_table_lookup (dialog->tag_info_table, tag_name); + + if (tag_data->set) { + tag_data->original_position = tag_data->position; + tag_data->new_position = g_utf8_pointer_to_offset(new_entry_text->str, + g_strrstr (new_entry_text->str, tag_name)); + } +} + +static gboolean +on_key_press_event (GtkWidget *widget, + GdkEventKey *event, + gpointer user_data) +{ + NautilusBatchRenameDialog *dialog; + gint keyval; + GdkEvent *gdk_event; + GString *old_entry_text; + GString *new_entry_text; + gboolean entry_has_selection; + gint start; + gint end; + gboolean tag_removed; + TagData *tag_data; + gint minimum_tag_position; + GAction *action; + + gdk_event = (GdkEvent *) event; + + dialog = NAUTILUS_BATCH_RENAME_DIALOG (user_data); + + keyval = event->keyval; + + entry_has_selection = (gtk_editable_get_selection_bounds (GTK_EDITABLE (dialog->name_entry), + &start, + &end)); + + if (event->state & GDK_CONTROL_MASK) + return GDK_EVENT_PROPAGATE; + + if (entry_has_selection && + ((keyval == GDK_KEY_Delete || keyval == GDK_KEY_BackSpace) || + (!gdk_event->key.is_modifier && + keyval != GDK_KEY_Left && + keyval != GDK_KEY_Right && + keyval != GDK_KEY_Return && + keyval != GDK_KEY_Escape && + keyval != GDK_KEY_Tab && + keyval != GDK_KEY_End && + keyval != GDK_KEY_Home))) { + old_entry_text = g_string_new (gtk_entry_get_text (GTK_ENTRY (dialog->name_entry))); + + minimum_tag_position = G_MAXINT; + tag_removed = FALSE; + + tag_data = g_hash_table_lookup (dialog->tag_info_table, ORIGINAL_FILE_NAME); + if (tag_data->set) { + update_tag_position (dialog, ORIGINAL_FILE_NAME, old_entry_text); + new_entry_text = remove_tag_selection (dialog, + old_entry_text, + "add-original-file-name-tag", + ORIGINAL_FILE_NAME, + start, + end); + + if (!g_string_equal (new_entry_text, old_entry_text)) { + if (tag_data->position < minimum_tag_position) + minimum_tag_position = tag_data->position; + + tag_removed = TRUE; + } + g_string_free (old_entry_text, TRUE); + old_entry_text = new_entry_text; + } + + tag_data = g_hash_table_lookup (dialog->tag_info_table, CREATION_DATE); + if (tag_data->set) { + update_tag_position (dialog, CREATION_DATE, old_entry_text); + new_entry_text = remove_tag_selection (dialog, + old_entry_text, + "add-creation-date-tag", + CREATION_DATE, + start, + end); + + if (!g_string_equal (new_entry_text, old_entry_text)) { + if (tag_data->position < minimum_tag_position) + minimum_tag_position = tag_data->position; + + tag_removed = TRUE; + } + + g_string_free (old_entry_text, TRUE); + old_entry_text = new_entry_text; + } + + tag_data = g_hash_table_lookup (dialog->tag_info_table, NUMBERING); + if (tag_data->set) { + update_tag_position (dialog, NUMBERING, old_entry_text); + new_entry_text = remove_tag_selection (dialog, + old_entry_text, + "add-numbering-tag-zero", + NUMBERING, + start, + end); + + action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group), + "add-numbering-tag-one"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); + + action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group), + "add-numbering-tag-two"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); + + if (!g_string_equal (new_entry_text, old_entry_text)) { + if (tag_data->position < minimum_tag_position) + minimum_tag_position = tag_data->position; + + tag_removed = TRUE; + } + g_string_free (old_entry_text, TRUE); + old_entry_text = new_entry_text; + } + + tag_data = g_hash_table_lookup (dialog->tag_info_table, NUMBERING0); + if (tag_data->set) { + update_tag_position (dialog, NUMBERING0, old_entry_text); + new_entry_text = remove_tag_selection (dialog, + old_entry_text, + "add-numbering-tag-one", + NUMBERING0, + start, + end); + + action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group), + "add-numbering-tag-zero"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); + + action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group), + "add-numbering-tag-two"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); + + if (!g_string_equal (new_entry_text, old_entry_text)) { + if (tag_data->position < minimum_tag_position) + minimum_tag_position = tag_data->position; + + tag_removed = TRUE; + } + g_string_free (old_entry_text, TRUE); + old_entry_text = new_entry_text; + } + + tag_data = g_hash_table_lookup (dialog->tag_info_table, NUMBERING00); + if (tag_data->set) { + update_tag_position (dialog, NUMBERING00, old_entry_text); + new_entry_text = remove_tag_selection (dialog, + old_entry_text, + "add-numbering-tag-two", + NUMBERING0, + start, + end); + + action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group), + "add-numbering-tag-one"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); + + action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group), + "add-numbering-tag-zero"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); + + if (!g_string_equal (new_entry_text, old_entry_text)) { + if (tag_data->position < minimum_tag_position) + minimum_tag_position = tag_data->position; + + tag_removed = TRUE; + } + g_string_free (old_entry_text, TRUE); + old_entry_text = new_entry_text; + } + + tag_data = g_hash_table_lookup (dialog->tag_info_table, CAMERA_MODEL); + if (tag_data->set) { + update_tag_position (dialog, CAMERA_MODEL, old_entry_text); + new_entry_text = remove_tag_selection (dialog, + old_entry_text, + "add-equipment-tag", + CAMERA_MODEL, + start, + end); + + if (!g_string_equal (new_entry_text, old_entry_text)) { + if (tag_data->position < minimum_tag_position) + minimum_tag_position = tag_data->position; + + tag_removed = TRUE; + } + g_string_free (old_entry_text, TRUE); + old_entry_text = new_entry_text; + } + + tag_data = g_hash_table_lookup (dialog->tag_info_table, TRACK_NUMBER); + if (tag_data->set) { + update_tag_position (dialog, TRACK_NUMBER, old_entry_text); + new_entry_text = remove_tag_selection (dialog, + old_entry_text, + "add-track-number-tag", + TRACK_NUMBER, + start, + end); + + if (!g_string_equal (new_entry_text, old_entry_text)) { + if (tag_data->position < minimum_tag_position) + minimum_tag_position = tag_data->position; + + tag_removed = TRUE; + } + g_string_free (old_entry_text, TRUE); + old_entry_text = new_entry_text; + } + + tag_data = g_hash_table_lookup (dialog->tag_info_table, SEASON_NUMBER); + if (tag_data->set) { + update_tag_position (dialog, SEASON_NUMBER, old_entry_text); + new_entry_text = remove_tag_selection (dialog, + old_entry_text, + "add-season-tag", + SEASON_NUMBER, + start, + end); + + if (!g_string_equal (new_entry_text, old_entry_text)) { + if (tag_data->position < minimum_tag_position) + minimum_tag_position = tag_data->position; + + tag_removed = TRUE; + } + g_string_free (old_entry_text, TRUE); + old_entry_text = new_entry_text; + } + + tag_data = g_hash_table_lookup (dialog->tag_info_table, EPISODE_NUMBER); + if (tag_data->set) { + update_tag_position (dialog, EPISODE_NUMBER, old_entry_text); + new_entry_text = remove_tag_selection (dialog, + old_entry_text, + "add-episode-tag", + EPISODE_NUMBER, + start, + end); + + if (!g_string_equal (new_entry_text, old_entry_text)) { + if (tag_data->position < minimum_tag_position) + minimum_tag_position = tag_data->position; + + tag_removed = TRUE; + } + g_string_free (old_entry_text, TRUE); + old_entry_text = new_entry_text; + } + + tag_data = g_hash_table_lookup (dialog->tag_info_table, ARTIST_NAME); + if (tag_data->set) { + update_tag_position (dialog, ARTIST_NAME, old_entry_text); + new_entry_text = remove_tag_selection (dialog, + old_entry_text, + "add-artist-name-tag", + ARTIST_NAME, + start, + end); + + if (!g_string_equal (new_entry_text, old_entry_text)) { + if (tag_data->position < minimum_tag_position) + minimum_tag_position = tag_data->position; + + tag_removed = TRUE; + } + g_string_free (old_entry_text, TRUE); + old_entry_text = new_entry_text; + } + + tag_data = g_hash_table_lookup (dialog->tag_info_table, TITLE); + if (tag_data->set) { + update_tag_position (dialog, TITLE, old_entry_text); + new_entry_text = remove_tag_selection (dialog, + old_entry_text, + "add-title-tag", + TITLE, + start, + end); + + if (!g_string_equal (new_entry_text, old_entry_text)) { + if (tag_data->position < minimum_tag_position) + minimum_tag_position = tag_data->position; + + tag_removed = TRUE; + } + g_string_free (old_entry_text, TRUE); + old_entry_text = new_entry_text; + } + + tag_data = g_hash_table_lookup (dialog->tag_info_table, ALBUM_NAME); + if (tag_data->set) { + update_tag_position (dialog, ALBUM_NAME, old_entry_text); + new_entry_text = remove_tag_selection (dialog, + old_entry_text, + "add-album-name-tag", + ALBUM_NAME, + start, + end); + + if (!g_string_equal (new_entry_text, old_entry_text)) { + if (tag_data->position < minimum_tag_position) + minimum_tag_position = tag_data->position; + + tag_removed = TRUE; + } + g_string_free (old_entry_text, TRUE); + old_entry_text = new_entry_text; + } + + if (minimum_tag_position != G_MAXINT) { + dialog->use_manual_cursor_position = TRUE; + dialog->cursor_position = minimum_tag_position; + + gtk_entry_set_text (GTK_ENTRY (dialog->name_entry), new_entry_text->str); + gtk_editable_set_position (GTK_EDITABLE (dialog->name_entry), minimum_tag_position); + + dialog->use_manual_cursor_position = FALSE; + + g_string_free (new_entry_text, TRUE); + } + + if ((keyval == GDK_KEY_Delete || keyval == GDK_KEY_BackSpace) && + tag_removed) + return TRUE; + + return GDK_EVENT_PROPAGATE; + } + + tag_removed = FALSE; + + if (remove_tag (dialog, + ORIGINAL_FILE_NAME, + "add-original-file-name-tag", + keyval, + gdk_event->key.is_modifier)) + tag_removed = TRUE; + + if (!tag_removed && remove_tag (dialog, + CREATION_DATE, + "add-creation-date-tag", + keyval, + gdk_event->key.is_modifier)) + tag_removed = TRUE; + + if (!tag_removed && remove_tag (dialog, + NUMBERING, + "add-numbering-tag-zero", + keyval, + gdk_event->key.is_modifier)) { + tag_removed = TRUE; + action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group), + "add-numbering-tag-one"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); + + action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group), + "add-numbering-tag-two"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); + } + + if (!tag_removed && remove_tag (dialog, + NUMBERING0, + "add-numbering-tag-one", + keyval, + gdk_event->key.is_modifier)) { + tag_removed = TRUE; + action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group), + "add-numbering-tag-zero"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); + + action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group), + "add-numbering-tag-two"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); + } + + if (!tag_removed && remove_tag (dialog, + NUMBERING00, + "add-numbering-tag-two", + keyval, + gdk_event->key.is_modifier)) { + tag_removed = TRUE; + action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group), + "add-numbering-tag-one"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); + + action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group), + "add-numbering-tag-zero"); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); + } + + if (!tag_removed && remove_tag (dialog, + CAMERA_MODEL, + "add-equipment-tag", + keyval, + gdk_event->key.is_modifier)) + tag_removed = TRUE; + + if (!tag_removed && remove_tag (dialog, + SEASON_NUMBER, + "add-season-tag", + keyval, + gdk_event->key.is_modifier)) + tag_removed = TRUE; + + if (!tag_removed && remove_tag (dialog, + EPISODE_NUMBER, + "add-episode-tag", + keyval, + gdk_event->key.is_modifier)) + tag_removed = TRUE; + + if (!tag_removed && remove_tag (dialog, + TRACK_NUMBER, + "add-track-number-tag", + keyval, + gdk_event->key.is_modifier)) + tag_removed = TRUE; + + if (!tag_removed && remove_tag (dialog, + ARTIST_NAME, + "add-artist-name-tag", + keyval, + gdk_event->key.is_modifier)) + tag_removed = TRUE; + + if (!tag_removed && remove_tag (dialog, + TITLE, + "add-title-tag", + keyval, + gdk_event->key.is_modifier)) + tag_removed = TRUE; + + if (!tag_removed && remove_tag (dialog, + ALBUM_NAME, + "add-album-name-tag", + keyval, + gdk_event->key.is_modifier)) + tag_removed = TRUE; + + if (tag_removed) { + if (keyval == GDK_KEY_Delete || keyval == GDK_KEY_BackSpace) + return TRUE; + + return GDK_EVENT_PROPAGATE; + } + + return GDK_EVENT_PROPAGATE; +} + +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 (metadata->album_name != NULL) + g_string_free (metadata->album_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; + 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)); + + dialog_title = g_string_new (""); + g_string_append_printf (dialog_title, "Rename %d Files", g_list_length (selection)); + 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 = batch_rename_files_get_distinct_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); + + 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 (ALBUM_NAME), tag_data); + + gtk_entry_set_text (GTK_ENTRY (self->name_entry),ORIGINAL_FILE_NAME); + self->name_entry_characters = g_utf8_strlen (ORIGINAL_FILE_NAME, -1); + + 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 000000000..3f11f4eaa --- /dev/null +++ b/src/nautilus-batch-rename-dialog.h @@ -0,0 +1,100 @@ +/* nautilus-batch-rename-utilities.c + * + * Copyright (C) 2016 Alexandru Pandelea + * + * 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 . + */ + +#ifndef NAUTILUS_BATCH_RENAME_DIALOG_H +#define NAUTILUS_BATCH_RENAME_DIALOG_H + +#include +#include +#include +#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]" +#define ALBUM_NAME "[Album name]" + +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; + GString *album_name; +} 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_files (NautilusBatchRenameDialog *dialog, + NautilusDirectory *directory, + GList *files); + +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 000000000..7fb9383b4 --- /dev/null +++ b/src/nautilus-batch-rename-utilities.c @@ -0,0 +1,1050 @@ +/* nautilus-batch-rename-utilities.c + * + * Copyright (C) 2016 Alexandru Pandelea + * + * 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 . + */ + +#include "nautilus-batch-rename-dialog.h" +#include "nautilus-batch-rename-utilities.h" +#include "nautilus-file.h" + +#include +#include +#include +#include +#include + +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; + gboolean have_album_name; +} QueryData; + +enum { + FILE_NAME_INDEX, + CREATION_DATE_INDEX, + YEAR_INDEX, + MONTH_INDEX, + DAY_INDEX, + HOURS_INDEX, + MINUTES_INDEX, + SECONDS_INDEX, + CAMERA_MODEL_INDEX, + SEASON_INDEX, + EPISODE_NUMBER_INDEX, + TRACK_NUMBER_INDEX, + ARTIST_NAME_INDEX, + TITLE_INDEX, + ALBUM_NAME_INDEX, + +} QueryMetadata; + +static void on_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 *conflict_data = mem; + + g_free (conflict_data->name); + g_free (conflict_data); +} + +static GString* +batch_rename_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; +} + +/* This function changes the background color of the replaced part of the name */ +GString* +batch_rename_replace_label_text (gchar *label, + const gchar *substring) +{ + GString *new_label; + gchar **splitted_string; + gchar *token; + gint i, n_splits; + + new_label = g_string_new (""); + + if (substring == NULL || g_strcmp0 (substring, "") == 0) { + token = g_markup_escape_text (label, g_utf8_strlen (label, -1)); + new_label = g_string_append (new_label, token); + g_free (token); + + return new_label; + } + + splitted_string = g_strsplit (label, substring, -1); + if (splitted_string == NULL) { + token = g_markup_escape_text (label, g_utf8_strlen (label, -1)); + new_label = g_string_append (new_label, token); + g_free (token); + + return new_label; + } + + 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_label = g_string_append (new_label, token); + + g_free (token); + + if (i != n_splits - 1) { + token = g_markup_escape_text (substring, g_utf8_strlen (substring, -1)); + g_string_append_printf (new_label, + "%s", + token); + + g_free (token); + } + } + + g_strfreev (splitted_string); + + return new_label; +} + +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; + + if (g_strcmp0 (metadata, "album_name") == 0 && + file_metadata->album_name != NULL && + file_metadata->album_name->len != 0) + return file_metadata->album_name->str; + } + } + + return NULL; +} + +static GString* +batch_rename_format (NautilusFile *file, + GList *text_chunks, + GList *selection_metadata, + gint count) +{ + GList *l; + GString *tag; + GString *new_name; + gboolean added_tag; + g_autofree gchar *file_name; + g_autofree gchar *extension; + gchar *metadata; + gchar *base_name; + + file_name = nautilus_file_get_display_name (file); + extension = nautilus_file_get_extension (file); + + new_name = g_string_new (""); + + for (l = text_chunks; 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) { + g_string_append_printf (new_name, "%02d", count); + + added_tag = TRUE; + } + + if (!added_tag && g_strcmp0 (tag->str, NUMBERING00) == 0) { + g_string_append_printf (new_name, "%03d", 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) { + new_name = g_string_append (new_name, metadata); + added_tag = TRUE; + } + } + + 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 && g_strcmp0 (tag->str, ALBUM_NAME) == 0) { + metadata = get_metadata (selection_metadata, file_name, "album_name"); + + 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 *text_chunks, + 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_format (file, + text_chunks, + selection_metadata, + count++); + result = g_list_prepend (result, new_name); + } + + if (mode == NAUTILUS_BATCH_RENAME_DIALOG_REPLACE) { + new_name = batch_rename_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); + } + + /* the case this function searched for doesn't exist, so the file + * has a conlfict */ + return FALSE; +} + +static 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); +} + +static 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); +} + +static 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); +} + +static 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); +} + +static 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; +} + +static 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 *query_data, + TrackerSparqlCursor *cursor) +{ + tracker_sparql_cursor_next_async (cursor, + NULL, + on_cursor_callback, + query_data); +} + +static void +on_cursor_callback (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + GHashTable *hash_table; + TrackerSparqlCursor *cursor; + gboolean success; + QueryData *query_data; + GError *error; + GList *l; + FileMetadata *metadata; + FileMetadata *metadata_clear; + GDateTime *datetime; + gchar *date; + const gchar *file_name; + const gchar *creation_date; + const gchar *year; + const gchar *month; + const gchar *day; + const gchar *hours; + const gchar *minutes; + const gchar *seconds; + const gchar *equipment; + const gchar *season_number; + const gchar *episode_number; + const gchar *track_number; + const gchar *artist_name; + const gchar *title; + const gchar *album_name; + + error = NULL; + metadata = NULL; + + cursor = TRACKER_SPARQL_CURSOR (object); + query_data = user_data; + hash_table = query_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 (query_data->dialog, + query_data->hash_table, + query_data->selection_metadata); + + return; + } + + creation_date = tracker_sparql_cursor_get_string (cursor, CREATION_DATE_INDEX, NULL); + + year = tracker_sparql_cursor_get_string (cursor, YEAR_INDEX, NULL); + month = tracker_sparql_cursor_get_string (cursor, MONTH_INDEX, NULL); + day = tracker_sparql_cursor_get_string (cursor, DAY_INDEX, NULL); + hours = tracker_sparql_cursor_get_string (cursor, HOURS_INDEX, NULL); + minutes = tracker_sparql_cursor_get_string (cursor, MINUTES_INDEX, NULL); + seconds = tracker_sparql_cursor_get_string (cursor, SECONDS_INDEX, NULL); + equipment = tracker_sparql_cursor_get_string (cursor, CAMERA_MODEL_INDEX, NULL); + season_number = tracker_sparql_cursor_get_string (cursor, SEASON_INDEX, NULL); + episode_number = tracker_sparql_cursor_get_string (cursor, EPISODE_NUMBER_INDEX, NULL); + track_number = tracker_sparql_cursor_get_string (cursor, TRACK_NUMBER_INDEX, NULL); + artist_name = tracker_sparql_cursor_get_string (cursor, ARTIST_NAME_INDEX, NULL); + title = tracker_sparql_cursor_get_string (cursor, TITLE_INDEX, NULL); + album_name = tracker_sparql_cursor_get_string (cursor, ALBUM_NAME_INDEX, NULL); + + /* creation date used for sorting criteria */ + if (creation_date == NULL) { + if (hash_table != NULL) + g_hash_table_destroy (hash_table); + + query_data->hash_table = NULL; + query_data->have_creation_date = FALSE; + } else { + if (query_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, FILE_NAME_INDEX, NULL); + for (l = query_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 (query_data->have_creation_date) { + if (!creation_date) { + query_data->have_creation_date = FALSE; + + for (l = query_data->selection_metadata; l != NULL; l = l->next) { + metadata_clear = l->data; + + if (metadata_clear->creation_date != NULL) { + g_string_free (metadata_clear->creation_date, TRUE); + metadata_clear->creation_date = NULL; + } + } + } else { + datetime = g_date_time_new_local (atoi (year), + atoi (month), + atoi (day), + atoi (hours), + atoi (minutes), + atoi (seconds)); + + date = g_date_time_format (datetime, "%x"); + + if (strstr (date, "/") != NULL) { + metadata->creation_date = batch_rename_replace (date, "/", "-"); + } else { + metadata->creation_date = g_string_new (date); + } + + g_free (date); + } + } + + /* equipment */ + if (query_data->have_equipment) { + if (equipment == NULL) { + query_data->have_equipment = FALSE; + + for (l = query_data->selection_metadata; l != NULL; l = l->next) { + metadata_clear = l->data; + + if (metadata_clear->equipment != NULL) { + g_string_free (metadata_clear->equipment, TRUE); + metadata_clear->equipment = NULL; + } + } + } else { + metadata->equipment = g_string_new (equipment); + } + } + + /* season number */ + if (query_data->have_season) { + if (season_number == NULL) { + query_data->have_season = FALSE; + + for (l = query_data->selection_metadata; l != NULL; l = l->next) { + metadata_clear = l->data; + + if (metadata_clear->season != NULL) { + g_string_free (metadata_clear->season, TRUE); + metadata_clear->season = NULL; + } + } + } else { + metadata->season = g_string_new (season_number); + } + } + + /* episode number */ + if (query_data->have_episode_number) { + if (episode_number == NULL) { + query_data->have_episode_number = FALSE; + + for (l = query_data->selection_metadata; l != NULL; l = l->next) { + metadata_clear = l->data; + + if (metadata_clear->episode_number != NULL) { + g_string_free (metadata_clear->episode_number, TRUE); + metadata_clear->episode_number = NULL; + } + } + } else { + metadata->episode_number = g_string_new (episode_number); + } + } + + /* track number */ + if (query_data->have_track_number) { + if (track_number == NULL) { + query_data->have_track_number = FALSE; + for (l = query_data->selection_metadata; l != NULL; l = l->next) { + metadata_clear = l->data; + + if (metadata_clear->track_number != NULL) { + g_string_free (metadata_clear->track_number, TRUE); + metadata_clear->track_number = NULL; + } + } + } else { + metadata->track_number = g_string_new (track_number); + } + } + + /* artist name */ + if (query_data->have_artist_name) { + if (artist_name == NULL) { + query_data->have_artist_name = FALSE; + + for (l = query_data->selection_metadata; l != NULL; l = l->next) { + metadata_clear = l->data; + + if (metadata_clear->artist_name != NULL) { + g_string_free (metadata_clear->artist_name, TRUE); + metadata_clear->artist_name = NULL; + } + } + } else { + metadata->artist_name = g_string_new (artist_name); + } + } + + /* title */ + if (query_data->have_title) { + if (title == NULL) { + query_data->have_title = FALSE; + + for (l = query_data->selection_metadata; l != NULL; l = l->next) { + metadata_clear = l->data; + + if (metadata_clear->title != NULL) { + g_string_free (metadata_clear->title, TRUE); + metadata_clear->title = NULL; + } + } + } else { + metadata->title = g_string_new (title); + } + } + + /* album name */ + if (query_data->have_album_name) { + if (album_name == NULL) { + query_data->have_album_name = FALSE; + + for (l = query_data->selection_metadata; l != NULL; l = l->next) { + metadata_clear = l->data; + + if (metadata_clear->album_name != NULL) { + g_string_free (metadata_clear->album_name, TRUE); + metadata_clear->album_name = NULL; + } + } + } else { + metadata->album_name = g_string_new (album_name); + } + } + + /* Get next */ + cursor_next (query_data, cursor); +} + +static void +batch_rename_dialog_query_callback (GObject *object, + GAsyncResult *result, + gpointer user_data) +{ + TrackerSparqlConnection *connection; + TrackerSparqlCursor *cursor; + QueryData *query_data; + GError *error; + + error = NULL; + + connection = TRACKER_SPARQL_CONNECTION (object); + query_data = user_data; + + cursor = tracker_sparql_connection_query_finish (connection, + result, + &error); + + if (error != NULL) { + g_warning ("Error on batch rename query for metadata: %s", error->message); + g_error_free (error); + + nautilus_batch_rename_dialog_query_finished (query_data->dialog, + query_data->hash_table, + query_data->selection_metadata); + } else { + cursor_next (query_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 *query_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) " + "year(nie:contentCreated(?file)) " + "month(nie:contentCreated(?file)) " + "day(nie:contentCreated(?file)) " + "hours(nie:contentCreated(?file)) " + "minutes(nie:contentCreated(?file)) " + "seconds(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) " + "nmm:albumTitle(nmm:musicAlbum(?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 = NULL; + metadata->equipment = NULL; + metadata->season = NULL; + metadata->episode_number = NULL; + metadata->track_number = NULL; + metadata->artist_name = NULL; + metadata->title = NULL; + metadata->album_name = NULL; + + 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); + + query_data = g_new (QueryData, 1); + query_data->hash_table = hash_table; + query_data->dialog = dialog; + query_data->selection_metadata = selection_metadata; + + query_data->have_season = TRUE; + query_data->have_creation_date = TRUE; + query_data->have_artist_name = TRUE; + query_data->have_track_number = TRUE; + query_data->have_equipment = TRUE; + query_data->have_episode_number = TRUE; + query_data->have_title = TRUE; + query_data->have_album_name = TRUE; + + /* Make an asynchronous query to the store */ + tracker_sparql_connection_query_async (connection, + query->str, + NULL, + batch_rename_dialog_query_callback, + query_data); + + g_object_unref (connection); + g_string_free (query, TRUE); +} + +GList* +batch_rename_files_get_distinct_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 000000000..e343b25e3 --- /dev/null +++ b/src/nautilus-batch-rename-utilities.h @@ -0,0 +1,63 @@ +/* nautilus-batch-rename-utilities.c + * + * Copyright (C) 2016 Alexandru Pandelea + * + * 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 . + */ + +#ifndef NAUTILUS_BATCH_RENAME_UTILITIES_H +#define NAUTILUS_BATCH_RENAME_UTILITIES_H + +#include +#include +#include + +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); + +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* batch_rename_files_get_distinct_parents (GList *selection); + +gboolean file_name_conflicts_with_results (GList *selection, + GList *new_names, + GString *old_name, + gchar *parent_uri); + +GString* batch_rename_replace_label_text (gchar *label, + 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 c4986511b..f0ecf7294 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 e88054722..3ec2832f8 100644 --- a/src/nautilus-file-undo-operations.c +++ b/src/nautilus-file-undo-operations.c @@ -994,6 +994,292 @@ 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; + GFile *new_file; + GList *l1; + GList *l2; + GList *l3; + GList *l4; + GList *l5; + GList *l6; + GList *l7; + gchar *file_name; + gchar *old_file_name; + GString *new_file_name; + GString *new_name; + GString *old_name; + + 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); + } + + for (l1 = self->priv->new_display_names, l2 = files; 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 = files, l4 = self->priv->new_display_names, l5 = self->priv->old_display_names, l6 = self->priv->old_files, l7 = self->priv->new_files; + l3 != NULL && l4 != NULL && l5 != NULL && l6 != NULL && l7 != NULL; + l3 = l3->next, l4 = l4->next, l5 = l5->next, l6 = l6->next, l7 = l7->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 = l4->data; + old_name = l5->data; + old_file = l6->data; + new_file = l7->data; + + files = g_list_remove_link (files, l3); + self->priv->new_display_names = g_list_remove_link (self->priv->new_display_names, l4); + self->priv->old_display_names = g_list_remove_link (self->priv->old_display_names, l5); + self->priv->old_files = g_list_remove_link (self->priv->old_files, l6); + self->priv->new_files = g_list_remove_link (self->priv->new_files, l7); + + files = g_list_prepend (files, file); + self->priv->new_display_names = g_list_prepend (self->priv->new_display_names, new_name); + self->priv->old_display_names = g_list_prepend (self->priv->old_display_names, old_name); + self->priv->old_files = g_list_prepend (self->priv->old_files, old_file); + self->priv->new_files = g_list_prepend (self->priv->new_files, new_file); + + g_free (file_name); + + break; + } + + g_free (file_name); + } + + g_free (old_file_name); + } + + 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; + GFile *old_file; + GList *l1; + GList *l2; + GList *l3; + GList *l4; + GList *l5; + GList *l6; + GList *l7; + gchar *file_name; + gchar *old_file_name; + GString *new_file_name; + GString *new_name; + GString *old_name; + + 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); + } + + for (l1 = self->priv->old_display_names, l2 = files; 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 = files, l4 = self->priv->old_display_names, l5 = self->priv->new_display_names, l6 = self->priv->old_files, l7 = self->priv->new_files; + l3 != NULL && l4 != NULL && l5 != NULL && l6 != NULL && l7 != NULL; + l3 = l3->next, l4 = l4->next, l5 = l5->next, l6 = l6->next, l7 = l7->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 = l4->data; + old_name = l5->data; + old_file = l6->data; + new_file = l7->data; + + files = g_list_remove_link (files, l3); + self->priv->old_display_names = g_list_remove_link (self->priv->old_display_names, l4); + self->priv->new_display_names = g_list_remove_link (self->priv->new_display_names, l5); + self->priv->old_files = g_list_remove_link (self->priv->old_files, l6); + self->priv->new_files = g_list_remove_link (self->priv->new_files, l7); + + files = g_list_prepend (files, file); + self->priv->old_display_names = g_list_prepend (self->priv->old_display_names, new_name); + self->priv->new_display_names = g_list_prepend (self->priv->new_display_names, old_name); + self->priv->old_files = g_list_prepend (self->priv->old_files, old_file); + self->priv->new_files = g_list_prepend (self->priv->new_files, new_file); + + g_free (file_name); + + break; + } + + g_free (file_name); + } + + g_free (old_file_name); + } + + 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 940246916..630443f10 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 262f48516..6ed4127a0 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 4b26a8caa..c4d863fec 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 60e2f9b38..c68753e98 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,10 +1701,12 @@ nautilus_file_operation_complete (NautilusFileOperation *op, * as "changing back". */ nautilus_file_operation_remove (op); - nautilus_file_changed (op->file); - if (op->callback) { + + if (op->files == NULL) + nautilus_file_changed (op->file); + + if (op->callback) (* op->callback) (op->file, result_file, error, op->callback_data); - } if (error != NULL) { g_clear_object (&op->undo_info); @@ -1759,6 +1782,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 +1882,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 +1914,217 @@ 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_warning ("Batch rename for file \"%s\" failed", nautilus_file_get_name (file)); + 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); + new_file_name = nautilus_file_can_rename_file (file, + new_name, + callback, + callback_data); + + if (new_file_name == NULL) 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; - } - - /* 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); - 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 fe226c072..7552dff07 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 cd7aa2772..b1acfe845 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 bc8e596e3..48beeeecf 100644 --- a/src/resources/css/Adwaita.css +++ b/src/resources/css/Adwaita.css @@ -169,3 +169,19 @@ * 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; + color: black; +} + +.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 05b9a4db9..53682b612 100644 --- a/src/resources/nautilus.gresource.xml +++ b/src/resources/nautilus.gresource.xml @@ -18,6 +18,7 @@ ui/nautilus-no-search-results.ui ui/nautilus-folder-is-empty.ui gtk/help-overlay.ui + ui/nautilus-batch-rename-dialog.ui ../gtk/nautilusgtkplacesview.ui ../gtk/nautilusgtkplacesviewrow.ui ../../icons/thumbnail_frame.png 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 000000000..6628abf7e --- /dev/null +++ b/src/resources/ui/nautilus-batch-rename-dialog.ui @@ -0,0 +1,502 @@ + + + + +
+ Automatic Numbers + + 1, 2, 3, 4 + dialog.add-numbering-tag-zero + + + 01, 02, 03, 04 + dialog.add-numbering-tag-one + + + 001, 002, 003, 004 + dialog.add-numbering-tag-two + +
+
+ Metadata + + Creation Date + dialog.add-creation-date-tag + action-disabled + + + Camera Model + dialog.add-equipment-tag + action-disabled + + + Season Number + dialog.add-season-tag + action-disabled + + + Episode Number + dialog.add-episode-tag + action-disabled + + + Track Number + dialog.add-track-number-tag + action-disabled + + + Artist Name + dialog.add-artist-name-tag + action-disabled + + + Title + dialog.add-title-tag + action-disabled + + + Album Name + dialog.add-album-name-tag + action-disabled + +
+
+ + Original File Name + dialog.add-original-file-name-tag + +
+
+ + bottom + add_button + + + + True + object-select-symbolic + + +
+ + Original name (Ascending) + dialog.numbering-order-changed + name-ascending + + + Original name (Descending) + dialog.numbering-order-changed + name-descending + + + First Modified + dialog.numbering-order-changed + first-modified + + + Last Modified + dialog.numbering-order-changed + last-modified + +
+
+ + bottom + numbering_order_button + + +
\ No newline at end of file diff --git a/src/resources/ui/nautilus-files-view-context-menus.ui b/src/resources/ui/nautilus-files-view-context-menus.ui index 31f26fbbb..f69e6737f 100644 --- a/src/resources/ui/nautilus-files-view-context-menus.ui +++ b/src/resources/ui/nautilus-files-view-context-menus.ui @@ -215,7 +215,7 @@
- Rena_me + Rena_me… view.rename