diff options
Diffstat (limited to '')
-rw-r--r-- | src/nautilus-batch-rename-dialog.c | 2040 |
1 files changed, 2040 insertions, 0 deletions
diff --git a/src/nautilus-batch-rename-dialog.c b/src/nautilus-batch-rename-dialog.c new file mode 100644 index 0000000..14f142b --- /dev/null +++ b/src/nautilus-batch-rename-dialog.c @@ -0,0 +1,2040 @@ +/* nautilus-batch-rename-dialog.c + * + * Copyright (C) 2016 Alexandru Pandelea <alexandru.pandelea@gmail.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include "nautilus-batch-rename-dialog.h" +#include "nautilus-file.h" +#include "nautilus-error-reporting.h" +#include "nautilus-batch-rename-utilities.h" + +#include <glib/gprintf.h> +#include <glib.h> +#include <string.h> +#include <glib/gi18n.h> + +#define ROW_MARGIN_START 6 +#define ROW_MARGIN_TOP_BOTTOM 4 + +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 *numbering_order_button; + GtkWidget *numbering_label; + GtkWidget *scrolled_window; + GtkWidget *numbering_revealer; + GtkWidget *conflict_box; + GtkWidget *conflict_label; + GtkWidget *conflict_down; + GtkWidget *conflict_up; + + 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; + + GHashTable *create_date; + GList *selection_metadata; + + /* the index of the currently selected conflict */ + gint selected_conflict; + /* total conflicts number */ + gint conflicts_number; + + GList *duplicates; + GList *distinct_parent_directories; + GList *directories_pending_conflict_check; + + /* 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; + + GCancellable *metadata_cancellable; +}; + +typedef struct +{ + gboolean available; + gboolean set; + gint position; + /* if the tag was just added, then we shouldn't update it's position */ + gboolean just_added; + TagConstants tag_constants; +} TagData; + + +static void update_display_text (NautilusBatchRenameDialog *dialog); +static void cancel_conflict_check (NautilusBatchRenameDialog *self); + +G_DEFINE_TYPE (NautilusBatchRenameDialog, nautilus_batch_rename_dialog, GTK_TYPE_DIALOG); + +static void +change_numbering_order (GSimpleAction *action, + GVariant *value, + gpointer user_data) +{ + NautilusBatchRenameDialog *dialog; + const gchar *target_name; + guint i; + + dialog = NAUTILUS_BATCH_RENAME_DIALOG (user_data); + + target_name = g_variant_get_string (value, NULL); + + for (i = 0; i < G_N_ELEMENTS (sorts_constants); i++) + { + if (g_strcmp0 (sorts_constants[i].action_target_name, target_name) == 0) + { + gtk_menu_button_set_label (GTK_MENU_BUTTON (dialog->numbering_order_button), + gettext (sorts_constants[i].label)); + dialog->selection = nautilus_batch_rename_dialog_sort (dialog->selection, + sorts_constants[i].sort_mode, + dialog->create_date); + break; + } + } + + g_simple_action_set_state (action, value); + + update_display_text (dialog); +} + +static void +enable_action (NautilusBatchRenameDialog *self, + const gchar *action_name) +{ + GAction *action; + + action = g_action_map_lookup_action (G_ACTION_MAP (self->action_group), + action_name); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); +} + +static void +disable_action (NautilusBatchRenameDialog *self, + const gchar *action_name) +{ + GAction *action; + + action = g_action_map_lookup_action (G_ACTION_MAP (self->action_group), + action_name); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE); +} + +static void +add_tag (NautilusBatchRenameDialog *self, + TagConstants tag_constants) +{ + g_autofree gchar *tag_text_representation = NULL; + gint cursor_position; + TagData *tag_data; + + g_object_get (self->name_entry, "cursor-position", &cursor_position, NULL); + + tag_text_representation = batch_rename_get_tag_text_representation (tag_constants); + tag_data = g_hash_table_lookup (self->tag_info_table, tag_text_representation); + tag_data->available = TRUE; + tag_data->set = TRUE; + tag_data->just_added = TRUE; + tag_data->position = cursor_position; + + /* FIXME: We can add a tag when the cursor is inside a tag, which breaks this. + * We need to check the cursor movement and update the actions acordingly or + * even better add the tag at the end of the previous tag if this happens. + */ + gtk_editable_insert_text (GTK_EDITABLE (self->name_entry), + tag_text_representation, + strlen (tag_text_representation), + &cursor_position); + tag_data->just_added = FALSE; + gtk_editable_set_position (GTK_EDITABLE (self->name_entry), cursor_position); + + gtk_entry_grab_focus_without_selecting (GTK_ENTRY (self->name_entry)); +} + +static void +add_metadata_tag (GSimpleAction *action, + GVariant *value, + gpointer user_data) +{ + NautilusBatchRenameDialog *self; + const gchar *action_name; + guint i; + + self = NAUTILUS_BATCH_RENAME_DIALOG (user_data); + action_name = g_action_get_name (G_ACTION (action)); + + for (i = 0; i < G_N_ELEMENTS (metadata_tags_constants); i++) + { + if (g_strcmp0 (metadata_tags_constants[i].action_name, action_name) == 0) + { + add_tag (self, metadata_tags_constants[i]); + disable_action (self, metadata_tags_constants[i].action_name); + + break; + } + } +} + +static void +add_numbering_tag (GSimpleAction *action, + GVariant *value, + gpointer user_data) +{ + NautilusBatchRenameDialog *self; + const gchar *action_name; + guint i; + + self = NAUTILUS_BATCH_RENAME_DIALOG (user_data); + action_name = g_action_get_name (G_ACTION (action)); + + for (i = 0; i < G_N_ELEMENTS (numbering_tags_constants); i++) + { + if (g_strcmp0 (numbering_tags_constants[i].action_name, action_name) == 0) + { + add_tag (self, numbering_tags_constants[i]); + } + /* We want to allow only one tag of numbering type, so we disable all + * of them */ + disable_action (self, numbering_tags_constants[i].action_name); + } +} + +const GActionEntry dialog_entries[] = +{ + { "numbering-order-changed", NULL, "s", "'name-ascending'", change_numbering_order }, + { "add-numbering-no-zero-pad-tag", add_numbering_tag }, + { "add-numbering-one-zero-pad-tag", add_numbering_tag }, + { "add-numbering-two-zero-pad-tag", add_numbering_tag }, + { "add-original-file-name-tag", add_metadata_tag }, + { "add-creation-date-tag", add_metadata_tag }, + { "add-equipment-tag", add_metadata_tag }, + { "add-season-number-tag", add_metadata_tag }, + { "add-episode-number-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 gint +compare_int (gconstpointer a, + gconstpointer b) +{ + int *number1 = (int *) a; + int *number2 = (int *) b; + + return *number1 - *number2; +} + +/* This function splits the entry text into a list of regular text and tags. + * For instance, "[1, 2, 3]Paris[Creation date]" would result in: + * "[1, 2, 3]", "Paris", "[Creation date]" */ +static GList * +split_entry_text (NautilusBatchRenameDialog *self, + gchar *entry_text) +{ + GString *normal_text; + GString *tag; + GArray *tag_positions; + g_autoptr (GList) tag_info_keys = NULL; + GList *l; + 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_info_keys = g_hash_table_get_keys (self->tag_info_table); + + for (l = tag_info_keys; l != NULL; l = l->next) + { + tag_data = g_hash_table_lookup (self->tag_info_table, l->data); + if (tag_data->set) + { + g_array_append_val (tag_positions, tag_data->position); + tags++; + } + } + + g_array_sort (tag_positions, compare_int); + + for (i = 0; i < tags; i++) + { + tag = g_string_new (""); + + 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); + } + else + { + g_string_free (normal_text, TRUE); + } + + for (l = tag_info_keys; l != NULL; l = l->next) + { + g_autofree gchar *tag_text_representation = NULL; + + tag_data = g_hash_table_lookup (self->tag_info_table, l->data); + if (tag_data->set && g_array_index (tag_positions, gint, i) == tag_data->position) + { + tag_text_representation = batch_rename_get_tag_text_representation (tag_data->tag_constants); + tag_end_position = g_array_index (tag_positions, gint, i) + + g_utf8_strlen (tag_text_representation, -1); + tag = g_string_append (tag, tag_text_representation); + + break; + } + } + + 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); + } + else + { + g_string_free (normal_text, TRUE); + } + + 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 = NULL; + g_autofree gchar *replace_text = NULL; + + selection = dialog->selection; + text_chunks = NULL; + + if (dialog->mode == NAUTILUS_BATCH_RENAME_DIALOG_REPLACE) + { + entry_text = g_strdup (gtk_editable_get_text (GTK_EDITABLE (dialog->find_entry))); + } + else + { + entry_text = g_strdup (gtk_editable_get_text (GTK_EDITABLE (dialog->name_entry))); + } + + replace_text = g_strdup (gtk_editable_get_text (GTK_EDITABLE (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) +{ + batch_rename_sort_lists_for_rename (&dialog->selection, &new_names, NULL, NULL, NULL, FALSE); + + /* do the actual rename here */ + nautilus_file_batch_rename (dialog->selection, new_names, NULL, NULL); + + gtk_widget_set_cursor (GTK_WIDGET (dialog->window), NULL); +} + +static void +listbox_header_func (GtkListBoxRow *row, + GtkListBoxRow *before, + NautilusBatchRenameDialog *dialog) +{ + GtkWidget *separator; + + if (before == NULL) + { + /* First row needs no separator */ + gtk_list_box_row_set_header (row, NULL); + return; + } + + separator = gtk_list_box_row_get_header (row); + if (separator == NULL) + { + 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; + GtkRequisition current_row_natural_size; + 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_size (GTK_WIDGET (l->data), + NULL, + ¤t_row_natural_size); + + if (current_row_natural_size.height > maximum_height) + { + maximum_height = current_row_natural_size.height; + } + } + + for (l = dialog->listbox_labels_old; l != NULL; l = l->next) + { + gtk_widget_get_preferred_size (GTK_WIDGET (l->data), + NULL, + ¤t_row_natural_size); + + if (current_row_natural_size.height > maximum_height) + { + maximum_height = current_row_natural_size.height; + } + } + + for (l = dialog->listbox_icons; l != NULL; l = l->next) + { + gtk_widget_get_preferred_size (GTK_WIDGET (l->data), + NULL, + ¤t_row_natural_size); + + if (current_row_natural_size.height > maximum_height) + { + maximum_height = current_row_natural_size.height; + } + } + + if (maximum_height != dialog->row_height) + { + dialog->row_height = maximum_height + ROW_MARGIN_TOP_BOTTOM * 2; + + 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_label (NautilusBatchRenameDialog *dialog, + const gchar *old_text) +{ + GtkWidget *label_old; + + 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, ROW_MARGIN_START); + 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_widget_show (label_old); + + return label_old; +} + +static GtkWidget * +create_result_label (NautilusBatchRenameDialog *dialog, + const gchar *new_text) +{ + GtkWidget *label_new; + + 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, ROW_MARGIN_START); + 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_widget_show (label_new); + + return label_new; +} + +static GtkWidget * +create_arrow (NautilusBatchRenameDialog *dialog, + GtkTextDirection text_direction) +{ + GtkWidget *icon; + + if (text_direction == GTK_TEXT_DIR_RTL) + { + icon = gtk_label_new ("←"); + } + else + { + 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, ROW_MARGIN_START); + + dialog->listbox_icons = g_list_prepend (dialog->listbox_icons, icon); + + gtk_widget_show (icon); + + return icon; +} + +static void +prepare_batch_rename (NautilusBatchRenameDialog *dialog) +{ + /* wait for checking conflicts to finish, to be sure that + * the rename can actually take place */ + if (dialog->directories_pending_conflict_check != NULL) + { + dialog->rename_clicked = TRUE; + return; + } + + if (!gtk_widget_is_sensitive (dialog->rename_button)) + { + return; + } + + gtk_widget_set_cursor_from_name (GTK_WIDGET (dialog->window), "progress"); + + gtk_widget_set_cursor_from_name (GTK_WIDGET (dialog), "progress"); + + gtk_widget_hide (GTK_WIDGET (dialog)); + begin_batch_rename (dialog, dialog->new_names); + + gtk_window_destroy (GTK_WINDOW (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->directories_pending_conflict_check != NULL) + { + cancel_conflict_check (dialog); + } + + gtk_window_destroy (GTK_WINDOW (dialog)); + } +} + +static void +fill_display_listbox (NautilusBatchRenameDialog *dialog) +{ + GtkWidget *row_child; + GList *l1; + GList *l2; + NautilusFile *file; + GString *new_name; + gchar *name; + GtkTextDirection text_direction; + + gtk_size_group_add_widget (dialog->size_group, dialog->result_listbox); + gtk_size_group_add_widget (dialog->size_group, dialog->original_name_listbox); + + text_direction = gtk_widget_get_direction (GTK_WIDGET (dialog)); + + 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_child = create_original_name_label (dialog, name); + gtk_list_box_insert (GTK_LIST_BOX (dialog->original_name_listbox), row_child, -1); + + row_child = create_arrow (dialog, text_direction); + gtk_list_box_insert (GTK_LIST_BOX (dialog->arrow_listbox), row_child, -1); + + row_child = create_result_label (dialog, new_name->str); + gtk_list_box_insert (GTK_LIST_BOX (dialog->result_listbox), row_child, -1); + + g_free (name); + } + + 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; + GtkListBoxRow *list_box_row; + + 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->listbox_labels_new, nth_conflict_index); + list_box_row = GTK_LIST_BOX_ROW (gtk_widget_get_parent (l->data)); + gtk_list_box_select_row (GTK_LIST_BOX (dialog->original_name_listbox), + list_box_row); + + l = g_list_nth (dialog->listbox_labels_old, nth_conflict_index); + list_box_row = GTK_LIST_BOX_ROW (gtk_widget_get_parent (l->data)); + gtk_list_box_select_row (GTK_LIST_BOX (dialog->arrow_listbox), + list_box_row); + + l = g_list_nth (dialog->listbox_icons, nth_conflict_index); + list_box_row = GTK_LIST_BOX_ROW (gtk_widget_get_parent (l->data)); + gtk_list_box_select_row (GTK_LIST_BOX (dialog->result_listbox), + list_box_row); + + /* 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); + g_string_free (display_text, 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->listbox_labels_new, + l2 = dialog->listbox_labels_old, + l3 = dialog->listbox_icons; + l1 != NULL && l2 != NULL && l3 != NULL; + l1 = l1->next, l2 = l2->next, l3 = l3->next) + { + GtkWidget *row1 = gtk_widget_get_parent (l1->data); + GtkWidget *row2 = gtk_widget_get_parent (l2->data); + GtkWidget *row3 = gtk_widget_get_parent (l3->data); + + context = gtk_widget_get_style_context (row1); + + if (gtk_style_context_has_class (context, "conflict-row")) + { + gtk_style_context_remove_class (context, "conflict-row"); + + context = gtk_widget_get_style_context (row2); + gtk_style_context_remove_class (context, "conflict-row"); + + context = gtk_widget_get_style_context (row3); + 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 (row1); + gtk_style_context_add_class (context, "conflict-row"); + + context = gtk_widget_get_style_context (row2); + gtk_style_context_add_class (context, "conflict-row"); + + context = gtk_widget_get_style_context (row3); + 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; + gboolean empty_name = FALSE; + + 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); + gtk_widget_set_tooltip_text (GTK_WIDGET (label), new_name->str); + + if (g_strcmp0 (new_name->str, "") == 0) + { + empty_name = TRUE; + } + } + + for (l1 = dialog->selection, l2 = dialog->listbox_labels_old; l1 != NULL && l2 != NULL; l1 = l1->next, l2 = l2->next) + { + label = GTK_LABEL (l2->data); + file = NAUTILUS_FILE (l1->data); + + old_name = nautilus_file_get_name (file); + gtk_widget_set_tooltip_text (GTK_WIDGET (label), old_name); + + 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_editable_get_text (GTK_EDITABLE (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); + + if (empty_name) + { + gtk_widget_set_sensitive (dialog->rename_button, FALSE); + + return; + } + + /* 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; + } +} + +static 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 (parent_uri, 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); + } + + 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 +on_directory_attributes_ready_for_conflicts_check (NautilusDirectory *conflict_directory, + GList *files, + gpointer callback_data) +{ + NautilusBatchRenameDialog *self; + + self = NAUTILUS_BATCH_RENAME_DIALOG (callback_data); + + check_conflict_for_files (self, conflict_directory, files); + + g_assert (g_list_find (self->directories_pending_conflict_check, conflict_directory) != NULL); + + self->directories_pending_conflict_check = g_list_remove (self->directories_pending_conflict_check, conflict_directory); + + nautilus_directory_unref (conflict_directory); + + if (self->directories_pending_conflict_check == NULL) + { + self->duplicates = g_list_reverse (self->duplicates); + + update_listbox (self); + } +} + +static void +cancel_conflict_check (NautilusBatchRenameDialog *self) +{ + GList *l; + NautilusDirectory *directory; + + for (l = self->directories_pending_conflict_check; l != NULL; l = l->next) + { + directory = l->data; + + nautilus_directory_cancel_callback (directory, + on_directory_attributes_ready_for_conflicts_check, + self); + } + + g_clear_list (&self->directories_pending_conflict_check, g_object_unref); +} + +static void +file_names_list_has_duplicates_async (NautilusBatchRenameDialog *self) +{ + GList *l; + + if (self->directories_pending_conflict_check != NULL) + { + cancel_conflict_check (self); + } + + self->directories_pending_conflict_check = nautilus_directory_list_copy (self->distinct_parent_directories); + self->duplicates = NULL; + + for (l = self->distinct_parent_directories; l != NULL; l = l->next) + { + nautilus_directory_call_when_ready (l->data, + NAUTILUS_FILE_ATTRIBUTE_INFO, + TRUE, + on_directory_attributes_ready_for_conflicts_check, + self); + } +} + +static gboolean +have_unallowed_character (NautilusBatchRenameDialog *dialog) +{ + GList *names; + GString *new_name; + const gchar *entry_text; + gboolean have_empty_name; + gboolean have_unallowed_character_slash; + gboolean have_unallowed_character_dot; + gboolean have_unallowed_character_dotdot; + + have_empty_name = FALSE; + 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_editable_get_text (GTK_EDITABLE (dialog->name_entry)); + } + else + { + entry_text = gtk_editable_get_text (GTK_EDITABLE (dialog->replace_entry)); + } + + if (strstr (entry_text, "/") != NULL) + { + have_unallowed_character_slash = TRUE; + } + + if (dialog->mode == NAUTILUS_BATCH_RENAME_DIALOG_FORMAT && g_strcmp0 (entry_text, ".") == 0) + { + have_unallowed_character_dot = TRUE; + } + else if (dialog->mode == NAUTILUS_BATCH_RENAME_DIALOG_REPLACE) + { + for (names = dialog->new_names; names != NULL; names = names->next) + { + new_name = names->data; + + if (g_strcmp0 (new_name->str, ".") == 0) + { + have_unallowed_character_dot = TRUE; + break; + } + } + } + + if (dialog->mode == NAUTILUS_BATCH_RENAME_DIALOG_FORMAT && g_strcmp0 (entry_text, "..") == 0) + { + have_unallowed_character_dotdot = TRUE; + } + else if (dialog->mode == NAUTILUS_BATCH_RENAME_DIALOG_REPLACE) + { + for (names = dialog->new_names; names != NULL; names = names->next) + { + new_name = names->data; + + if (g_strcmp0 (new_name->str, "") == 0) + { + have_empty_name = TRUE; + break; + } + + if (g_strcmp0 (new_name->str, "..") == 0) + { + have_unallowed_character_dotdot = TRUE; + break; + } + } + } + + if (have_empty_name) + { + gtk_label_set_label (GTK_LABEL (dialog->conflict_label), + _("Name cannot be empty.")); + } + + if (have_unallowed_character_slash) + { + gtk_label_set_label (GTK_LABEL (dialog->conflict_label), + _("Name cannot contain “/”.")); + } + + if (have_unallowed_character_dot) + { + gtk_label_set_label (GTK_LABEL (dialog->conflict_label), + _("“.” is not a valid name.")); + } + + if (have_unallowed_character_dotdot) + { + gtk_label_set_label (GTK_LABEL (dialog->conflict_label), + _("“..” is not a valid name.")); + } + + if (have_unallowed_character_slash || have_unallowed_character_dot || have_unallowed_character_dotdot + || have_empty_name) + { + 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 gboolean +numbering_tag_is_some_added (NautilusBatchRenameDialog *self) +{ + guint i; + TagData *tag_data; + + for (i = 0; i < G_N_ELEMENTS (numbering_tags_constants); i++) + { + g_autofree gchar *tag_text_representation = NULL; + + tag_text_representation = batch_rename_get_tag_text_representation (numbering_tags_constants[i]); + tag_data = g_hash_table_lookup (self->tag_info_table, tag_text_representation); + if (tag_data->set) + { + return TRUE; + } + } + + return FALSE; +} + +static void +update_display_text (NautilusBatchRenameDialog *dialog) +{ + if (dialog->selection == NULL) + { + return; + } + + if (dialog->duplicates != NULL) + { + g_list_free_full (dialog->duplicates, conflict_data_free); + dialog->duplicates = NULL; + } + + if (dialog->new_names != NULL) + { + g_list_free_full (dialog->new_names, string_free); + } + + if (!numbering_tag_is_some_added (dialog)) + { + gtk_revealer_set_reveal_child (GTK_REVEALER (dialog->numbering_revealer), FALSE); + } + else + { + gtk_revealer_set_reveal_child (GTK_REVEALER (dialog->numbering_revealer), TRUE); + } + + dialog->new_names = batch_rename_dialog_get_new_names (dialog); + + if (have_unallowed_character (dialog)) + { + return; + } + + file_names_list_has_duplicates_async (dialog); +} + +static void +batch_rename_dialog_mode_changed (NautilusBatchRenameDialog *dialog) +{ + if (gtk_check_button_get_active (GTK_CHECK_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); +} + +void +nautilus_batch_rename_dialog_query_finished (NautilusBatchRenameDialog *dialog, + GHashTable *hash_table, + GList *selection_metadata) +{ + GMenuItem *first_created; + GMenuItem *last_created; + FileMetadata *file_metadata; + MetadataType metadata_type; + gboolean is_metadata; + TagData *tag_data; + g_autoptr (GList) tag_info_keys = NULL; + GList *l; + + /* 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; + file_metadata = selection_metadata->data; + tag_info_keys = g_hash_table_get_keys (dialog->tag_info_table); + for (l = tag_info_keys; l != NULL; l = l->next) + { + /* Only metadata has to be handled here. */ + tag_data = g_hash_table_lookup (dialog->tag_info_table, l->data); + is_metadata = tag_data->tag_constants.is_metadata; + if (!is_metadata) + { + continue; + } + + metadata_type = tag_data->tag_constants.metadata_type; + if (file_metadata->metadata[metadata_type] == NULL || + file_metadata->metadata[metadata_type]->len <= 0) + { + disable_action (dialog, tag_data->tag_constants.action_name); + tag_data->available = 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_FLAG_PRELIGHT; + } + else + { + flags &= ~GTK_STATE_FLAG_PRELIGHT; + } + + gtk_style_context_set_state (context, flags); +} + +static void +on_event_controller_motion_motion (GtkEventControllerMotion *controller, + double x, + double y, + gpointer user_data) +{ + GtkWidget *widget; + NautilusBatchRenameDialog *dialog; + GtkListBoxRow *row; + + widget = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (controller)); + 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), 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), 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), 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), 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), 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), y); + update_row_shadowing (GTK_WIDGET (row), TRUE); + dialog->preselected_row2 = GTK_WIDGET (row); + } +} + +static void +on_event_controller_motion_leave (GtkEventControllerMotion *controller, + 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; +} + +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), + metadata_tags_constants[ORIGINAL_FILE_NAME].action_name); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE); + + check_metadata_for_selection (dialog, dialog->selection, + dialog->metadata_cancellable); + + /* Make sure that the state is initialized to name-ascending */ + action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group), + "numbering-order-changed"); + g_action_change_state (action, g_variant_new_string ("name-ascending")); +} + +static void +file_names_widget_on_activate (NautilusBatchRenameDialog *dialog) +{ + prepare_batch_rename (dialog); +} + +static void +remove_tag (NautilusBatchRenameDialog *dialog, + TagData *tag_data) +{ + GAction *action; + + if (!tag_data->set) + { + g_warning ("Trying to remove an already removed tag"); + + return; + } + + tag_data->set = FALSE; + tag_data->position = -1; + action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group), + tag_data->tag_constants.action_name); + g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE); +} + +static gint +compare_tag_position (gconstpointer a, + gconstpointer b) +{ + const TagData *tag_data1 = a; + const TagData *tag_data2 = b; + + return tag_data1->position - tag_data2->position; +} + +typedef enum +{ + TEXT_WAS_DELETED, + TEXT_WAS_INSERTED +} TextChangedMode; + +static GList * +get_tags_intersecting_sorted (NautilusBatchRenameDialog *self, + gint start_position, + gint end_position, + TextChangedMode text_changed_mode) +{ + g_autoptr (GList) tag_info_keys = NULL; + TagData *tag_data; + GList *l; + GList *intersecting_tags = NULL; + gint tag_end_position; + + tag_info_keys = g_hash_table_get_keys (self->tag_info_table); + for (l = tag_info_keys; l != NULL; l = l->next) + { + g_autofree gchar *tag_text_representation = NULL; + + tag_data = g_hash_table_lookup (self->tag_info_table, l->data); + tag_text_representation = batch_rename_get_tag_text_representation (tag_data->tag_constants); + tag_end_position = tag_data->position + g_utf8_strlen (tag_text_representation, -1); + if (tag_data->set && !tag_data->just_added) + { + gboolean selection_intersects_tag_start; + gboolean selection_intersects_tag_end; + gboolean tag_is_contained_in_selection; + + if (text_changed_mode == TEXT_WAS_DELETED) + { + selection_intersects_tag_start = end_position > tag_data->position && + end_position <= tag_end_position; + selection_intersects_tag_end = start_position >= tag_data->position && + start_position < tag_end_position; + tag_is_contained_in_selection = start_position <= tag_data->position && + end_position >= tag_end_position; + } + else + { + selection_intersects_tag_start = start_position > tag_data->position && + start_position < tag_end_position; + selection_intersects_tag_end = FALSE; + tag_is_contained_in_selection = FALSE; + } + if (selection_intersects_tag_end || selection_intersects_tag_start || tag_is_contained_in_selection) + { + intersecting_tags = g_list_prepend (intersecting_tags, tag_data); + } + } + } + + return g_list_sort (intersecting_tags, compare_tag_position); +} + +static void +update_tags_positions (NautilusBatchRenameDialog *self, + gint start_position, + gint end_position, + TextChangedMode text_changed_mode) +{ + g_autoptr (GList) tag_info_keys = NULL; + TagData *tag_data; + GList *l; + + tag_info_keys = g_hash_table_get_keys (self->tag_info_table); + for (l = tag_info_keys; l != NULL; l = l->next) + { + tag_data = g_hash_table_lookup (self->tag_info_table, l->data); + if (tag_data->set && !tag_data->just_added && tag_data->position >= start_position) + { + if (text_changed_mode == TEXT_WAS_DELETED) + { + tag_data->position -= end_position - start_position; + } + else + { + tag_data->position += end_position - start_position; + } + } + } +} + +static void +on_delete_text (GtkEditable *editable, + gint start_position, + gint end_position, + gpointer user_data) +{ + NautilusBatchRenameDialog *self; + g_autoptr (GList) intersecting_tags = NULL; + gint final_start_position; + gint final_end_position; + GList *l; + + self = NAUTILUS_BATCH_RENAME_DIALOG (user_data); + intersecting_tags = get_tags_intersecting_sorted (self, start_position, + end_position, TEXT_WAS_DELETED); + if (intersecting_tags) + { + gint last_tag_end_position; + g_autofree gchar *tag_text_representation = NULL; + TagData *first_tag = g_list_first (intersecting_tags)->data; + TagData *last_tag = g_list_last (intersecting_tags)->data; + + tag_text_representation = batch_rename_get_tag_text_representation (last_tag->tag_constants); + last_tag_end_position = last_tag->position + + g_utf8_strlen (tag_text_representation, -1); + final_start_position = MIN (start_position, first_tag->position); + final_end_position = MAX (end_position, last_tag_end_position); + } + else + { + final_start_position = start_position; + final_end_position = end_position; + } + + g_signal_handlers_block_by_func (editable, (gpointer) on_delete_text, user_data); + gtk_editable_delete_text (editable, final_start_position, final_end_position); + g_signal_handlers_unblock_by_func (editable, (gpointer) on_delete_text, user_data); + + /* Mark the tags as removed */ + for (l = intersecting_tags; l != NULL; l = l->next) + { + remove_tag (self, l->data); + } + + /* If we removed the numbering tag, we want to enable all numbering actions */ + if (!numbering_tag_is_some_added (self)) + { + guint i; + + for (i = 0; i < G_N_ELEMENTS (numbering_tags_constants); i++) + { + enable_action (self, numbering_tags_constants[i].action_name); + } + } + + update_tags_positions (self, final_start_position, + final_end_position, TEXT_WAS_DELETED); + update_display_text (self); + + g_signal_stop_emission_by_name (editable, "delete-text"); +} + +static void +on_insert_text (GtkEditable *editable, + const gchar *new_text, + gint new_text_length, + gpointer position, + gpointer user_data) +{ + NautilusBatchRenameDialog *self; + gint start_position; + gint end_position; + g_autoptr (GList) intersecting_tags = NULL; + + self = NAUTILUS_BATCH_RENAME_DIALOG (user_data); + start_position = *(int *) position; + end_position = start_position + g_utf8_strlen (new_text, -1); + intersecting_tags = get_tags_intersecting_sorted (self, start_position, + end_position, TEXT_WAS_INSERTED); + if (!intersecting_tags) + { + g_signal_handlers_block_by_func (editable, (gpointer) on_insert_text, user_data); + gtk_editable_insert_text (editable, new_text, new_text_length, position); + g_signal_handlers_unblock_by_func (editable, (gpointer) on_insert_text, user_data); + + update_tags_positions (self, start_position, end_position, TEXT_WAS_INSERTED); + update_display_text (self); + } + + g_signal_stop_emission_by_name (editable, "insert-text"); +} + +static void +file_names_widget_entry_on_changed (NautilusBatchRenameDialog *self) +{ + update_display_text (self); +} + +static void +nautilus_batch_rename_dialog_finalize (GObject *object) +{ + NautilusBatchRenameDialog *dialog; + GList *l; + guint i; + + dialog = NAUTILUS_BATCH_RENAME_DIALOG (object); + + if (dialog->directories_pending_conflict_check != NULL) + { + cancel_conflict_check (dialog); + } + + 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 *file_metadata; + + file_metadata = l->data; + for (i = 0; i < G_N_ELEMENTS (file_metadata->metadata); i++) + { + if (file_metadata->metadata[i]) + { + g_string_free (file_metadata->metadata[i], TRUE); + } + } + + g_string_free (file_metadata->file_name, TRUE); + g_free (file_metadata); + } + + if (dialog->create_date != NULL) + { + g_hash_table_destroy (dialog->create_date); + } + + g_list_free_full (dialog->new_names, string_free); + g_list_free_full (dialog->duplicates, conflict_data_free); + + nautilus_file_list_free (dialog->selection); + nautilus_directory_unref (dialog->directory); + nautilus_directory_list_free (dialog->distinct_parent_directories); + + g_object_unref (dialog->size_group); + + g_hash_table_destroy (dialog->tag_info_table); + + g_cancellable_cancel (dialog->metadata_cancellable); + g_clear_object (&dialog->metadata_cancellable); + + 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, numbering_order_button); + gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, scrolled_window); + gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, numbering_order_menu); + gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, numbering_revealer); + 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, numbering_label); + + gtk_widget_class_bind_template_callback (widget_class, file_names_widget_on_activate); + gtk_widget_class_bind_template_callback (widget_class, file_names_widget_entry_on_changed); + gtk_widget_class_bind_template_callback (widget_class, batch_rename_dialog_mode_changed); + 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); +} + +GtkWidget * +nautilus_batch_rename_dialog_new (GList *selection, + NautilusDirectory *directory, + NautilusWindow *window) +{ + NautilusBatchRenameDialog *dialog; + GString *dialog_title; + GList *l; + gboolean all_targets_are_folders; + gboolean all_targets_are_regular_files; + + dialog = g_object_new (NAUTILUS_TYPE_BATCH_RENAME_DIALOG, "use-header-bar", TRUE, NULL); + + dialog->selection = nautilus_file_list_copy (selection); + dialog->directory = nautilus_directory_ref (directory); + dialog->window = window; + + gtk_window_set_transient_for (GTK_WINDOW (dialog), + GTK_WINDOW (window)); + + all_targets_are_folders = TRUE; + for (l = selection; l != NULL; l = l->next) + { + if (!nautilus_file_is_directory (NAUTILUS_FILE (l->data))) + { + all_targets_are_folders = FALSE; + break; + } + } + + all_targets_are_regular_files = TRUE; + for (l = selection; l != NULL; l = l->next) + { + if (!nautilus_file_is_regular_file (NAUTILUS_FILE (l->data))) + { + all_targets_are_regular_files = FALSE; + break; + } + } + + dialog_title = g_string_new (""); + if (all_targets_are_folders) + { + g_string_append_printf (dialog_title, + ngettext ("Rename %d Folder", + "Rename %d Folders", + g_list_length (selection)), + g_list_length (selection)); + } + else if (all_targets_are_regular_files) + { + g_string_append_printf (dialog_title, + ngettext ("Rename %d File", + "Rename %d Files", + g_list_length (selection)), + g_list_length (selection)); + } + else + { + g_string_append_printf (dialog_title, + /* To translators: %d is the total number of files and folders. + * Singular case of the string is never used */ + ngettext ("Rename %d File and Folder", + "Rename %d Files and Folders", + g_list_length (selection)), + g_list_length (selection)); + } + + gtk_window_set_title (GTK_WINDOW (dialog), dialog_title->str); + + dialog->distinct_parent_directories = batch_rename_files_get_distinct_parents (selection); + + add_tag (dialog, metadata_tags_constants[ORIGINAL_FILE_NAME]); + + nautilus_batch_rename_dialog_initialize_actions (dialog); + + update_display_text (dialog); + + fill_display_listbox (dialog); + + gtk_widget_set_cursor (GTK_WIDGET (window), NULL); + + g_string_free (dialog_title, TRUE); + + return GTK_WIDGET (dialog); +} + +static void +connect_to_pointer_motion_events (NautilusBatchRenameDialog *self, + GtkWidget *listbox) +{ + GtkEventController *controller; + + controller = gtk_event_controller_motion_new (); + gtk_widget_add_controller (listbox, controller); + gtk_event_controller_set_propagation_phase (controller, GTK_PHASE_CAPTURE); + g_signal_connect (controller, "leave", + G_CALLBACK (on_event_controller_motion_leave), self); + g_signal_connect (controller, "motion", + G_CALLBACK (on_event_controller_motion_motion), self); +} + +static void +nautilus_batch_rename_dialog_init (NautilusBatchRenameDialog *self) +{ + TagData *tag_data; + guint i; + + 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_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->distinct_parent_directories = NULL; + self->directories_pending_conflict_check = NULL; + self->new_names = NULL; + 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); + + for (i = 0; i < G_N_ELEMENTS (numbering_tags_constants); i++) + { + g_autofree gchar *tag_text_representation = NULL; + + tag_text_representation = batch_rename_get_tag_text_representation (numbering_tags_constants[i]); + tag_data = g_new (TagData, 1); + tag_data->available = TRUE; + tag_data->set = FALSE; + tag_data->position = -1; + tag_data->tag_constants = numbering_tags_constants[i]; + g_hash_table_insert (self->tag_info_table, g_strdup (tag_text_representation), tag_data); + } + + for (i = 0; i < G_N_ELEMENTS (metadata_tags_constants); i++) + { + g_autofree gchar *tag_text_representation = NULL; + + /* Only the original name is available and set at the start */ + tag_text_representation = batch_rename_get_tag_text_representation (metadata_tags_constants[i]); + + tag_data = g_new (TagData, 1); + tag_data->available = FALSE; + tag_data->set = FALSE; + tag_data->position = -1; + tag_data->tag_constants = metadata_tags_constants[i]; + g_hash_table_insert (self->tag_info_table, g_strdup (tag_text_representation), tag_data); + } + + self->row_height = -1; + + g_signal_connect_object (gtk_editable_get_delegate (GTK_EDITABLE (self->name_entry)), + "delete-text", G_CALLBACK (on_delete_text), self, 0); + g_signal_connect_object (gtk_editable_get_delegate (GTK_EDITABLE (self->name_entry)), + "insert-text", G_CALLBACK (on_insert_text), self, 0); + + self->size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + + connect_to_pointer_motion_events (self, self->original_name_listbox); + connect_to_pointer_motion_events (self, self->result_listbox); + connect_to_pointer_motion_events (self, self->arrow_listbox); + + self->metadata_cancellable = g_cancellable_new (); +} |