1 /* nautilus-batch-rename-dialog.c
2  *
3  * Copyright (C) 2016 Alexandru Pandelea <alexandru.pandelea@gmail.com>
4  *
5  * This program is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation, either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 #include <config.h>
20 
21 #include "nautilus-batch-rename-dialog.h"
22 #include "nautilus-file.h"
23 #include "nautilus-error-reporting.h"
24 #include "nautilus-batch-rename-utilities.h"
25 
26 #include <glib/gprintf.h>
27 #include <glib.h>
28 #include <string.h>
29 #include <glib/gi18n.h>
30 
31 #define ROW_MARGIN_START 6
32 #define ROW_MARGIN_TOP_BOTTOM 4
33 
34 struct _NautilusBatchRenameDialog
35 {
36     GtkDialog parent;
37 
38     GtkWidget *grid;
39     NautilusWindow *window;
40 
41     GtkWidget *cancel_button;
42     GtkWidget *original_name_listbox;
43     GtkWidget *arrow_listbox;
44     GtkWidget *result_listbox;
45     GtkWidget *name_entry;
46     GtkWidget *rename_button;
47     GtkWidget *find_entry;
48     GtkWidget *mode_stack;
49     GtkWidget *replace_entry;
50     GtkWidget *format_mode_button;
51     GtkWidget *replace_mode_button;
52     GtkWidget *add_button;
53     GtkWidget *add_popover;
54     GtkWidget *numbering_order_label;
55     GtkWidget *numbering_label;
56     GtkWidget *scrolled_window;
57     GtkWidget *numbering_order_popover;
58     GtkWidget *numbering_order_button;
59     GtkWidget *numbering_revealer;
60     GtkWidget *conflict_box;
61     GtkWidget *conflict_label;
62     GtkWidget *conflict_down;
63     GtkWidget *conflict_up;
64 
65     GList *original_name_listbox_rows;
66     GList *arrow_listbox_rows;
67     GList *result_listbox_rows;
68     GList *listbox_labels_new;
69     GList *listbox_labels_old;
70     GList *listbox_icons;
71     GtkSizeGroup *size_group;
72 
73     GList *selection;
74     GList *new_names;
75     NautilusBatchRenameDialogMode mode;
76     NautilusDirectory *directory;
77 
78     GActionGroup *action_group;
79 
80     GMenu *numbering_order_menu;
81     GMenu *add_tag_menu;
82 
83     GHashTable *create_date;
84     GList *selection_metadata;
85 
86     /* the index of the currently selected conflict */
87     gint selected_conflict;
88     /* total conflicts number */
89     gint conflicts_number;
90 
91     GList *duplicates;
92     GList *distinct_parent_directories;
93     GList *directories_pending_conflict_check;
94 
95     /* this hash table has information about the status
96      * of all tags: availability, if it's currently used
97      * and position */
98     GHashTable *tag_info_table;
99 
100     GtkWidget *preselected_row1;
101     GtkWidget *preselected_row2;
102 
103     gint row_height;
104     gboolean rename_clicked;
105 
106     GCancellable *metadata_cancellable;
107 };
108 
109 typedef struct
110 {
111     gboolean available;
112     gboolean set;
113     gint position;
114     /* if the tag was just added, then we shouldn't update it's position */
115     gboolean just_added;
116     TagConstants tag_constants;
117 } TagData;
118 
119 
120 static void     update_display_text (NautilusBatchRenameDialog *dialog);
121 static void     cancel_conflict_check (NautilusBatchRenameDialog *self);
122 
123 G_DEFINE_TYPE (NautilusBatchRenameDialog, nautilus_batch_rename_dialog, GTK_TYPE_DIALOG);
124 
125 static void
change_numbering_order(GSimpleAction * action,GVariant * value,gpointer user_data)126 change_numbering_order (GSimpleAction *action,
127                         GVariant      *value,
128                         gpointer       user_data)
129 {
130     NautilusBatchRenameDialog *dialog;
131     const gchar *target_name;
132     guint i;
133 
134     dialog = NAUTILUS_BATCH_RENAME_DIALOG (user_data);
135 
136     target_name = g_variant_get_string (value, NULL);
137 
138     for (i = 0; i < G_N_ELEMENTS (sorts_constants); i++)
139     {
140         if (g_strcmp0 (sorts_constants[i].action_target_name, target_name) == 0)
141         {
142             gtk_label_set_label (GTK_LABEL (dialog->numbering_order_label),
143                                  gettext (sorts_constants[i].label));
144             dialog->selection = nautilus_batch_rename_dialog_sort (dialog->selection,
145                                                                    sorts_constants[i].sort_mode,
146                                                                    dialog->create_date);
147             break;
148         }
149     }
150 
151     g_simple_action_set_state (action, value);
152 
153     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->numbering_order_button), FALSE);
154 
155     update_display_text (dialog);
156 }
157 
158 static void
enable_action(NautilusBatchRenameDialog * self,const gchar * action_name)159 enable_action (NautilusBatchRenameDialog *self,
160                const gchar               *action_name)
161 {
162     GAction *action;
163 
164     action = g_action_map_lookup_action (G_ACTION_MAP (self->action_group),
165                                          action_name);
166     g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE);
167 }
168 
169 static void
disable_action(NautilusBatchRenameDialog * self,const gchar * action_name)170 disable_action (NautilusBatchRenameDialog *self,
171                 const gchar               *action_name)
172 {
173     GAction *action;
174 
175     action = g_action_map_lookup_action (G_ACTION_MAP (self->action_group),
176                                          action_name);
177     g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
178 }
179 
180 static void
add_tag(NautilusBatchRenameDialog * self,TagConstants tag_constants)181 add_tag (NautilusBatchRenameDialog *self,
182          TagConstants               tag_constants)
183 {
184     g_autofree gchar *tag_text_representation = NULL;
185     gint cursor_position;
186     TagData *tag_data;
187 
188     g_object_get (self->name_entry, "cursor-position", &cursor_position, NULL);
189 
190     tag_text_representation = batch_rename_get_tag_text_representation (tag_constants);
191     tag_data = g_hash_table_lookup (self->tag_info_table, tag_text_representation);
192     tag_data->available = TRUE;
193     tag_data->set = TRUE;
194     tag_data->just_added = TRUE;
195     tag_data->position = cursor_position;
196 
197     /* FIXME: We can add a tag when the cursor is inside a tag, which breaks this.
198      * We need to check the cursor movement and update the actions acordingly or
199      * even better add the tag at the end of the previous tag if this happens.
200      */
201     gtk_editable_insert_text (GTK_EDITABLE (self->name_entry),
202                               tag_text_representation,
203                               strlen (tag_text_representation),
204                               &cursor_position);
205     tag_data->just_added = FALSE;
206     gtk_editable_set_position (GTK_EDITABLE (self->name_entry), cursor_position);
207 
208     gtk_entry_grab_focus_without_selecting (GTK_ENTRY (self->name_entry));
209 }
210 
211 static void
add_metadata_tag(GSimpleAction * action,GVariant * value,gpointer user_data)212 add_metadata_tag (GSimpleAction *action,
213                   GVariant      *value,
214                   gpointer       user_data)
215 {
216     NautilusBatchRenameDialog *self;
217     const gchar *action_name;
218     guint i;
219 
220     self = NAUTILUS_BATCH_RENAME_DIALOG (user_data);
221     action_name = g_action_get_name (G_ACTION (action));
222 
223     for (i = 0; i < G_N_ELEMENTS (metadata_tags_constants); i++)
224     {
225         if (g_strcmp0 (metadata_tags_constants[i].action_name, action_name) == 0)
226         {
227             add_tag (self, metadata_tags_constants[i]);
228             disable_action (self, metadata_tags_constants[i].action_name);
229 
230             break;
231         }
232     }
233 }
234 
235 static void
add_numbering_tag(GSimpleAction * action,GVariant * value,gpointer user_data)236 add_numbering_tag (GSimpleAction *action,
237                    GVariant      *value,
238                    gpointer       user_data)
239 {
240     NautilusBatchRenameDialog *self;
241     const gchar *action_name;
242     guint i;
243 
244     self = NAUTILUS_BATCH_RENAME_DIALOG (user_data);
245     action_name = g_action_get_name (G_ACTION (action));
246 
247     for (i = 0; i < G_N_ELEMENTS (numbering_tags_constants); i++)
248     {
249         if (g_strcmp0 (numbering_tags_constants[i].action_name, action_name) == 0)
250         {
251             add_tag (self, numbering_tags_constants[i]);
252         }
253         /* We want to allow only one tag of numbering type, so we disable all
254          * of them */
255         disable_action (self, numbering_tags_constants[i].action_name);
256     }
257 }
258 
259 const GActionEntry dialog_entries[] =
260 {
261     { "numbering-order-changed", NULL, "s", "'name-ascending'", change_numbering_order },
262     { "add-numbering-no-zero-pad-tag", add_numbering_tag },
263     { "add-numbering-one-zero-pad-tag", add_numbering_tag },
264     { "add-numbering-two-zero-pad-tag", add_numbering_tag },
265     { "add-original-file-name-tag", add_metadata_tag },
266     { "add-creation-date-tag", add_metadata_tag },
267     { "add-equipment-tag", add_metadata_tag },
268     { "add-season-number-tag", add_metadata_tag },
269     { "add-episode-number-tag", add_metadata_tag },
270     { "add-video-album-tag", add_metadata_tag },
271     { "add-track-number-tag", add_metadata_tag },
272     { "add-artist-name-tag", add_metadata_tag },
273     { "add-title-tag", add_metadata_tag },
274     { "add-album-name-tag", add_metadata_tag },
275 };
276 
277 static void
row_selected(GtkListBox * box,GtkListBoxRow * listbox_row,gpointer user_data)278 row_selected (GtkListBox    *box,
279               GtkListBoxRow *listbox_row,
280               gpointer       user_data)
281 {
282     NautilusBatchRenameDialog *dialog;
283     GtkListBoxRow *row;
284     gint index;
285 
286     if (!GTK_IS_LIST_BOX_ROW (listbox_row))
287     {
288         return;
289     }
290 
291     dialog = NAUTILUS_BATCH_RENAME_DIALOG (user_data);
292     index = gtk_list_box_row_get_index (listbox_row);
293 
294     if (GTK_WIDGET (box) == dialog->original_name_listbox)
295     {
296         row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (dialog->arrow_listbox), index);
297         gtk_list_box_select_row (GTK_LIST_BOX (dialog->arrow_listbox),
298                                  row);
299         row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (dialog->result_listbox), index);
300         gtk_list_box_select_row (GTK_LIST_BOX (dialog->result_listbox),
301                                  row);
302     }
303 
304     if (GTK_WIDGET (box) == dialog->arrow_listbox)
305     {
306         row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (dialog->original_name_listbox), index);
307         gtk_list_box_select_row (GTK_LIST_BOX (dialog->original_name_listbox),
308                                  row);
309         row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (dialog->result_listbox), index);
310         gtk_list_box_select_row (GTK_LIST_BOX (dialog->result_listbox),
311                                  row);
312     }
313 
314     if (GTK_WIDGET (box) == dialog->result_listbox)
315     {
316         row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (dialog->arrow_listbox), index);
317         gtk_list_box_select_row (GTK_LIST_BOX (dialog->arrow_listbox),
318                                  row);
319         row = gtk_list_box_get_row_at_index (GTK_LIST_BOX (dialog->original_name_listbox), index);
320         gtk_list_box_select_row (GTK_LIST_BOX (dialog->original_name_listbox),
321                                  row);
322     }
323 }
324 
325 static gint
compare_int(gconstpointer a,gconstpointer b)326 compare_int (gconstpointer a,
327              gconstpointer b)
328 {
329     int *number1 = (int *) a;
330     int *number2 = (int *) b;
331 
332     return *number1 - *number2;
333 }
334 
335 /* This function splits the entry text into a list of regular text and tags.
336  * For instance, "[1, 2, 3]Paris[Creation date]" would result in:
337  * "[1, 2, 3]", "Paris", "[Creation date]" */
338 static GList *
split_entry_text(NautilusBatchRenameDialog * self,gchar * entry_text)339 split_entry_text (NautilusBatchRenameDialog *self,
340                   gchar                     *entry_text)
341 {
342     GString *normal_text;
343     GString *tag;
344     GArray *tag_positions;
345     g_autoptr (GList) tag_info_keys = NULL;
346     GList *l;
347     gint tags;
348     gint i;
349     gchar *substring;
350     gint tag_end_position;
351     GList *result = NULL;
352     TagData *tag_data;
353 
354     tags = 0;
355     tag_end_position = 0;
356     tag_positions = g_array_new (FALSE, FALSE, sizeof (gint));
357 
358     tag_info_keys = g_hash_table_get_keys (self->tag_info_table);
359 
360     for (l = tag_info_keys; l != NULL; l = l->next)
361     {
362         tag_data = g_hash_table_lookup (self->tag_info_table, l->data);
363         if (tag_data->set)
364         {
365             g_array_append_val (tag_positions, tag_data->position);
366             tags++;
367         }
368     }
369 
370     g_array_sort (tag_positions, compare_int);
371 
372     for (i = 0; i < tags; i++)
373     {
374         tag = g_string_new ("");
375 
376         substring = g_utf8_substring (entry_text, tag_end_position,
377                                       g_array_index (tag_positions, gint, i));
378         normal_text = g_string_new (substring);
379         g_free (substring);
380 
381         if (g_strcmp0 (normal_text->str, ""))
382         {
383             result = g_list_prepend (result, normal_text);
384         }
385         else
386         {
387             g_string_free (normal_text, TRUE);
388         }
389 
390         for (l = tag_info_keys; l != NULL; l = l->next)
391         {
392             g_autofree gchar *tag_text_representation = NULL;
393 
394             tag_data = g_hash_table_lookup (self->tag_info_table, l->data);
395             if (tag_data->set && g_array_index (tag_positions, gint, i) == tag_data->position)
396             {
397                 tag_text_representation = batch_rename_get_tag_text_representation (tag_data->tag_constants);
398                 tag_end_position = g_array_index (tag_positions, gint, i) +
399                                    g_utf8_strlen (tag_text_representation, -1);
400                 tag = g_string_append (tag, tag_text_representation);
401 
402                 break;
403             }
404         }
405 
406         result = g_list_prepend (result, tag);
407     }
408 
409     normal_text = g_string_new (g_utf8_offset_to_pointer (entry_text, tag_end_position));
410 
411     if (g_strcmp0 (normal_text->str, "") != 0)
412     {
413         result = g_list_prepend (result, normal_text);
414     }
415     else
416     {
417         g_string_free (normal_text, TRUE);
418     }
419 
420     result = g_list_reverse (result);
421 
422     g_array_free (tag_positions, TRUE);
423     return result;
424 }
425 
426 static GList *
batch_rename_dialog_get_new_names(NautilusBatchRenameDialog * dialog)427 batch_rename_dialog_get_new_names (NautilusBatchRenameDialog *dialog)
428 {
429     GList *result = NULL;
430     GList *selection;
431     GList *text_chunks;
432     g_autofree gchar *entry_text = NULL;
433     g_autofree gchar *replace_text = NULL;
434 
435     selection = dialog->selection;
436     text_chunks = NULL;
437 
438     if (dialog->mode == NAUTILUS_BATCH_RENAME_DIALOG_REPLACE)
439     {
440         entry_text = g_strdup (gtk_entry_get_text (GTK_ENTRY (dialog->find_entry)));
441     }
442     else
443     {
444         entry_text = g_strdup (gtk_entry_get_text (GTK_ENTRY (dialog->name_entry)));
445     }
446 
447     replace_text = g_strdup (gtk_entry_get_text (GTK_ENTRY (dialog->replace_entry)));
448 
449     if (dialog->mode == NAUTILUS_BATCH_RENAME_DIALOG_REPLACE)
450     {
451         result = batch_rename_dialog_get_new_names_list (dialog->mode,
452                                                          selection,
453                                                          NULL,
454                                                          NULL,
455                                                          entry_text,
456                                                          replace_text);
457     }
458     else
459     {
460         text_chunks = split_entry_text (dialog, entry_text);
461 
462         result = batch_rename_dialog_get_new_names_list (dialog->mode,
463                                                          selection,
464                                                          text_chunks,
465                                                          dialog->selection_metadata,
466                                                          entry_text,
467                                                          replace_text);
468         g_list_free_full (text_chunks, string_free);
469     }
470 
471     result = g_list_reverse (result);
472 
473     return result;
474 }
475 
476 static void
begin_batch_rename(NautilusBatchRenameDialog * dialog,GList * new_names)477 begin_batch_rename (NautilusBatchRenameDialog *dialog,
478                     GList                     *new_names)
479 {
480     batch_rename_sort_lists_for_rename (&dialog->selection, &new_names, NULL, NULL, NULL, FALSE);
481 
482     /* do the actual rename here */
483     nautilus_file_batch_rename (dialog->selection, new_names, NULL, NULL);
484 
485     gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (dialog->window)), NULL);
486 }
487 
488 static void
listbox_header_func(GtkListBoxRow * row,GtkListBoxRow * before,NautilusBatchRenameDialog * dialog)489 listbox_header_func (GtkListBoxRow             *row,
490                      GtkListBoxRow             *before,
491                      NautilusBatchRenameDialog *dialog)
492 {
493     gboolean show_separator;
494 
495     show_separator = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (row),
496                                                          "show-separator"));
497 
498     if (show_separator)
499     {
500         GtkWidget *separator;
501 
502         separator = gtk_separator_new (GTK_ORIENTATION_HORIZONTAL);
503         gtk_widget_show (separator);
504 
505         gtk_list_box_row_set_header (row, separator);
506     }
507 }
508 
509 /* This is manually done instead of using GtkSizeGroup because of the computational
510  * complexity of the later.*/
511 static void
update_rows_height(NautilusBatchRenameDialog * dialog)512 update_rows_height (NautilusBatchRenameDialog *dialog)
513 {
514     GList *l;
515     gint current_row_natural_height;
516     gint maximum_height;
517 
518     maximum_height = -1;
519 
520     /* check if maximum height has changed */
521     for (l = dialog->listbox_labels_new; l != NULL; l = l->next)
522     {
523         gtk_widget_get_preferred_height (GTK_WIDGET (l->data),
524                                          NULL,
525                                          &current_row_natural_height);
526 
527         if (current_row_natural_height > maximum_height)
528         {
529             maximum_height = current_row_natural_height;
530         }
531     }
532 
533     for (l = dialog->listbox_labels_old; l != NULL; l = l->next)
534     {
535         gtk_widget_get_preferred_height (GTK_WIDGET (l->data),
536                                          NULL,
537                                          &current_row_natural_height);
538 
539         if (current_row_natural_height > maximum_height)
540         {
541             maximum_height = current_row_natural_height;
542         }
543     }
544 
545     for (l = dialog->listbox_icons; l != NULL; l = l->next)
546     {
547         gtk_widget_get_preferred_height (GTK_WIDGET (l->data),
548                                          NULL,
549                                          &current_row_natural_height);
550 
551         if (current_row_natural_height > maximum_height)
552         {
553             maximum_height = current_row_natural_height;
554         }
555     }
556 
557     if (maximum_height != dialog->row_height)
558     {
559         dialog->row_height = maximum_height + ROW_MARGIN_TOP_BOTTOM * 2;
560 
561         for (l = dialog->listbox_icons; l != NULL; l = l->next)
562         {
563             g_object_set (G_OBJECT (l->data), "height-request", dialog->row_height, NULL);
564         }
565 
566         for (l = dialog->listbox_labels_new; l != NULL; l = l->next)
567         {
568             g_object_set (G_OBJECT (l->data), "height-request", dialog->row_height, NULL);
569         }
570 
571         for (l = dialog->listbox_labels_old; l != NULL; l = l->next)
572         {
573             g_object_set (G_OBJECT (l->data), "height-request", dialog->row_height, NULL);
574         }
575     }
576 }
577 
578 static GtkWidget *
create_original_name_row_for_label(NautilusBatchRenameDialog * dialog,const gchar * old_text,gboolean show_separator)579 create_original_name_row_for_label (NautilusBatchRenameDialog *dialog,
580                                     const gchar               *old_text,
581                                     gboolean                   show_separator)
582 {
583     GtkWidget *row;
584     GtkWidget *label_old;
585 
586     row = gtk_list_box_row_new ();
587 
588     g_object_set_data (G_OBJECT (row), "show-separator", GINT_TO_POINTER (show_separator));
589 
590     label_old = gtk_label_new (old_text);
591     gtk_label_set_xalign (GTK_LABEL (label_old), 0.0);
592     gtk_widget_set_hexpand (label_old, TRUE);
593     gtk_widget_set_margin_start (label_old, ROW_MARGIN_START);
594     gtk_label_set_ellipsize (GTK_LABEL (label_old), PANGO_ELLIPSIZE_END);
595 
596     dialog->listbox_labels_old = g_list_prepend (dialog->listbox_labels_old, label_old);
597 
598     gtk_container_add (GTK_CONTAINER (row), label_old);
599     gtk_widget_show_all (row);
600 
601     return row;
602 }
603 
604 static GtkWidget *
create_result_row_for_label(NautilusBatchRenameDialog * dialog,const gchar * new_text,gboolean show_separator)605 create_result_row_for_label (NautilusBatchRenameDialog *dialog,
606                              const gchar               *new_text,
607                              gboolean                   show_separator)
608 {
609     GtkWidget *row;
610     GtkWidget *label_new;
611 
612     row = gtk_list_box_row_new ();
613 
614     g_object_set_data (G_OBJECT (row), "show-separator", GINT_TO_POINTER (show_separator));
615 
616     label_new = gtk_label_new (new_text);
617     gtk_label_set_xalign (GTK_LABEL (label_new), 0.0);
618     gtk_widget_set_hexpand (label_new, TRUE);
619     gtk_widget_set_margin_start (label_new, ROW_MARGIN_START);
620     gtk_label_set_ellipsize (GTK_LABEL (label_new), PANGO_ELLIPSIZE_END);
621 
622     dialog->listbox_labels_new = g_list_prepend (dialog->listbox_labels_new, label_new);
623 
624     gtk_container_add (GTK_CONTAINER (row), label_new);
625     gtk_widget_show_all (row);
626 
627     return row;
628 }
629 
630 static GtkWidget *
create_arrow_row_for_label(NautilusBatchRenameDialog * dialog,gboolean show_separator)631 create_arrow_row_for_label (NautilusBatchRenameDialog *dialog,
632                             gboolean                   show_separator)
633 {
634     GtkWidget *row;
635     GtkWidget *icon;
636 
637     row = gtk_list_box_row_new ();
638 
639     g_object_set_data (G_OBJECT (row), "show-separator", GINT_TO_POINTER (show_separator));
640 
641     if (gtk_widget_get_direction (row) == GTK_TEXT_DIR_RTL)
642     {
643         icon = gtk_label_new ("←");
644     }
645     else
646     {
647         icon = gtk_label_new ("→");
648     }
649 
650     gtk_label_set_xalign (GTK_LABEL (icon), 1.0);
651     gtk_widget_set_hexpand (icon, FALSE);
652     gtk_widget_set_margin_start (icon, ROW_MARGIN_START);
653 
654     dialog->listbox_icons = g_list_prepend (dialog->listbox_icons, icon);
655 
656     gtk_container_add (GTK_CONTAINER (row), icon);
657     gtk_widget_show_all (row);
658 
659     return row;
660 }
661 
662 static void
prepare_batch_rename(NautilusBatchRenameDialog * dialog)663 prepare_batch_rename (NautilusBatchRenameDialog *dialog)
664 {
665     GdkCursor *cursor;
666     GdkDisplay *display;
667 
668     /* wait for checking conflicts to finish, to be sure that
669      * the rename can actually take place */
670     if (dialog->directories_pending_conflict_check != NULL)
671     {
672         dialog->rename_clicked = TRUE;
673         return;
674     }
675 
676     if (!gtk_widget_is_sensitive (dialog->rename_button))
677     {
678         return;
679     }
680 
681     display = gtk_widget_get_display (GTK_WIDGET (dialog->window));
682     cursor = gdk_cursor_new_from_name (display, "progress");
683     gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (dialog->window)),
684                            cursor);
685     g_object_unref (cursor);
686 
687     display = gtk_widget_get_display (GTK_WIDGET (dialog));
688     cursor = gdk_cursor_new_from_name (display, "progress");
689     gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (dialog)),
690                            cursor);
691     g_object_unref (cursor);
692 
693     gtk_widget_hide (GTK_WIDGET (dialog));
694     begin_batch_rename (dialog, dialog->new_names);
695 
696     gtk_widget_destroy (GTK_WIDGET (dialog));
697 }
698 
699 static void
batch_rename_dialog_on_response(NautilusBatchRenameDialog * dialog,gint response_id,gpointer user_data)700 batch_rename_dialog_on_response (NautilusBatchRenameDialog *dialog,
701                                  gint                       response_id,
702                                  gpointer                   user_data)
703 {
704     if (response_id == GTK_RESPONSE_OK)
705     {
706         prepare_batch_rename (dialog);
707     }
708     else
709     {
710         if (dialog->directories_pending_conflict_check != NULL)
711         {
712             cancel_conflict_check (dialog);
713         }
714 
715         gtk_widget_destroy (GTK_WIDGET (dialog));
716     }
717 }
718 
719 static void
fill_display_listbox(NautilusBatchRenameDialog * dialog)720 fill_display_listbox (NautilusBatchRenameDialog *dialog)
721 {
722     GtkWidget *row;
723     GList *l1;
724     GList *l2;
725     NautilusFile *file;
726     GString *new_name;
727     gchar *name;
728 
729     dialog->original_name_listbox_rows = NULL;
730     dialog->arrow_listbox_rows = NULL;
731     dialog->result_listbox_rows = NULL;
732 
733     gtk_size_group_add_widget (dialog->size_group, dialog->result_listbox);
734     gtk_size_group_add_widget (dialog->size_group, dialog->original_name_listbox);
735 
736     for (l1 = dialog->new_names, l2 = dialog->selection; l1 != NULL && l2 != NULL; l1 = l1->next, l2 = l2->next)
737     {
738         file = NAUTILUS_FILE (l2->data);
739         new_name = l1->data;
740 
741         name = nautilus_file_get_name (file);
742         row = create_original_name_row_for_label (dialog, name, TRUE);
743         gtk_container_add (GTK_CONTAINER (dialog->original_name_listbox), row);
744         dialog->original_name_listbox_rows = g_list_prepend (dialog->original_name_listbox_rows,
745                                                              row);
746 
747         row = create_arrow_row_for_label (dialog, TRUE);
748         gtk_container_add (GTK_CONTAINER (dialog->arrow_listbox), row);
749         dialog->arrow_listbox_rows = g_list_prepend (dialog->arrow_listbox_rows,
750                                                      row);
751 
752         row = create_result_row_for_label (dialog, new_name->str, TRUE);
753         gtk_container_add (GTK_CONTAINER (dialog->result_listbox), row);
754         dialog->result_listbox_rows = g_list_prepend (dialog->result_listbox_rows,
755                                                       row);
756 
757         g_free (name);
758     }
759 
760     dialog->original_name_listbox_rows = g_list_reverse (dialog->original_name_listbox_rows);
761     dialog->arrow_listbox_rows = g_list_reverse (dialog->arrow_listbox_rows);
762     dialog->result_listbox_rows = g_list_reverse (dialog->result_listbox_rows);
763     dialog->listbox_labels_old = g_list_reverse (dialog->listbox_labels_old);
764     dialog->listbox_labels_new = g_list_reverse (dialog->listbox_labels_new);
765     dialog->listbox_icons = g_list_reverse (dialog->listbox_icons);
766 }
767 
768 static void
select_nth_conflict(NautilusBatchRenameDialog * dialog)769 select_nth_conflict (NautilusBatchRenameDialog *dialog)
770 {
771     GList *l;
772     GString *conflict_file_name;
773     GString *display_text;
774     GString *new_name;
775     gint nth_conflict_index;
776     gint nth_conflict;
777     gint name_occurences;
778     GtkAdjustment *adjustment;
779     GtkAllocation allocation;
780     ConflictData *conflict_data;
781 
782     nth_conflict = dialog->selected_conflict;
783     l = g_list_nth (dialog->duplicates, nth_conflict);
784     conflict_data = l->data;
785 
786     /* the conflict that has to be selected */
787     conflict_file_name = g_string_new (conflict_data->name);
788     display_text = g_string_new ("");
789 
790     nth_conflict_index = conflict_data->index;
791 
792     l = g_list_nth (dialog->original_name_listbox_rows, nth_conflict_index);
793     gtk_list_box_select_row (GTK_LIST_BOX (dialog->original_name_listbox),
794                              l->data);
795 
796     l = g_list_nth (dialog->arrow_listbox_rows, nth_conflict_index);
797     gtk_list_box_select_row (GTK_LIST_BOX (dialog->arrow_listbox),
798                              l->data);
799 
800     l = g_list_nth (dialog->result_listbox_rows, nth_conflict_index);
801     gtk_list_box_select_row (GTK_LIST_BOX (dialog->result_listbox),
802                              l->data);
803 
804     /* scroll to the selected row */
805     adjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (dialog->scrolled_window));
806     gtk_widget_get_allocation (GTK_WIDGET (l->data), &allocation);
807     gtk_adjustment_set_value (adjustment, (allocation.height + 1) * nth_conflict_index);
808 
809     name_occurences = 0;
810     for (l = dialog->new_names; l != NULL; l = l->next)
811     {
812         new_name = l->data;
813         if (g_string_equal (new_name, conflict_file_name))
814         {
815             name_occurences++;
816         }
817     }
818     if (name_occurences > 1)
819     {
820         g_string_append_printf (display_text,
821                                 _("“%s” would not be a unique new name."),
822                                 conflict_file_name->str);
823     }
824     else
825     {
826         g_string_append_printf (display_text,
827                                 _("“%s” would conflict with an existing file."),
828                                 conflict_file_name->str);
829     }
830 
831     gtk_label_set_label (GTK_LABEL (dialog->conflict_label),
832                          display_text->str);
833 
834     g_string_free (conflict_file_name, TRUE);
835     g_string_free (display_text, TRUE);
836 }
837 
838 static void
select_next_conflict_down(NautilusBatchRenameDialog * dialog)839 select_next_conflict_down (NautilusBatchRenameDialog *dialog)
840 {
841     dialog->selected_conflict++;
842 
843     if (dialog->selected_conflict == 1)
844     {
845         gtk_widget_set_sensitive (dialog->conflict_up, TRUE);
846     }
847 
848     if (dialog->selected_conflict == dialog->conflicts_number - 1)
849     {
850         gtk_widget_set_sensitive (dialog->conflict_down, FALSE);
851     }
852 
853     select_nth_conflict (dialog);
854 }
855 
856 static void
select_next_conflict_up(NautilusBatchRenameDialog * dialog)857 select_next_conflict_up (NautilusBatchRenameDialog *dialog)
858 {
859     dialog->selected_conflict--;
860 
861     if (dialog->selected_conflict == 0)
862     {
863         gtk_widget_set_sensitive (dialog->conflict_up, FALSE);
864     }
865 
866     if (dialog->selected_conflict == dialog->conflicts_number - 2)
867     {
868         gtk_widget_set_sensitive (dialog->conflict_down, TRUE);
869     }
870 
871     select_nth_conflict (dialog);
872 }
873 
874 static void
update_conflict_row_background(NautilusBatchRenameDialog * dialog)875 update_conflict_row_background (NautilusBatchRenameDialog *dialog)
876 {
877     GList *l1;
878     GList *l2;
879     GList *l3;
880     GList *duplicates;
881     gint index;
882     GtkStyleContext *context;
883     ConflictData *conflict_data;
884 
885     index = 0;
886 
887     duplicates = dialog->duplicates;
888 
889     for (l1 = dialog->original_name_listbox_rows,
890          l2 = dialog->arrow_listbox_rows,
891          l3 = dialog->result_listbox_rows;
892          l1 != NULL && l2 != NULL && l3 != NULL;
893          l1 = l1->next, l2 = l2->next, l3 = l3->next)
894     {
895         context = gtk_widget_get_style_context (GTK_WIDGET (l1->data));
896 
897         if (gtk_style_context_has_class (context, "conflict-row"))
898         {
899             gtk_style_context_remove_class (context, "conflict-row");
900 
901             context = gtk_widget_get_style_context (GTK_WIDGET (l2->data));
902             gtk_style_context_remove_class (context, "conflict-row");
903 
904             context = gtk_widget_get_style_context (GTK_WIDGET (l3->data));
905             gtk_style_context_remove_class (context, "conflict-row");
906         }
907 
908         if (duplicates != NULL)
909         {
910             conflict_data = duplicates->data;
911             if (conflict_data->index == index)
912             {
913                 context = gtk_widget_get_style_context (GTK_WIDGET (l1->data));
914                 gtk_style_context_add_class (context, "conflict-row");
915 
916                 context = gtk_widget_get_style_context (GTK_WIDGET (l2->data));
917                 gtk_style_context_add_class (context, "conflict-row");
918 
919                 context = gtk_widget_get_style_context (GTK_WIDGET (l3->data));
920                 gtk_style_context_add_class (context, "conflict-row");
921 
922                 duplicates = duplicates->next;
923             }
924         }
925         index++;
926     }
927 }
928 
929 static void
update_listbox(NautilusBatchRenameDialog * dialog)930 update_listbox (NautilusBatchRenameDialog *dialog)
931 {
932     GList *l1;
933     GList *l2;
934     NautilusFile *file;
935     gchar *old_name;
936     GtkLabel *label;
937     GString *new_name;
938     gboolean empty_name = FALSE;
939 
940     for (l1 = dialog->new_names, l2 = dialog->listbox_labels_new; l1 != NULL && l2 != NULL; l1 = l1->next, l2 = l2->next)
941     {
942         label = GTK_LABEL (l2->data);
943         new_name = l1->data;
944 
945         gtk_label_set_label (label, new_name->str);
946         gtk_widget_set_tooltip_text (GTK_WIDGET (label), new_name->str);
947 
948         if (g_strcmp0 (new_name->str, "") == 0)
949         {
950             empty_name = TRUE;
951         }
952     }
953 
954     for (l1 = dialog->selection, l2 = dialog->listbox_labels_old; l1 != NULL && l2 != NULL; l1 = l1->next, l2 = l2->next)
955     {
956         label = GTK_LABEL (l2->data);
957         file = NAUTILUS_FILE (l1->data);
958 
959         old_name = nautilus_file_get_name (file);
960         gtk_widget_set_tooltip_text (GTK_WIDGET (label), old_name);
961 
962         if (dialog->mode == NAUTILUS_BATCH_RENAME_DIALOG_FORMAT)
963         {
964             gtk_label_set_label (label, old_name);
965         }
966         else
967         {
968             new_name = batch_rename_replace_label_text (old_name,
969                                                         gtk_entry_get_text (GTK_ENTRY (dialog->find_entry)));
970             gtk_label_set_markup (GTK_LABEL (label), new_name->str);
971 
972             g_string_free (new_name, TRUE);
973         }
974 
975         g_free (old_name);
976     }
977 
978     update_rows_height (dialog);
979 
980     if (empty_name)
981     {
982         gtk_widget_set_sensitive (dialog->rename_button, FALSE);
983 
984         return;
985     }
986 
987     /* check if there are name conflicts and display them if they exist */
988     if (dialog->duplicates != NULL)
989     {
990         update_conflict_row_background (dialog);
991 
992         gtk_widget_set_sensitive (dialog->rename_button, FALSE);
993 
994         gtk_widget_show (dialog->conflict_box);
995 
996         dialog->selected_conflict = 0;
997         dialog->conflicts_number = g_list_length (dialog->duplicates);
998 
999         select_nth_conflict (dialog);
1000 
1001         gtk_widget_set_sensitive (dialog->conflict_up, FALSE);
1002 
1003         if (g_list_length (dialog->duplicates) == 1)
1004         {
1005             gtk_widget_set_sensitive (dialog->conflict_down, FALSE);
1006         }
1007         else
1008         {
1009             gtk_widget_set_sensitive (dialog->conflict_down, TRUE);
1010         }
1011     }
1012     else
1013     {
1014         gtk_widget_hide (dialog->conflict_box);
1015 
1016         /* re-enable the rename button if there are no more name conflicts */
1017         if (dialog->duplicates == NULL && !gtk_widget_is_sensitive (dialog->rename_button))
1018         {
1019             update_conflict_row_background (dialog);
1020             gtk_widget_set_sensitive (dialog->rename_button, TRUE);
1021         }
1022     }
1023 
1024     /* if the rename button was clicked and there's no conflict, then start renaming */
1025     if (dialog->rename_clicked && dialog->duplicates == NULL)
1026     {
1027         prepare_batch_rename (dialog);
1028     }
1029 
1030     if (dialog->rename_clicked && dialog->duplicates != NULL)
1031     {
1032         dialog->rename_clicked = FALSE;
1033     }
1034 }
1035 
1036 static void
check_conflict_for_files(NautilusBatchRenameDialog * dialog,NautilusDirectory * directory,GList * files)1037 check_conflict_for_files (NautilusBatchRenameDialog *dialog,
1038                           NautilusDirectory         *directory,
1039                           GList                     *files)
1040 {
1041     gchar *current_directory;
1042     gchar *parent_uri;
1043     gchar *name;
1044     NautilusFile *file;
1045     GString *new_name;
1046     GString *file_name;
1047     GList *l1, *l2;
1048     GHashTable *directory_files_table;
1049     GHashTable *new_names_table;
1050     GHashTable *names_conflicts_table;
1051     gboolean exists;
1052     gboolean have_conflict;
1053     gboolean tag_present;
1054     gboolean same_parent_directory;
1055     ConflictData *conflict_data;
1056 
1057     current_directory = nautilus_directory_get_uri (directory);
1058 
1059     directory_files_table = g_hash_table_new_full (g_str_hash,
1060                                                    g_str_equal,
1061                                                    (GDestroyNotify) g_free,
1062                                                    NULL);
1063     new_names_table = g_hash_table_new_full (g_str_hash,
1064                                              g_str_equal,
1065                                              (GDestroyNotify) g_free,
1066                                              (GDestroyNotify) g_free);
1067     names_conflicts_table = g_hash_table_new_full (g_str_hash,
1068                                                    g_str_equal,
1069                                                    (GDestroyNotify) g_free,
1070                                                    (GDestroyNotify) g_free);
1071 
1072     /* names_conflicts_table is used for knowing which names from the list are not unique,
1073      * so that they can easily be reached when needed */
1074     for (l1 = dialog->new_names, l2 = dialog->selection;
1075          l1 != NULL && l2 != NULL;
1076          l1 = l1->next, l2 = l2->next)
1077     {
1078         new_name = l1->data;
1079         file = NAUTILUS_FILE (l2->data);
1080         parent_uri = nautilus_file_get_parent_uri (file);
1081 
1082         tag_present = g_hash_table_lookup (new_names_table, new_name->str) != NULL;
1083         same_parent_directory = g_strcmp0 (parent_uri, current_directory) == 0;
1084 
1085         if (same_parent_directory)
1086         {
1087             if (!tag_present)
1088             {
1089                 g_hash_table_insert (new_names_table,
1090                                      g_strdup (new_name->str),
1091                                      nautilus_file_get_parent_uri (file));
1092             }
1093             else
1094             {
1095                 g_hash_table_insert (names_conflicts_table,
1096                                      g_strdup (new_name->str),
1097                                      nautilus_file_get_parent_uri (file));
1098             }
1099         }
1100 
1101         g_free (parent_uri);
1102     }
1103 
1104     for (l1 = files; l1 != NULL; l1 = l1->next)
1105     {
1106         file = NAUTILUS_FILE (l1->data);
1107         g_hash_table_insert (directory_files_table,
1108                              nautilus_file_get_name (file),
1109                              GINT_TO_POINTER (TRUE));
1110     }
1111 
1112     for (l1 = dialog->selection, l2 = dialog->new_names; l1 != NULL && l2 != NULL; l1 = l1->next, l2 = l2->next)
1113     {
1114         file = NAUTILUS_FILE (l1->data);
1115 
1116         name = nautilus_file_get_name (file);
1117         file_name = g_string_new (name);
1118         g_free (name);
1119 
1120         parent_uri = nautilus_file_get_parent_uri (file);
1121 
1122         new_name = l2->data;
1123 
1124         have_conflict = FALSE;
1125 
1126         /* check for duplicate only if the parent of the current file is
1127          * the current directory and the name of the file has changed */
1128         if (g_strcmp0 (parent_uri, current_directory) == 0 &&
1129             !g_string_equal (new_name, file_name))
1130         {
1131             exists = GPOINTER_TO_INT (g_hash_table_lookup (directory_files_table, new_name->str));
1132 
1133             if (exists == TRUE &&
1134                 !file_name_conflicts_with_results (dialog->selection, dialog->new_names, new_name, parent_uri))
1135             {
1136                 conflict_data = g_new (ConflictData, 1);
1137                 conflict_data->name = g_strdup (new_name->str);
1138                 conflict_data->index = g_list_index (dialog->selection, l1->data);
1139                 dialog->duplicates = g_list_prepend (dialog->duplicates,
1140                                                      conflict_data);
1141 
1142                 have_conflict = TRUE;
1143             }
1144         }
1145 
1146         if (!have_conflict)
1147         {
1148             tag_present = g_hash_table_lookup (names_conflicts_table, new_name->str) != NULL;
1149             same_parent_directory = g_strcmp0 (parent_uri, current_directory) == 0;
1150 
1151             if (tag_present && same_parent_directory)
1152             {
1153                 conflict_data = g_new (ConflictData, 1);
1154                 conflict_data->name = g_strdup (new_name->str);
1155                 conflict_data->index = g_list_index (dialog->selection, l1->data);
1156                 dialog->duplicates = g_list_prepend (dialog->duplicates,
1157                                                      conflict_data);
1158 
1159                 have_conflict = TRUE;
1160             }
1161         }
1162 
1163         g_string_free (file_name, TRUE);
1164         g_free (parent_uri);
1165     }
1166 
1167     g_free (current_directory);
1168     g_hash_table_destroy (directory_files_table);
1169     g_hash_table_destroy (new_names_table);
1170     g_hash_table_destroy (names_conflicts_table);
1171 }
1172 
1173 static void
on_directory_attributes_ready_for_conflicts_check(NautilusDirectory * conflict_directory,GList * files,gpointer callback_data)1174 on_directory_attributes_ready_for_conflicts_check (NautilusDirectory *conflict_directory,
1175                                                    GList             *files,
1176                                                    gpointer           callback_data)
1177 {
1178     NautilusBatchRenameDialog *self;
1179 
1180     self = NAUTILUS_BATCH_RENAME_DIALOG (callback_data);
1181 
1182     check_conflict_for_files (self, conflict_directory, files);
1183 
1184     g_assert (g_list_find (self->directories_pending_conflict_check, conflict_directory) != NULL);
1185 
1186     self->directories_pending_conflict_check = g_list_remove (self->directories_pending_conflict_check, conflict_directory);
1187 
1188     nautilus_directory_unref (conflict_directory);
1189 
1190     if (self->directories_pending_conflict_check == NULL)
1191     {
1192         self->duplicates = g_list_reverse (self->duplicates);
1193 
1194         update_listbox (self);
1195     }
1196 }
1197 
1198 static void
cancel_conflict_check(NautilusBatchRenameDialog * self)1199 cancel_conflict_check (NautilusBatchRenameDialog *self)
1200 {
1201     GList *l;
1202     NautilusDirectory *directory;
1203 
1204     for (l = self->directories_pending_conflict_check; l != NULL; l = l->next)
1205     {
1206         directory = l->data;
1207 
1208         nautilus_directory_cancel_callback (directory,
1209                                             on_directory_attributes_ready_for_conflicts_check,
1210                                             self);
1211     }
1212 
1213     g_clear_list (&self->directories_pending_conflict_check, g_object_unref);
1214 }
1215 
1216 static void
file_names_list_has_duplicates_async(NautilusBatchRenameDialog * self)1217 file_names_list_has_duplicates_async (NautilusBatchRenameDialog *self)
1218 {
1219     GList *l;
1220 
1221     if (self->directories_pending_conflict_check != NULL)
1222     {
1223         cancel_conflict_check (self);
1224     }
1225 
1226     self->directories_pending_conflict_check = nautilus_directory_list_copy (self->distinct_parent_directories);
1227     self->duplicates = NULL;
1228 
1229     for (l = self->distinct_parent_directories; l != NULL; l = l->next)
1230     {
1231         nautilus_directory_call_when_ready (l->data,
1232                                             NAUTILUS_FILE_ATTRIBUTE_INFO,
1233                                             TRUE,
1234                                             on_directory_attributes_ready_for_conflicts_check,
1235                                             self);
1236     }
1237 }
1238 
1239 static gboolean
have_unallowed_character(NautilusBatchRenameDialog * dialog)1240 have_unallowed_character (NautilusBatchRenameDialog *dialog)
1241 {
1242     GList *names;
1243     GString *new_name;
1244     const gchar *entry_text;
1245     gboolean have_empty_name;
1246     gboolean have_unallowed_character_slash;
1247     gboolean have_unallowed_character_dot;
1248     gboolean have_unallowed_character_dotdot;
1249 
1250     have_empty_name = FALSE;
1251     have_unallowed_character_slash = FALSE;
1252     have_unallowed_character_dot = FALSE;
1253     have_unallowed_character_dotdot = FALSE;
1254 
1255     if (dialog->mode == NAUTILUS_BATCH_RENAME_DIALOG_FORMAT)
1256     {
1257         entry_text = gtk_entry_get_text (GTK_ENTRY (dialog->name_entry));
1258     }
1259     else
1260     {
1261         entry_text = gtk_entry_get_text (GTK_ENTRY (dialog->replace_entry));
1262     }
1263 
1264     if (strstr (entry_text, "/") != NULL)
1265     {
1266         have_unallowed_character_slash = TRUE;
1267     }
1268 
1269     if (dialog->mode == NAUTILUS_BATCH_RENAME_DIALOG_FORMAT && g_strcmp0 (entry_text, ".") == 0)
1270     {
1271         have_unallowed_character_dot = TRUE;
1272     }
1273     else if (dialog->mode == NAUTILUS_BATCH_RENAME_DIALOG_REPLACE)
1274     {
1275         for (names = dialog->new_names; names != NULL; names = names->next)
1276         {
1277             new_name = names->data;
1278 
1279             if (g_strcmp0 (new_name->str, ".") == 0)
1280             {
1281                 have_unallowed_character_dot = TRUE;
1282                 break;
1283             }
1284         }
1285     }
1286 
1287     if (dialog->mode == NAUTILUS_BATCH_RENAME_DIALOG_FORMAT && g_strcmp0 (entry_text, "..") == 0)
1288     {
1289         have_unallowed_character_dotdot = TRUE;
1290     }
1291     else if (dialog->mode == NAUTILUS_BATCH_RENAME_DIALOG_REPLACE)
1292     {
1293         for (names = dialog->new_names; names != NULL; names = names->next)
1294         {
1295             new_name = names->data;
1296 
1297             if (g_strcmp0 (new_name->str, "") == 0)
1298             {
1299                 have_empty_name = TRUE;
1300                 break;
1301             }
1302 
1303             if (g_strcmp0 (new_name->str, "..") == 0)
1304             {
1305                 have_unallowed_character_dotdot = TRUE;
1306                 break;
1307             }
1308         }
1309     }
1310 
1311     if (have_empty_name)
1312     {
1313         gtk_label_set_label (GTK_LABEL (dialog->conflict_label),
1314                              _("Name cannot be empty."));
1315     }
1316 
1317     if (have_unallowed_character_slash)
1318     {
1319         gtk_label_set_label (GTK_LABEL (dialog->conflict_label),
1320                              _("Name cannot contain “/”."));
1321     }
1322 
1323     if (have_unallowed_character_dot)
1324     {
1325         gtk_label_set_label (GTK_LABEL (dialog->conflict_label),
1326                              _("“.” is not a valid name."));
1327     }
1328 
1329     if (have_unallowed_character_dotdot)
1330     {
1331         gtk_label_set_label (GTK_LABEL (dialog->conflict_label),
1332                              _("“..” is not a valid name."));
1333     }
1334 
1335     if (have_unallowed_character_slash || have_unallowed_character_dot || have_unallowed_character_dotdot
1336         || have_empty_name)
1337     {
1338         gtk_widget_set_sensitive (dialog->rename_button, FALSE);
1339         gtk_widget_set_sensitive (dialog->conflict_down, FALSE);
1340         gtk_widget_set_sensitive (dialog->conflict_up, FALSE);
1341 
1342         gtk_widget_show (dialog->conflict_box);
1343 
1344         return TRUE;
1345     }
1346     else
1347     {
1348         gtk_widget_hide (dialog->conflict_box);
1349 
1350         return FALSE;
1351     }
1352 }
1353 
1354 static gboolean
numbering_tag_is_some_added(NautilusBatchRenameDialog * self)1355 numbering_tag_is_some_added (NautilusBatchRenameDialog *self)
1356 {
1357     guint i;
1358     TagData *tag_data;
1359 
1360     for (i = 0; i < G_N_ELEMENTS (numbering_tags_constants); i++)
1361     {
1362         g_autofree gchar *tag_text_representation = NULL;
1363 
1364         tag_text_representation = batch_rename_get_tag_text_representation (numbering_tags_constants[i]);
1365         tag_data = g_hash_table_lookup (self->tag_info_table, tag_text_representation);
1366         if (tag_data->set)
1367         {
1368             return TRUE;
1369         }
1370     }
1371 
1372     return FALSE;
1373 }
1374 
1375 static void
update_display_text(NautilusBatchRenameDialog * dialog)1376 update_display_text (NautilusBatchRenameDialog *dialog)
1377 {
1378     if (dialog->selection == NULL)
1379     {
1380         return;
1381     }
1382 
1383     if (dialog->duplicates != NULL)
1384     {
1385         g_list_free_full (dialog->duplicates, conflict_data_free);
1386         dialog->duplicates = NULL;
1387     }
1388 
1389     if (dialog->new_names != NULL)
1390     {
1391         g_list_free_full (dialog->new_names, string_free);
1392     }
1393 
1394     if (!numbering_tag_is_some_added (dialog))
1395     {
1396         gtk_revealer_set_reveal_child (GTK_REVEALER (dialog->numbering_revealer), FALSE);
1397     }
1398     else
1399     {
1400         gtk_revealer_set_reveal_child (GTK_REVEALER (dialog->numbering_revealer), TRUE);
1401     }
1402 
1403     dialog->new_names = batch_rename_dialog_get_new_names (dialog);
1404 
1405     if (have_unallowed_character (dialog))
1406     {
1407         return;
1408     }
1409 
1410     file_names_list_has_duplicates_async (dialog);
1411 }
1412 
1413 static void
batch_rename_dialog_mode_changed(NautilusBatchRenameDialog * dialog)1414 batch_rename_dialog_mode_changed (NautilusBatchRenameDialog *dialog)
1415 {
1416     if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (dialog->format_mode_button)))
1417     {
1418         gtk_stack_set_visible_child_name (GTK_STACK (dialog->mode_stack), "format");
1419 
1420         dialog->mode = NAUTILUS_BATCH_RENAME_DIALOG_FORMAT;
1421 
1422         gtk_entry_grab_focus_without_selecting (GTK_ENTRY (dialog->name_entry));
1423     }
1424     else
1425     {
1426         gtk_stack_set_visible_child_name (GTK_STACK (dialog->mode_stack), "replace");
1427 
1428         dialog->mode = NAUTILUS_BATCH_RENAME_DIALOG_REPLACE;
1429 
1430         gtk_entry_grab_focus_without_selecting (GTK_ENTRY (dialog->find_entry));
1431     }
1432 
1433     update_display_text (dialog);
1434 }
1435 
1436 static void
add_button_clicked(NautilusBatchRenameDialog * dialog)1437 add_button_clicked (NautilusBatchRenameDialog *dialog)
1438 {
1439     if (gtk_widget_is_visible (dialog->add_popover))
1440     {
1441         gtk_widget_set_visible (dialog->add_popover, FALSE);
1442     }
1443     else
1444     {
1445         gtk_widget_set_visible (dialog->add_popover, TRUE);
1446     }
1447 }
1448 
1449 static void
add_popover_closed(NautilusBatchRenameDialog * dialog)1450 add_popover_closed (NautilusBatchRenameDialog *dialog)
1451 {
1452     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->add_button), FALSE);
1453 }
1454 
1455 static void
numbering_order_button_clicked(NautilusBatchRenameDialog * dialog)1456 numbering_order_button_clicked (NautilusBatchRenameDialog *dialog)
1457 {
1458     if (gtk_widget_is_visible (dialog->numbering_order_popover))
1459     {
1460         gtk_widget_set_visible (dialog->numbering_order_popover, FALSE);
1461     }
1462     else
1463     {
1464         gtk_widget_set_visible (dialog->numbering_order_popover, TRUE);
1465     }
1466 }
1467 
1468 static void
numbering_order_popover_closed(NautilusBatchRenameDialog * dialog)1469 numbering_order_popover_closed (NautilusBatchRenameDialog *dialog)
1470 {
1471     gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->numbering_order_button), FALSE);
1472 }
1473 
1474 void
nautilus_batch_rename_dialog_query_finished(NautilusBatchRenameDialog * dialog,GHashTable * hash_table,GList * selection_metadata)1475 nautilus_batch_rename_dialog_query_finished (NautilusBatchRenameDialog *dialog,
1476                                              GHashTable                *hash_table,
1477                                              GList                     *selection_metadata)
1478 {
1479     GMenuItem *first_created;
1480     GMenuItem *last_created;
1481     FileMetadata *file_metadata;
1482     MetadataType metadata_type;
1483     gboolean is_metadata;
1484     TagData *tag_data;
1485     g_autoptr (GList) tag_info_keys = NULL;
1486     GList *l;
1487 
1488     /* for files with no metadata */
1489     if (hash_table != NULL && g_hash_table_size (hash_table) == 0)
1490     {
1491         g_hash_table_destroy (hash_table);
1492 
1493         hash_table = NULL;
1494     }
1495 
1496     if (hash_table == NULL)
1497     {
1498         dialog->create_date = NULL;
1499     }
1500     else
1501     {
1502         dialog->create_date = hash_table;
1503     }
1504 
1505     if (dialog->create_date != NULL)
1506     {
1507         first_created = g_menu_item_new ("First Created",
1508                                          "dialog.numbering-order-changed('first-created')");
1509 
1510         g_menu_append_item (dialog->numbering_order_menu, first_created);
1511 
1512         last_created = g_menu_item_new ("Last Created",
1513                                         "dialog.numbering-order-changed('last-created')");
1514 
1515         g_menu_append_item (dialog->numbering_order_menu, last_created);
1516     }
1517 
1518     dialog->selection_metadata = selection_metadata;
1519     file_metadata = selection_metadata->data;
1520     tag_info_keys = g_hash_table_get_keys (dialog->tag_info_table);
1521     for (l = tag_info_keys; l != NULL; l = l->next)
1522     {
1523         /* Only metadata has to be handled here. */
1524         tag_data = g_hash_table_lookup (dialog->tag_info_table, l->data);
1525         is_metadata = tag_data->tag_constants.is_metadata;
1526         if (!is_metadata)
1527         {
1528             continue;
1529         }
1530 
1531         metadata_type = tag_data->tag_constants.metadata_type;
1532         if (file_metadata->metadata[metadata_type] == NULL ||
1533             file_metadata->metadata[metadata_type]->len <= 0)
1534         {
1535             disable_action (dialog, tag_data->tag_constants.action_name);
1536             tag_data->available = FALSE;
1537         }
1538     }
1539 }
1540 
1541 static void
update_row_shadowing(GtkWidget * row,gboolean shown)1542 update_row_shadowing (GtkWidget *row,
1543                       gboolean   shown)
1544 {
1545     GtkStyleContext *context;
1546     GtkStateFlags flags;
1547 
1548     if (!GTK_IS_LIST_BOX_ROW (row))
1549     {
1550         return;
1551     }
1552 
1553     context = gtk_widget_get_style_context (row);
1554     flags = gtk_style_context_get_state (context);
1555 
1556     if (shown)
1557     {
1558         flags |= GTK_STATE_PRELIGHT;
1559     }
1560     else
1561     {
1562         flags &= ~GTK_STATE_PRELIGHT;
1563     }
1564 
1565     gtk_style_context_set_state (context, flags);
1566 }
1567 
1568 static gboolean
on_motion_notify(GtkWidget * widget,GdkEvent * event,gpointer user_data)1569 on_motion_notify (GtkWidget *widget,
1570                   GdkEvent  *event,
1571                   gpointer   user_data)
1572 {
1573     NautilusBatchRenameDialog *dialog;
1574     gdouble y;
1575     GtkListBoxRow *row;
1576 
1577     dialog = NAUTILUS_BATCH_RENAME_DIALOG (user_data);
1578 
1579     if (dialog->preselected_row1 && dialog->preselected_row2)
1580     {
1581         update_row_shadowing (dialog->preselected_row1, FALSE);
1582         update_row_shadowing (dialog->preselected_row2, FALSE);
1583     }
1584 
1585     if (G_UNLIKELY (!gdk_event_get_coords (event, NULL, &y)))
1586     {
1587         g_return_val_if_reached (GDK_EVENT_PROPAGATE);
1588     }
1589 
1590     if (widget == dialog->result_listbox)
1591     {
1592         row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (dialog->original_name_listbox), y);
1593         update_row_shadowing (GTK_WIDGET (row), TRUE);
1594         dialog->preselected_row1 = GTK_WIDGET (row);
1595 
1596         row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (dialog->arrow_listbox), y);
1597         update_row_shadowing (GTK_WIDGET (row), TRUE);
1598         dialog->preselected_row2 = GTK_WIDGET (row);
1599     }
1600 
1601     if (widget == dialog->arrow_listbox)
1602     {
1603         row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (dialog->original_name_listbox), y);
1604         update_row_shadowing (GTK_WIDGET (row), TRUE);
1605         dialog->preselected_row1 = GTK_WIDGET (row);
1606 
1607         row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (dialog->result_listbox), y);
1608         update_row_shadowing (GTK_WIDGET (row), TRUE);
1609         dialog->preselected_row2 = GTK_WIDGET (row);
1610     }
1611 
1612     if (widget == dialog->original_name_listbox)
1613     {
1614         row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (dialog->result_listbox), y);
1615         update_row_shadowing (GTK_WIDGET (row), TRUE);
1616         dialog->preselected_row1 = GTK_WIDGET (row);
1617 
1618         row = gtk_list_box_get_row_at_y (GTK_LIST_BOX (dialog->arrow_listbox), y);
1619         update_row_shadowing (GTK_WIDGET (row), TRUE);
1620         dialog->preselected_row2 = GTK_WIDGET (row);
1621     }
1622 
1623     return GDK_EVENT_PROPAGATE;
1624 }
1625 
1626 static gboolean
on_leave_notify(GtkWidget * widget,GdkEvent * event,gpointer user_data)1627 on_leave_notify (GtkWidget *widget,
1628                  GdkEvent  *event,
1629                  gpointer   user_data)
1630 {
1631     NautilusBatchRenameDialog *dialog;
1632 
1633     dialog = NAUTILUS_BATCH_RENAME_DIALOG (user_data);
1634 
1635     update_row_shadowing (dialog->preselected_row1, FALSE);
1636     update_row_shadowing (dialog->preselected_row2, FALSE);
1637 
1638     dialog->preselected_row1 = NULL;
1639     dialog->preselected_row2 = NULL;
1640 
1641     return GDK_EVENT_PROPAGATE;
1642 }
1643 
1644 static gboolean
on_event(GtkWidget * widget,GdkEvent * event,gpointer user_data)1645 on_event (GtkWidget *widget,
1646           GdkEvent  *event,
1647           gpointer   user_data)
1648 {
1649     GdkEventType event_type;
1650 
1651     event_type = gdk_event_get_event_type (event);
1652 
1653     if (event_type == GDK_MOTION_NOTIFY)
1654     {
1655         return on_motion_notify (widget, event, user_data);
1656     }
1657 
1658     if (event_type == GDK_LEAVE_NOTIFY)
1659     {
1660         return on_leave_notify (widget, event, user_data);
1661     }
1662 
1663     return GDK_EVENT_PROPAGATE;
1664 }
1665 
1666 static void
nautilus_batch_rename_dialog_initialize_actions(NautilusBatchRenameDialog * dialog)1667 nautilus_batch_rename_dialog_initialize_actions (NautilusBatchRenameDialog *dialog)
1668 {
1669     GAction *action;
1670 
1671     dialog->action_group = G_ACTION_GROUP (g_simple_action_group_new ());
1672 
1673     g_action_map_add_action_entries (G_ACTION_MAP (dialog->action_group),
1674                                      dialog_entries,
1675                                      G_N_ELEMENTS (dialog_entries),
1676                                      dialog);
1677     gtk_widget_insert_action_group (GTK_WIDGET (dialog),
1678                                     "dialog",
1679                                     G_ACTION_GROUP (dialog->action_group));
1680 
1681     action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group),
1682                                          metadata_tags_constants[ORIGINAL_FILE_NAME].action_name);
1683     g_simple_action_set_enabled (G_SIMPLE_ACTION (action), FALSE);
1684 
1685     check_metadata_for_selection (dialog, dialog->selection,
1686                                   dialog->metadata_cancellable);
1687 }
1688 
1689 static void
file_names_widget_on_activate(NautilusBatchRenameDialog * dialog)1690 file_names_widget_on_activate (NautilusBatchRenameDialog *dialog)
1691 {
1692     prepare_batch_rename (dialog);
1693 }
1694 
1695 static void
remove_tag(NautilusBatchRenameDialog * dialog,TagData * tag_data)1696 remove_tag (NautilusBatchRenameDialog *dialog,
1697             TagData                   *tag_data)
1698 {
1699     GAction *action;
1700 
1701     if (!tag_data->set)
1702     {
1703         g_warning ("Trying to remove an already removed tag");
1704 
1705         return;
1706     }
1707 
1708     tag_data->set = FALSE;
1709     tag_data->position = -1;
1710     action = g_action_map_lookup_action (G_ACTION_MAP (dialog->action_group),
1711                                          tag_data->tag_constants.action_name);
1712     g_simple_action_set_enabled (G_SIMPLE_ACTION (action), TRUE);
1713 }
1714 
1715 static gint
compare_tag_position(gconstpointer a,gconstpointer b)1716 compare_tag_position (gconstpointer a,
1717                       gconstpointer b)
1718 {
1719     const TagData *tag_data1 = a;
1720     const TagData *tag_data2 = b;
1721 
1722     return tag_data1->position - tag_data2->position;
1723 }
1724 
1725 typedef enum
1726 {
1727     TEXT_WAS_DELETED,
1728     TEXT_WAS_INSERTED
1729 } TextChangedMode;
1730 
1731 static GList *
get_tags_intersecting_sorted(NautilusBatchRenameDialog * self,gint start_position,gint end_position,TextChangedMode text_changed_mode)1732 get_tags_intersecting_sorted (NautilusBatchRenameDialog *self,
1733                               gint                       start_position,
1734                               gint                       end_position,
1735                               TextChangedMode            text_changed_mode)
1736 {
1737     g_autoptr (GList) tag_info_keys = NULL;
1738     TagData *tag_data;
1739     GList *l;
1740     GList *intersecting_tags = NULL;
1741     gint tag_end_position;
1742 
1743     tag_info_keys = g_hash_table_get_keys (self->tag_info_table);
1744     for (l = tag_info_keys; l != NULL; l = l->next)
1745     {
1746         g_autofree gchar *tag_text_representation = NULL;
1747 
1748         tag_data = g_hash_table_lookup (self->tag_info_table, l->data);
1749         tag_text_representation = batch_rename_get_tag_text_representation (tag_data->tag_constants);
1750         tag_end_position = tag_data->position + g_utf8_strlen (tag_text_representation, -1);
1751         if (tag_data->set && !tag_data->just_added)
1752         {
1753             gboolean selection_intersects_tag_start;
1754             gboolean selection_intersects_tag_end;
1755             gboolean tag_is_contained_in_selection;
1756 
1757             if (text_changed_mode == TEXT_WAS_DELETED)
1758             {
1759                 selection_intersects_tag_start = end_position > tag_data->position &&
1760                                                  end_position <= tag_end_position;
1761                 selection_intersects_tag_end = start_position >= tag_data->position &&
1762                                                start_position < tag_end_position;
1763                 tag_is_contained_in_selection = start_position <= tag_data->position &&
1764                                                 end_position >= tag_end_position;
1765             }
1766             else
1767             {
1768                 selection_intersects_tag_start = start_position > tag_data->position &&
1769                                                  start_position < tag_end_position;
1770                 selection_intersects_tag_end = FALSE;
1771                 tag_is_contained_in_selection = FALSE;
1772             }
1773             if (selection_intersects_tag_end || selection_intersects_tag_start || tag_is_contained_in_selection)
1774             {
1775                 intersecting_tags = g_list_prepend (intersecting_tags, tag_data);
1776             }
1777         }
1778     }
1779 
1780     return g_list_sort (intersecting_tags, compare_tag_position);
1781 }
1782 
1783 static void
update_tags_positions(NautilusBatchRenameDialog * self,gint start_position,gint end_position,TextChangedMode text_changed_mode)1784 update_tags_positions (NautilusBatchRenameDialog *self,
1785                        gint                       start_position,
1786                        gint                       end_position,
1787                        TextChangedMode            text_changed_mode)
1788 {
1789     g_autoptr (GList) tag_info_keys = NULL;
1790     TagData *tag_data;
1791     GList *l;
1792 
1793     tag_info_keys = g_hash_table_get_keys (self->tag_info_table);
1794     for (l = tag_info_keys; l != NULL; l = l->next)
1795     {
1796         tag_data = g_hash_table_lookup (self->tag_info_table, l->data);
1797         if (tag_data->set && !tag_data->just_added && tag_data->position >= start_position)
1798         {
1799             if (text_changed_mode == TEXT_WAS_DELETED)
1800             {
1801                 tag_data->position -= end_position - start_position;
1802             }
1803             else
1804             {
1805                 tag_data->position += end_position - start_position;
1806             }
1807         }
1808     }
1809 }
1810 
1811 static void
on_delete_text(GtkEditable * editable,gint start_position,gint end_position,gpointer user_data)1812 on_delete_text (GtkEditable *editable,
1813                 gint         start_position,
1814                 gint         end_position,
1815                 gpointer     user_data)
1816 {
1817     NautilusBatchRenameDialog *self;
1818     g_autoptr (GList) intersecting_tags = NULL;
1819     gint final_start_position;
1820     gint final_end_position;
1821     GList *l;
1822 
1823     self = NAUTILUS_BATCH_RENAME_DIALOG (user_data);
1824     intersecting_tags = get_tags_intersecting_sorted (self, start_position,
1825                                                       end_position, TEXT_WAS_DELETED);
1826     if (intersecting_tags)
1827     {
1828         gint last_tag_end_position;
1829         g_autofree gchar *tag_text_representation = NULL;
1830         TagData *first_tag = g_list_first (intersecting_tags)->data;
1831         TagData *last_tag = g_list_last (intersecting_tags)->data;
1832 
1833         tag_text_representation = batch_rename_get_tag_text_representation (last_tag->tag_constants);
1834         last_tag_end_position = last_tag->position +
1835                                 g_utf8_strlen (tag_text_representation, -1);
1836         final_start_position = MIN (start_position, first_tag->position);
1837         final_end_position = MAX (end_position, last_tag_end_position);
1838     }
1839     else
1840     {
1841         final_start_position = start_position;
1842         final_end_position = end_position;
1843     }
1844 
1845     g_signal_handlers_block_by_func (editable, (gpointer) on_delete_text, user_data);
1846     gtk_editable_delete_text (editable, final_start_position, final_end_position);
1847     g_signal_handlers_unblock_by_func (editable, (gpointer) on_delete_text, user_data);
1848 
1849     /* Mark the tags as removed */
1850     for (l = intersecting_tags; l != NULL; l = l->next)
1851     {
1852         remove_tag (self, l->data);
1853     }
1854 
1855     /* If we removed the numbering tag, we want to enable all numbering actions */
1856     if (!numbering_tag_is_some_added (self))
1857     {
1858         guint i;
1859 
1860         for (i = 0; i < G_N_ELEMENTS (numbering_tags_constants); i++)
1861         {
1862             enable_action (self, numbering_tags_constants[i].action_name);
1863         }
1864     }
1865 
1866     update_tags_positions (self, final_start_position,
1867                            final_end_position, TEXT_WAS_DELETED);
1868     update_display_text (self);
1869 
1870     g_signal_stop_emission_by_name (editable, "delete-text");
1871 }
1872 
1873 static void
on_insert_text(GtkEditable * editable,const gchar * new_text,gint new_text_length,gpointer position,gpointer user_data)1874 on_insert_text (GtkEditable *editable,
1875                 const gchar *new_text,
1876                 gint         new_text_length,
1877                 gpointer     position,
1878                 gpointer     user_data)
1879 {
1880     NautilusBatchRenameDialog *self;
1881     gint start_position;
1882     gint end_position;
1883     g_autoptr (GList) intersecting_tags = NULL;
1884 
1885     self = NAUTILUS_BATCH_RENAME_DIALOG (user_data);
1886     start_position = *(int *) position;
1887     end_position = start_position + g_utf8_strlen (new_text, -1);
1888     intersecting_tags = get_tags_intersecting_sorted (self, start_position,
1889                                                       end_position, TEXT_WAS_INSERTED);
1890     if (!intersecting_tags)
1891     {
1892         g_signal_handlers_block_by_func (editable, (gpointer) on_insert_text, user_data);
1893         gtk_editable_insert_text (editable, new_text, new_text_length, position);
1894         g_signal_handlers_unblock_by_func (editable, (gpointer) on_insert_text, user_data);
1895 
1896         update_tags_positions (self, start_position, end_position, TEXT_WAS_INSERTED);
1897         update_display_text (self);
1898     }
1899 
1900     g_signal_stop_emission_by_name (editable, "insert-text");
1901 }
1902 
1903 static void
file_names_widget_entry_on_changed(NautilusBatchRenameDialog * self)1904 file_names_widget_entry_on_changed (NautilusBatchRenameDialog *self)
1905 {
1906     update_display_text (self);
1907 }
1908 
1909 static void
nautilus_batch_rename_dialog_finalize(GObject * object)1910 nautilus_batch_rename_dialog_finalize (GObject *object)
1911 {
1912     NautilusBatchRenameDialog *dialog;
1913     GList *l;
1914     guint i;
1915 
1916     dialog = NAUTILUS_BATCH_RENAME_DIALOG (object);
1917 
1918     if (dialog->directories_pending_conflict_check != NULL)
1919     {
1920         cancel_conflict_check (dialog);
1921     }
1922 
1923     g_clear_object (&dialog->numbering_order_menu);
1924     g_clear_object (&dialog->add_tag_menu);
1925     g_list_free (dialog->original_name_listbox_rows);
1926     g_list_free (dialog->arrow_listbox_rows);
1927     g_list_free (dialog->result_listbox_rows);
1928     g_list_free (dialog->listbox_labels_new);
1929     g_list_free (dialog->listbox_labels_old);
1930     g_list_free (dialog->listbox_icons);
1931 
1932     for (l = dialog->selection_metadata; l != NULL; l = l->next)
1933     {
1934         FileMetadata *file_metadata;
1935 
1936         file_metadata = l->data;
1937         for (i = 0; i < G_N_ELEMENTS (file_metadata->metadata); i++)
1938         {
1939             if (file_metadata->metadata[i])
1940             {
1941                 g_string_free (file_metadata->metadata[i], TRUE);
1942             }
1943         }
1944 
1945         g_string_free (file_metadata->file_name, TRUE);
1946         g_free (file_metadata);
1947     }
1948 
1949     if (dialog->create_date != NULL)
1950     {
1951         g_hash_table_destroy (dialog->create_date);
1952     }
1953 
1954     g_list_free_full (dialog->new_names, string_free);
1955     g_list_free_full (dialog->duplicates, conflict_data_free);
1956 
1957     nautilus_file_list_free (dialog->selection);
1958     nautilus_directory_unref (dialog->directory);
1959     nautilus_directory_list_free (dialog->distinct_parent_directories);
1960 
1961     g_object_unref (dialog->size_group);
1962 
1963     g_hash_table_destroy (dialog->tag_info_table);
1964 
1965     g_cancellable_cancel (dialog->metadata_cancellable);
1966     g_clear_object (&dialog->metadata_cancellable);
1967 
1968     G_OBJECT_CLASS (nautilus_batch_rename_dialog_parent_class)->finalize (object);
1969 }
1970 
1971 static void
nautilus_batch_rename_dialog_class_init(NautilusBatchRenameDialogClass * klass)1972 nautilus_batch_rename_dialog_class_init (NautilusBatchRenameDialogClass *klass)
1973 {
1974     GtkWidgetClass *widget_class;
1975     GObjectClass *oclass;
1976 
1977     widget_class = GTK_WIDGET_CLASS (klass);
1978     oclass = G_OBJECT_CLASS (klass);
1979 
1980     oclass->finalize = nautilus_batch_rename_dialog_finalize;
1981 
1982     gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/nautilus/ui/nautilus-batch-rename-dialog.ui");
1983 
1984     gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, grid);
1985     gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, cancel_button);
1986     gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, original_name_listbox);
1987     gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, arrow_listbox);
1988     gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, result_listbox);
1989     gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, name_entry);
1990     gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, rename_button);
1991     gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, find_entry);
1992     gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, replace_entry);
1993     gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, mode_stack);
1994     gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, replace_mode_button);
1995     gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, format_mode_button);
1996     gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, add_button);
1997     gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, add_popover);
1998     gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, numbering_order_label);
1999     gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, scrolled_window);
2000     gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, numbering_order_popover);
2001     gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, numbering_order_button);
2002     gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, numbering_revealer);
2003     gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, conflict_box);
2004     gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, conflict_label);
2005     gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, conflict_up);
2006     gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, conflict_down);
2007     gtk_widget_class_bind_template_child (widget_class, NautilusBatchRenameDialog, numbering_label);
2008 
2009     gtk_widget_class_bind_template_callback (widget_class, file_names_widget_on_activate);
2010     gtk_widget_class_bind_template_callback (widget_class, file_names_widget_entry_on_changed);
2011     gtk_widget_class_bind_template_callback (widget_class, batch_rename_dialog_mode_changed);
2012     gtk_widget_class_bind_template_callback (widget_class, add_button_clicked);
2013     gtk_widget_class_bind_template_callback (widget_class, add_popover_closed);
2014     gtk_widget_class_bind_template_callback (widget_class, numbering_order_button_clicked);
2015     gtk_widget_class_bind_template_callback (widget_class, numbering_order_popover_closed);
2016     gtk_widget_class_bind_template_callback (widget_class, select_next_conflict_up);
2017     gtk_widget_class_bind_template_callback (widget_class, select_next_conflict_down);
2018     gtk_widget_class_bind_template_callback (widget_class, batch_rename_dialog_on_response);
2019     gtk_widget_class_bind_template_callback (widget_class, on_insert_text);
2020     gtk_widget_class_bind_template_callback (widget_class, on_delete_text);
2021 }
2022 
2023 GtkWidget *
nautilus_batch_rename_dialog_new(GList * selection,NautilusDirectory * directory,NautilusWindow * window)2024 nautilus_batch_rename_dialog_new (GList             *selection,
2025                                   NautilusDirectory *directory,
2026                                   NautilusWindow    *window)
2027 {
2028     NautilusBatchRenameDialog *dialog;
2029     GString *dialog_title;
2030     GList *l;
2031     gboolean all_targets_are_folders;
2032     gboolean all_targets_are_regular_files;
2033 
2034     dialog = g_object_new (NAUTILUS_TYPE_BATCH_RENAME_DIALOG, "use-header-bar", TRUE, NULL);
2035 
2036     dialog->selection = nautilus_file_list_copy (selection);
2037     dialog->directory = nautilus_directory_ref (directory);
2038     dialog->window = window;
2039 
2040     gtk_window_set_transient_for (GTK_WINDOW (dialog),
2041                                   GTK_WINDOW (window));
2042 
2043     all_targets_are_folders = TRUE;
2044     for (l = selection; l != NULL; l = l->next)
2045     {
2046         if (!nautilus_file_is_directory (NAUTILUS_FILE (l->data)))
2047         {
2048             all_targets_are_folders = FALSE;
2049             break;
2050         }
2051     }
2052 
2053     all_targets_are_regular_files = TRUE;
2054     for (l = selection; l != NULL; l = l->next)
2055     {
2056         if (!nautilus_file_is_regular_file (NAUTILUS_FILE (l->data)))
2057         {
2058             all_targets_are_regular_files = FALSE;
2059             break;
2060         }
2061     }
2062 
2063     dialog_title = g_string_new ("");
2064     if (all_targets_are_folders)
2065     {
2066         g_string_append_printf (dialog_title,
2067                                 ngettext ("Rename %d Folder",
2068                                           "Rename %d Folders",
2069                                           g_list_length (selection)),
2070                                 g_list_length (selection));
2071     }
2072     else if (all_targets_are_regular_files)
2073     {
2074         g_string_append_printf (dialog_title,
2075                                 ngettext ("Rename %d File",
2076                                           "Rename %d Files",
2077                                           g_list_length (selection)),
2078                                 g_list_length (selection));
2079     }
2080     else
2081     {
2082         g_string_append_printf (dialog_title,
2083                                 /* To translators: %d is the total number of files and folders.
2084                                  * Singular case of the string is never used */
2085                                 ngettext ("Rename %d File and Folder",
2086                                           "Rename %d Files and Folders",
2087                                           g_list_length (selection)),
2088                                 g_list_length (selection));
2089     }
2090 
2091     gtk_window_set_title (GTK_WINDOW (dialog), dialog_title->str);
2092 
2093     dialog->distinct_parent_directories = batch_rename_files_get_distinct_parents (selection);
2094 
2095     add_tag (dialog, metadata_tags_constants[ORIGINAL_FILE_NAME]);
2096 
2097     nautilus_batch_rename_dialog_initialize_actions (dialog);
2098 
2099     update_display_text (dialog);
2100 
2101     fill_display_listbox (dialog);
2102 
2103     gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (window)), NULL);
2104 
2105     g_string_free (dialog_title, TRUE);
2106 
2107     return GTK_WIDGET (dialog);
2108 }
2109 
2110 static void
nautilus_batch_rename_dialog_init(NautilusBatchRenameDialog * self)2111 nautilus_batch_rename_dialog_init (NautilusBatchRenameDialog *self)
2112 {
2113     TagData *tag_data;
2114     guint i;
2115     g_autoptr (GtkBuilder) builder = NULL;
2116 
2117     gtk_widget_init_template (GTK_WIDGET (self));
2118 
2119     gtk_list_box_set_header_func (GTK_LIST_BOX (self->original_name_listbox),
2120                                   (GtkListBoxUpdateHeaderFunc) listbox_header_func,
2121                                   self,
2122                                   NULL);
2123     gtk_list_box_set_header_func (GTK_LIST_BOX (self->arrow_listbox),
2124                                   (GtkListBoxUpdateHeaderFunc) listbox_header_func,
2125                                   self,
2126                                   NULL);
2127     gtk_list_box_set_header_func (GTK_LIST_BOX (self->result_listbox),
2128                                   (GtkListBoxUpdateHeaderFunc) listbox_header_func,
2129                                   self,
2130                                   NULL);
2131 
2132 
2133     self->mode = NAUTILUS_BATCH_RENAME_DIALOG_FORMAT;
2134 
2135     builder = gtk_builder_new_from_resource ("/org/gnome/nautilus/ui/nautilus-batch-rename-dialog-menu.ui");
2136     self->numbering_order_menu = g_object_ref_sink (G_MENU (gtk_builder_get_object (builder, "numbering_order_menu")));
2137     self->add_tag_menu = g_object_ref_sink (G_MENU (gtk_builder_get_object (builder, "add_tag_menu")));
2138 
2139     gtk_popover_bind_model (GTK_POPOVER (self->numbering_order_popover),
2140                             G_MENU_MODEL (self->numbering_order_menu),
2141                             NULL);
2142     gtk_popover_bind_model (GTK_POPOVER (self->add_popover),
2143                             G_MENU_MODEL (self->add_tag_menu),
2144                             NULL);
2145 
2146     gtk_label_set_ellipsize (GTK_LABEL (self->conflict_label), PANGO_ELLIPSIZE_END);
2147     gtk_label_set_max_width_chars (GTK_LABEL (self->conflict_label), 1);
2148 
2149     self->duplicates = NULL;
2150     self->distinct_parent_directories = NULL;
2151     self->directories_pending_conflict_check = NULL;
2152     self->new_names = NULL;
2153     self->rename_clicked = FALSE;
2154 
2155     self->tag_info_table = g_hash_table_new_full (g_str_hash,
2156                                                   g_str_equal,
2157                                                   (GDestroyNotify) g_free,
2158                                                   (GDestroyNotify) g_free);
2159 
2160     for (i = 0; i < G_N_ELEMENTS (numbering_tags_constants); i++)
2161     {
2162         g_autofree gchar *tag_text_representation = NULL;
2163 
2164         tag_text_representation = batch_rename_get_tag_text_representation (numbering_tags_constants[i]);
2165         tag_data = g_new (TagData, 1);
2166         tag_data->available = TRUE;
2167         tag_data->set = FALSE;
2168         tag_data->position = -1;
2169         tag_data->tag_constants = numbering_tags_constants[i];
2170         g_hash_table_insert (self->tag_info_table, g_strdup (tag_text_representation), tag_data);
2171     }
2172 
2173     for (i = 0; i < G_N_ELEMENTS (metadata_tags_constants); i++)
2174     {
2175         g_autofree gchar *tag_text_representation = NULL;
2176 
2177         /* Only the original name is available and set at the start */
2178         tag_text_representation = batch_rename_get_tag_text_representation (metadata_tags_constants[i]);
2179 
2180         tag_data = g_new (TagData, 1);
2181         tag_data->available = FALSE;
2182         tag_data->set = FALSE;
2183         tag_data->position = -1;
2184         tag_data->tag_constants = metadata_tags_constants[i];
2185         g_hash_table_insert (self->tag_info_table, g_strdup (tag_text_representation), tag_data);
2186     }
2187 
2188     self->row_height = -1;
2189 
2190     g_signal_connect (self->original_name_listbox, "row-selected", G_CALLBACK (row_selected), self);
2191     g_signal_connect (self->arrow_listbox, "row-selected", G_CALLBACK (row_selected), self);
2192     g_signal_connect (self->result_listbox, "row-selected", G_CALLBACK (row_selected), self);
2193 
2194     self->size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
2195 
2196     g_signal_connect (self->original_name_listbox,
2197                       "event",
2198                       G_CALLBACK (on_event),
2199                       self);
2200     g_signal_connect (self->result_listbox,
2201                       "event",
2202                       G_CALLBACK (on_event),
2203                       self);
2204     g_signal_connect (self->arrow_listbox,
2205                       "event",
2206                       G_CALLBACK (on_event),
2207                       self);
2208 
2209     self->metadata_cancellable = g_cancellable_new ();
2210 }
2211