1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 
3 /*
4  *  GThumb
5  *
6  *  Copyright (C) 2009 Free Software Foundation, Inc.
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License as published by
10  *  the Free Software Foundation; either version 2 of the License, or
11  *  (at your option) any later version.
12  *
13  *  This program is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
20  */
21 
22 #include <config.h>
23 #include <glib/gi18n.h>
24 #include <gtk/gtk.h>
25 #include <gthumb.h>
26 #include "dlg-rename-series.h"
27 #include "gth-template-editor-dialog.h"
28 #include "gth-template-selector.h"
29 #include "gth-rename-task.h"
30 #include "preferences.h"
31 
32 
33 enum {
34 	SORT_DATA_COLUMN,
35 	SORT_NAME_COLUMN,
36 	SORT_NUM_COLUMNS
37 };
38 
39 enum {
40 	PREVIEW_OLD_NAME_COLUMN,
41 	PREVIEW_NEW_NAME_COLUMN,
42 	PREVIEW_NUM_COLUMNS
43 };
44 
45 
46 #define GET_WIDGET(name) _gtk_builder_get_widget (data->builder, (name))
47 
48 #define DEFAULT_TEMPLATE       "####%E"
49 #define DEFAULT_START_AT       1
50 #define DEFAULT_SORT_BY        "general::unsorted"
51 #define DEFAULT_REVERSE_ORDER  FALSE
52 #define DEFAULT_CHANGE_CASE    GTH_CHANGE_CASE_NONE
53 #define UPDATE_DELAY           500
54 
55 
56 static GthTemplateCode Rename_Special_Codes[] = {
57 	{ GTH_TEMPLATE_CODE_TYPE_TEXT, N_("Text"), 0 },
58 	{ GTH_TEMPLATE_CODE_TYPE_ENUMERATOR, N_("Enumerator"), '#' },
59 	{ GTH_TEMPLATE_CODE_TYPE_SIMPLE, N_("Original filename"), 'F' },
60 	{ GTH_TEMPLATE_CODE_TYPE_SIMPLE, N_("Original extension"), 'E' },
61 	{ GTH_TEMPLATE_CODE_TYPE_SIMPLE, N_("Original enumerator"), 'N' },
62 	{ GTH_TEMPLATE_CODE_TYPE_DATE, N_("Modification date"), 'M' },
63 	{ GTH_TEMPLATE_CODE_TYPE_DATE, N_("Digitalization date"), 'D' },
64 	{ GTH_TEMPLATE_CODE_TYPE_FILE_ATTRIBUTE, N_("File attribute"), 'A' }
65 };
66 
67 
68 typedef struct {
69 	GthBrowser    *browser;
70 	GSettings     *settings;
71 	GList         *file_list;
72 	GList         *file_data_list;
73 	GList         *new_file_list;
74 	GList         *new_names_list;
75 	gboolean       single_file;
76 	gboolean       first_update;
77 	GtkBuilder    *builder;
78 	GtkWidget     *dialog;
79 	GtkWidget     *list_view;
80 	GtkWidget     *sort_combobox;
81 	GtkWidget     *change_case_combobox;
82 	GtkListStore  *list_store;
83 	GtkListStore  *sort_model;
84 	gboolean       help_visible;
85 	char          *required_attributes;
86 	guint          update_id;
87 	gboolean       template_changed;
88 	GList         *tasks;
89 	gboolean       closing;
90 } DialogData;
91 
92 
93 static void
destroy_dialog(DialogData * data)94 destroy_dialog (DialogData *data)
95 {
96 	if (data->dialog != NULL)
97 		gtk_widget_destroy (data->dialog);
98 	data->dialog = NULL;
99 	gth_browser_set_dialog (data->browser, "rename_series", NULL);
100 
101 	if (data->update_id != 0) {
102 		g_source_remove (data->update_id);
103 		data->update_id = 0;
104 	}
105 
106 	g_free (data->required_attributes);
107 	g_object_unref (data->builder);
108 	_g_object_list_unref (data->file_data_list);
109 	_g_object_list_unref (data->file_list);
110 	_g_string_list_free (data->new_names_list);
111 	g_list_free (data->new_file_list);
112 	g_object_unref (data->settings);
113 	g_free (data);
114 }
115 
116 
117 typedef struct {
118 	const char   *template;
119 	GthFileData  *file_data;
120 	int           n;
121 	GError      **error;
122 } TemplateData;
123 
124 
125 static char *
get_original_enum(GthFileData * file_data,char * match)126 get_original_enum (GthFileData *file_data,
127 	           char        *match)
128 {
129 	char    *basename;
130 	GRegex  *re;
131 	char   **a;
132 	char    *value = NULL;
133 
134 	basename = g_file_get_basename (file_data->file);
135 	re = g_regex_new ("([0-9]+)", 0, 0, NULL);
136 	a = g_regex_split (re, basename, 0);
137 	if (g_strv_length (a) >= 2)
138 		value = g_strdup (g_strstrip (a[1]));
139 
140 	g_strfreev (a);
141 	g_regex_unref (re);
142 	g_free (basename);
143 
144 	return value;
145 }
146 
147 
148 static char *
get_attribute_value(GthFileData * file_data,char * match)149 get_attribute_value (GthFileData *file_data,
150 	             char        *match)
151 {
152 	GRegex    *re;
153 	char     **a;
154 	char      *attribute = NULL;
155 	char      *value = NULL;
156 
157 	re = g_regex_new ("%A\\{([^}]+)\\}", 0, 0, NULL);
158 	a = g_regex_split (re, match, 0);
159 	if (g_strv_length (a) >= 2)
160 		attribute = g_strstrip (a[1]);
161 
162 	if ((attribute != NULL) && (*attribute != '\0')) {
163 		value = gth_file_data_get_attribute_as_string (file_data, attribute);
164 		if (value != NULL) {
165 			char *tmp_value;
166 
167 			tmp_value = _g_utf8_replace_pattern (value, "[\r\n]", " ");
168 			g_free (value);
169 			value = tmp_value;
170 		}
171 	}
172 
173 	g_strfreev (a);
174 	g_regex_unref (re);
175 
176 	return value;
177 }
178 
179 
180 static gboolean
template_eval_cb(const GMatchInfo * info,GString * res,gpointer data)181 template_eval_cb (const GMatchInfo *info,
182 		  GString          *res,
183 		  gpointer          data)
184 {
185 	TemplateData *template_data = data;
186 	char         *r = NULL;
187 	char         *match;
188 
189 	match = g_match_info_fetch (info, 0);
190 
191 	if (strncmp (match, "#", 1) == 0) {
192 		char *format;
193 
194 		format = g_strdup_printf ("%%0%" G_GSIZE_FORMAT "d", strlen (match));
195 		r = g_strdup_printf (format, template_data->n);
196 
197 		g_free (format);
198 	}
199 	else if (strncmp (match, "%A", 2) == 0) {
200 		r = get_attribute_value (template_data->file_data, match);
201 		/*if (r == NULL)
202 			*template_data->error = g_error_new_literal (GTH_TASK_ERROR, GTH_TASK_ERROR_FAILED, _("Malformed template"));*/
203 	}
204 	else if (strcmp (match, "%E") == 0) {
205 		char *uri;
206 
207 		uri = g_file_get_uri (template_data->file_data->file);
208 		r = _g_uri_get_extension (uri);
209 
210 		g_free (uri);
211 	}
212 	else if (strcmp (match, "%F") == 0) {
213 		char *basename;
214 
215 		basename = g_file_get_basename (template_data->file_data->file);
216 		r = _g_path_remove_extension (basename);
217 
218 		g_free (basename);
219 	}
220 	else if (strcmp (match, "%N") == 0) {
221 		r = get_original_enum (template_data->file_data, match);
222 	}
223 	else if ((strncmp (match, "%D", 2) == 0) || (strncmp (match, "%M", 2) == 0)) {
224 		gboolean value_available = FALSE;
225 		GTimeVal timeval;
226 
227 		if (strncmp (match, "%D", 2) == 0) {
228 			value_available = gth_file_data_get_digitalization_time (template_data->file_data, &timeval);
229 		}
230 		else if (strncmp (match, "%M", 2) == 0) {
231 			timeval = *gth_file_data_get_modification_time (template_data->file_data);
232 			value_available = TRUE;
233 		}
234 
235 		if (value_available) {
236 			GRegex  *re;
237 			char   **a;
238 			char    *format = NULL;
239 
240 			/* Get the date format */
241 
242 			re = g_regex_new ("%[A-Z]\\{([^}]+)\\}", 0, 0, NULL);
243 			a = g_regex_split (re, match, 0);
244 			if (g_strv_length (a) >= 2)
245 				format = g_strstrip (a[1]);
246 			r = _g_time_val_strftime (&timeval, format);
247 
248 			g_strfreev (a);
249 			g_regex_unref (re);
250 		}
251 	}
252 
253 	if (r != NULL)
254 		g_string_append (res, r);
255 
256 	g_free (r);
257 	g_free (match);
258 
259 	return FALSE;
260 }
261 
262 
263 static char *
get_required_attributes(DialogData * data)264 get_required_attributes (DialogData *data)
265 {
266 	GtkTreeIter  iter;
267 	GString     *required_attributes;
268 	const char  *template;
269 
270 	required_attributes = g_string_new (GFILE_STANDARD_ATTRIBUTES);
271 
272 	/* attributes required for sorting */
273 
274 	if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (data->sort_combobox), &iter)) {
275 		GthFileDataSort *sort_type;
276 
277 		gtk_tree_model_get (GTK_TREE_MODEL (data->sort_model),
278 				    &iter,
279 				    SORT_DATA_COLUMN, &sort_type,
280 				    -1);
281 
282 		if ((sort_type->required_attributes != NULL) && ! g_str_equal (sort_type->required_attributes, "")) {
283 			g_string_append (required_attributes, ",");
284 			g_string_append (required_attributes, sort_type->required_attributes);
285 		}
286 	}
287 
288 	/* attributes required for renaming */
289 
290 	template = gtk_entry_get_text (GTK_ENTRY (GET_WIDGET ("template_entry")));
291 
292 	if (g_strstr_len (template, -1, "%A") != NULL) {
293 		GRegex  *re;
294 		char   **a;
295 		int      i;
296 
297 		re = g_regex_new ("%A\\{([^}]+)\\}", 0, 0, NULL);
298 		a = g_regex_split (re, template, 0);
299 		for (i = 1; i < g_strv_length (a); i += 2) {
300 			char *id;
301 
302 			id = g_strstrip (g_strdup (a[i]));
303 			g_string_append (required_attributes, ",");
304 			g_string_append (required_attributes, id);
305 
306 			g_free (id);
307 		}
308 
309 		g_strfreev (a);
310 		g_regex_unref (re);
311 	}
312 
313 	if (g_strstr_len (template, -1, "%D") != NULL) {
314 		int i;
315 
316 		for (i = 0; FileDataDigitalizationTags[i] != NULL; i++) {
317 			g_string_append (required_attributes, ",");
318 			g_string_append (required_attributes, FileDataDigitalizationTags[i]);
319 		}
320 	}
321 
322 	if (g_strstr_len (template, -1, "%M") != NULL) {
323 		g_string_append (required_attributes, ",");
324 		g_string_append (required_attributes, G_FILE_ATTRIBUTE_TIME_MODIFIED "," G_FILE_ATTRIBUTE_TIME_MODIFIED_USEC);
325 	}
326 
327 	return g_string_free (required_attributes, FALSE);
328 }
329 
330 
331 /* update_file_list */
332 
333 
334 typedef struct {
335 	DialogData *data;
336 	ReadyFunc   ready_func;
337 	GthTask    *task;
338 	gulong      task_completed_id;
339 } UpdateData;
340 
341 
342 static void
update_data_free(UpdateData * update_data)343 update_data_free (UpdateData *update_data)
344 {
345 	g_free(update_data);
346 }
347 
348 
349 static void update_preview_cb (GtkWidget  *widget, DialogData *data);
350 
351 
352 static void
update_file_list__step2(gpointer user_data)353 update_file_list__step2 (gpointer user_data)
354 {
355 	UpdateData   *update_data = user_data;
356 	DialogData   *data = update_data->data;
357 	GtkTreeIter   iter;
358 	int           change_case;
359 	TemplateData *template_data;
360 	GRegex       *re;
361 	GList        *scan;
362 	GError       *error = NULL;
363 
364 	if (data->first_update) {
365 		if (data->file_data_list->next == NULL) {
366 			GthFileData *file_data = data->file_data_list->data;
367 			const char  *edit_name;
368 			const char  *end_pos;
369 
370 			g_signal_handlers_block_by_func (GET_WIDGET ("template_entry"), update_preview_cb, data);
371 			gtk_entry_set_text (GTK_ENTRY (GET_WIDGET ("template_entry")), g_file_info_get_attribute_string (file_data->info, G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME));
372 			g_signal_handlers_unblock_by_func (GET_WIDGET ("template_entry"), update_preview_cb, data);
373 
374 			gtk_widget_grab_focus (GET_WIDGET ("template_entry"));
375 
376 			edit_name = gtk_entry_get_text (GTK_ENTRY (GET_WIDGET ("template_entry")));
377 			end_pos = g_utf8_strrchr (edit_name, -1, '.');
378 			if (end_pos != NULL) {
379 				glong nchars;
380 
381 				nchars = g_utf8_strlen (edit_name, (gssize) (end_pos - edit_name));
382 				gtk_editable_select_region (GTK_EDITABLE (GET_WIDGET ("template_entry")), 0, nchars);
383 			}
384 		}
385 		else {
386 			gtk_widget_grab_focus (GET_WIDGET ("template_entry"));
387 			gtk_editable_select_region (GTK_EDITABLE (GET_WIDGET ("template_entry")), 0, -1);
388 		}
389 	}
390 
391 	data->first_update = FALSE;
392 
393 	if (data->new_names_list != NULL) {
394 		_g_string_list_free (data->new_names_list);
395 		data->new_names_list = NULL;
396 	}
397 
398 	if (data->new_file_list != NULL) {
399 		g_list_free (data->new_file_list);
400 		data->new_file_list = NULL;
401 	}
402 
403 	data->new_file_list = g_list_copy (data->file_data_list);
404 	if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (data->sort_combobox), &iter)) {
405 		GthFileDataSort *sort_type;
406 
407 		gtk_tree_model_get (GTK_TREE_MODEL (data->sort_model),
408 				    &iter,
409 				    SORT_DATA_COLUMN, &sort_type,
410 				    -1);
411 
412 		if (sort_type->cmp_func != NULL)
413 			data->new_file_list = g_list_sort (data->new_file_list, (GCompareFunc) sort_type->cmp_func);
414 	}
415 	if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("reverse_order_checkbutton"))))
416 		data->new_file_list = g_list_reverse (data->new_file_list);
417 
418 	change_case = gtk_combo_box_get_active (GTK_COMBO_BOX (data->change_case_combobox));
419 
420 	template_data = g_new0 (TemplateData, 1);
421 	template_data->error = &error;
422 	template_data->n = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (GET_WIDGET ("start_at_spinbutton")));
423 	template_data->template = gtk_entry_get_text (GTK_ENTRY (GET_WIDGET ("template_entry")));
424 	re = g_regex_new ("#+|%[ADEFMN](\\{[^}]+\\})?", 0, 0, NULL);
425 	for (scan = data->new_file_list; scan; scan = scan->next) {
426 		char *new_name;
427 		char *new_name2;
428 
429 		template_data->file_data = scan->data;
430 		new_name = g_regex_replace_eval (re, template_data->template, -1, 0, 0, template_eval_cb, template_data, &error);
431 		if (error != NULL)
432 			break;
433 
434 		switch (change_case) {
435 		case GTH_CHANGE_CASE_LOWER:
436 			new_name2 = g_utf8_strdown (new_name, -1);
437 			break;
438 		case GTH_CHANGE_CASE_UPPER:
439 			new_name2 = g_utf8_strup (new_name, -1);
440 			break;
441 		default:
442 			new_name2 = g_strdup (new_name);
443 			break;
444 		}
445 
446 		data->new_names_list = g_list_prepend (data->new_names_list, new_name2);
447 		template_data->n = template_data->n + 1;
448 
449 		g_free (new_name);
450 	}
451 	g_regex_unref (re);
452 	data->new_names_list = g_list_reverse (data->new_names_list);
453 
454 	if (update_data->ready_func)
455 		update_data->ready_func (error, update_data->data);
456 
457 	update_data_free (update_data);
458 }
459 
460 
461 static void
load_file_data_task_completed_cb(GthTask * task,GError * error,gpointer user_data)462 load_file_data_task_completed_cb (GthTask  *task,
463 				  GError   *error,
464 				  gpointer  user_data)
465 {
466 	UpdateData *update_data = user_data;
467 	DialogData *data = update_data->data;
468 
469 	gtk_widget_hide (GET_WIDGET ("task_box"));
470 	gtk_widget_set_sensitive (GET_WIDGET ("options_table"), TRUE);
471 	gtk_dialog_set_response_sensitive (GTK_DIALOG (data->dialog), GTK_RESPONSE_OK, TRUE);
472 
473 	data->tasks = g_list_remove (data->tasks, update_data->task);
474 
475 	g_object_unref (update_data->task);
476 	update_data->task = NULL;
477 	update_data->task_completed_id = 0;
478 
479 	if (error != NULL) {
480 		if (! data->closing && update_data->ready_func)
481 			update_data->ready_func (error, update_data->data);
482 		update_data_free (update_data);
483 
484 		if (data->tasks == NULL)
485 			destroy_dialog (data);
486 
487 		return;
488 	}
489 
490 	_g_object_list_unref (data->file_data_list);
491 	data->file_data_list = _g_object_list_ref (gth_load_file_data_task_get_result (GTH_LOAD_FILE_DATA_TASK (task)));
492 	data->template_changed = FALSE;
493 
494 	update_file_list__step2 (update_data);
495 }
496 
497 
498 static void
update_file_list(DialogData * data,ReadyFunc ready_func)499 update_file_list (DialogData *data,
500 		  ReadyFunc   ready_func)
501 {
502 	UpdateData *update_data;
503 
504 	update_data = g_new (UpdateData, 1);
505 	update_data->data = data;
506 	update_data->ready_func = ready_func;
507 
508 	if (data->template_changed) {
509 		char     *required_attributes;
510 		gboolean  reload_required;
511 
512 		required_attributes = get_required_attributes (data);
513 		reload_required = attribute_list_reload_required (data->required_attributes, required_attributes);
514 		g_free (data->required_attributes);
515 		data->required_attributes = required_attributes;
516 
517 		if (reload_required) {
518 			GtkWidget *child;
519 
520 			gtk_widget_set_sensitive (GET_WIDGET ("options_table"), FALSE);
521 			gtk_dialog_set_response_sensitive (GTK_DIALOG (data->dialog), GTK_RESPONSE_OK, FALSE);
522 			gtk_widget_show (GET_WIDGET ("task_box"));
523 
524 			update_data->task = gth_load_file_data_task_new (data->file_list, data->required_attributes);
525 			update_data->task_completed_id = g_signal_connect (update_data->task,
526 									   "completed",
527 									   G_CALLBACK (load_file_data_task_completed_cb),
528 									   update_data);
529 
530 			data->tasks = g_list_prepend (data->tasks, update_data->task);
531 
532 			child = gth_task_progress_new (update_data->task);
533 			gtk_widget_show (child);
534 			gtk_box_pack_start (GTK_BOX (GET_WIDGET ("task_box")), child, TRUE, TRUE, 0);
535 			gth_task_exec (update_data->task, NULL);
536 
537 			return;
538 		}
539 	}
540 
541 	call_when_idle (update_file_list__step2, update_data);
542 }
543 
544 
545 static void
ok_button_clicked__step2(GError * error,gpointer user_data)546 ok_button_clicked__step2 (GError   *error,
547 			  gpointer  user_data)
548 {
549 	DialogData  *data = user_data;
550 	GtkTreeIter  iter;
551 	GList       *old_files;
552 	GList       *new_files;
553 	GList       *scan1;
554 	GList       *scan2;
555 	GthTask     *task;
556 
557 	if (error != NULL) {
558 		_gtk_error_dialog_from_gerror_show (GTK_WINDOW (data->dialog), _("Could not rename the files"), error);
559 		return;
560 	}
561 
562 	/* -- save preferences -- */
563 
564 	if (data->file_list->next != NULL)
565 		g_settings_set_string (data->settings, PREF_RENAME_SERIES_TEMPLATE, gtk_entry_get_text (GTK_ENTRY (GET_WIDGET ("template_entry"))));
566 
567 	g_settings_set_int (data->settings, PREF_RENAME_SERIES_START_AT, gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (GET_WIDGET ("start_at_spinbutton"))));
568 
569 	if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (data->sort_combobox), &iter)) {
570 		GthFileDataSort *sort_type;
571 
572 		gtk_tree_model_get (GTK_TREE_MODEL (data->sort_model),
573 				    &iter,
574 				    SORT_DATA_COLUMN, &sort_type,
575 				    -1);
576 		g_settings_set_string (data->settings, PREF_RENAME_SERIES_SORT_BY, sort_type->name);
577 	}
578 
579 	g_settings_set_boolean (data->settings, PREF_RENAME_SERIES_REVERSE_ORDER, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("reverse_order_checkbutton"))));
580 	g_settings_set_enum (data->settings, PREF_RENAME_SERIES_CHANGE_CASE, gtk_combo_box_get_active (GTK_COMBO_BOX (data->change_case_combobox)));
581 
582 	/* -- prepare and exec rename task -- */
583 
584 	old_files = NULL;
585 	new_files = NULL;
586 
587 	for (scan1 = data->new_file_list, scan2 = data->new_names_list;
588 	     scan1 && scan2;
589 	     scan1 = scan1->next, scan2 = scan2->next)
590 	{
591 		GthFileData *file_data = scan1->data;
592 		char        *new_name  = scan2->data;
593 		GFile       *parent;
594 		GFile       *new_file;
595 
596 		parent = g_file_get_parent (file_data->file);
597 		new_file = g_file_get_child (parent, new_name);
598 
599 		old_files = g_list_prepend (old_files, g_object_ref (file_data->file));
600 		new_files = g_list_prepend (new_files, new_file);
601 
602 		g_object_unref (parent);
603 	}
604 	old_files = g_list_reverse (old_files);
605 	new_files = g_list_reverse (new_files);
606 
607 	task = gth_rename_task_new (old_files, new_files);
608 	gth_browser_exec_task (data->browser, task, GTH_TASK_FLAGS_DEFAULT);
609 
610 	g_object_unref (task);
611 	destroy_dialog (data);
612 }
613 
614 
615 static void
ok_button_clicked(DialogData * data)616 ok_button_clicked (DialogData *data)
617 {
618 	if (data->update_id != 0) {
619 		g_source_remove (data->update_id);
620 		data->update_id = 0;
621 	}
622 
623 	update_file_list (data, ok_button_clicked__step2);
624 }
625 
626 
627 static void
dialog_response_cb(GtkDialog * dialog,int response_id,gpointer user_data)628 dialog_response_cb (GtkDialog *dialog,
629 		    int        response_id,
630 		    gpointer   user_data)
631 {
632 	DialogData *data = user_data;
633 
634 	switch (response_id) {
635 	case GTK_RESPONSE_DELETE_EVENT:
636 	case GTK_RESPONSE_CANCEL:
637 		if (data->tasks != NULL) {
638 			GList *tasks;
639 
640 			data->closing = TRUE;
641 
642 			tasks = g_list_copy (data->tasks);
643 			g_list_foreach (tasks, (GFunc) gth_task_cancel, NULL);
644 			g_list_free (tasks);
645 		}
646 		else
647 			destroy_dialog (data);
648 		break;
649 
650 	case GTK_RESPONSE_OK:
651 		ok_button_clicked (data);
652 		break;
653 
654 	default:
655 		break;
656 	}
657 }
658 
659 
660 static void
error_dialog_response_cb(GtkDialog * dialog,int response,gpointer user_data)661 error_dialog_response_cb (GtkDialog *dialog,
662 			  int        response,
663 			  gpointer   user_data)
664 {
665 	DialogData *data = user_data;
666 
667 	gtk_widget_destroy (GTK_WIDGET (dialog));
668 	destroy_dialog (data);
669 }
670 
671 
672 static void
update_preview__step2(GError * error,gpointer user_data)673 update_preview__step2 (GError   *error,
674 		       gpointer  user_data)
675 {
676 	DialogData *data = user_data;
677 	GList      *scan1, *scan2;
678 
679 	if (error != NULL) {
680 		GtkWidget *d;
681 
682 		d = _gtk_message_dialog_new (GTK_WINDOW (data->dialog),
683 					     GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
684 					     _GTK_ICON_NAME_DIALOG_ERROR,
685 					     _("Could not rename the files"),
686 					     error->message,
687 					     _GTK_LABEL_OK, GTK_RESPONSE_OK,
688 					     NULL);
689 		g_signal_connect (d, "response", G_CALLBACK (error_dialog_response_cb), data);
690 		gtk_window_present (GTK_WINDOW (d));
691 
692 		return;
693 	}
694 
695 	/* -- update the list view -- */
696 
697 	gtk_list_store_clear (data->list_store);
698 	for (scan1 = data->new_file_list, scan2 = data->new_names_list;
699 	     scan1 && scan2;
700 	     scan1 = scan1->next, scan2 = scan2->next)
701 	{
702 		GthFileData *file_data1 = scan1->data;
703 		GthFileData *new_name = scan2->data;
704 		GtkTreeIter  iter;
705 
706 		gtk_list_store_append (data->list_store, &iter);
707 		gtk_list_store_set (data->list_store, &iter,
708 				    PREVIEW_OLD_NAME_COLUMN, g_file_info_get_display_name (file_data1->info),
709 				    PREVIEW_NEW_NAME_COLUMN, new_name,
710 				    -1);
711 	}
712 }
713 
714 
715 static void
dlg_rename_series_update_preview(DialogData * data)716 dlg_rename_series_update_preview (DialogData *data)
717 {
718 	update_file_list (data, update_preview__step2);
719 }
720 
721 
722 static void
template_entry_icon_release_cb(GtkEntry * entry,GtkEntryIconPosition icon_pos,GdkEvent * event,gpointer user_data)723 template_entry_icon_release_cb (GtkEntry             *entry,
724                                 GtkEntryIconPosition  icon_pos,
725 				GdkEvent             *event,
726 				gpointer              user_data)
727 {
728 	DialogData *data = user_data;
729 
730 	data->help_visible = ! data->help_visible;
731 
732 	if (data->help_visible)
733 		gtk_widget_show (GET_WIDGET("template_help_table"));
734 	else
735 		gtk_widget_hide (GET_WIDGET("template_help_table"));
736 }
737 
738 
739 static gboolean
update_preview_after_delay_cb(gpointer user_data)740 update_preview_after_delay_cb (gpointer user_data)
741 {
742 	DialogData *data = user_data;
743 
744 	if (data->update_id != 0) {
745 		g_source_remove (data->update_id);
746 		data->update_id = 0;
747 	}
748 
749 	dlg_rename_series_update_preview (data);
750 
751 	return FALSE;
752 }
753 
754 
755 static void
update_preview_cb(GtkWidget * widget,DialogData * data)756 update_preview_cb (GtkWidget  *widget,
757 		   DialogData *data)
758 {
759 	data->template_changed = TRUE;
760 	if (data->update_id != 0)
761 		g_source_remove (data->update_id);
762 	data->update_id = g_timeout_add (UPDATE_DELAY, update_preview_after_delay_cb, data);
763 }
764 
765 
766 static void
template_editor_dialog_response_cb(GtkDialog * dialog,int response_id,gpointer user_data)767 template_editor_dialog_response_cb (GtkDialog *dialog,
768 				    int        response_id,
769 				    gpointer   user_data)
770 {
771 	DialogData *data = user_data;
772 	char       *template;
773 	GError     *error = NULL;
774 
775 	if (response_id != GTK_RESPONSE_OK) {
776 		gtk_widget_destroy (GTK_WIDGET (dialog));
777 		return;
778 	}
779 
780 	template = gth_template_editor_dialog_get_template (GTH_TEMPLATE_EDITOR_DIALOG (dialog), &error);
781 	if (error != NULL) {
782 		_gtk_error_dialog_from_gerror_show (GTK_WINDOW (dialog), _("Could not save the template"), error);
783 		g_clear_error (&error);
784 		return;
785 	}
786 
787 	gtk_entry_set_text (GTK_ENTRY (GET_WIDGET ("template_entry")), template);
788 	gtk_widget_destroy (GTK_WIDGET (dialog));
789 
790 	g_free (template);
791 }
792 
793 
794 static void
edit_template_button_clicked_cb(GtkWidget * widget,DialogData * data)795 edit_template_button_clicked_cb (GtkWidget  *widget,
796 				 DialogData *data)
797 {
798 	GtkWidget *dialog;
799 
800 	dialog = gth_template_editor_dialog_new (Rename_Special_Codes, 8, _("Edit Template"), GTK_WINDOW (data->dialog));
801 	gth_template_editor_dialog_set_template (GTH_TEMPLATE_EDITOR_DIALOG (dialog),
802 						 gtk_entry_get_text (GTK_ENTRY (GET_WIDGET ("template_entry"))));
803 	g_signal_connect (dialog,
804 			  "response",
805 			  G_CALLBACK (template_editor_dialog_response_cb),
806 			  data);
807 	gtk_window_set_modal (GTK_WINDOW (dialog), TRUE);
808 	gtk_window_present (GTK_WINDOW (dialog));
809 }
810 
811 static void
return_pressed_callback(GtkDialog * dialog,gpointer user_data)812 return_pressed_callback (GtkDialog *dialog,
813 			 gpointer   user_data)
814 {
815 	ok_button_clicked (user_data);
816 }
817 
818 void
dlg_rename_series(GthBrowser * browser,GList * file_list)819 dlg_rename_series (GthBrowser *browser,
820 		   GList      *file_list)
821 {
822 	DialogData        *data;
823 	GtkCellRenderer   *renderer;
824 	GtkTreeViewColumn *column;
825 	int                i;
826 	GList             *sort_types;
827 	GList             *scan;
828 	int                change_case;
829 	int                start_at;
830 	char              *sort_by;
831 	gboolean           found;
832 
833 	if (gth_browser_get_dialog (browser, "rename_series") != NULL) {
834 		gtk_window_present (GTK_WINDOW (gth_browser_get_dialog (browser, "rename_series")));
835 		return;
836 	}
837 
838 	data = g_new0 (DialogData, 1);
839 	data->browser = browser;
840 	data->builder = _gtk_builder_new_from_file ("rename-series.ui", "rename_series");
841 	data->settings = g_settings_new (GTHUMB_RENAME_SERIES_SCHEMA);
842 	data->file_list = _g_file_list_dup (file_list);
843 	data->first_update = TRUE;
844 	data->template_changed = TRUE;
845 	data->closing = FALSE;
846 
847 	/* Get the widgets. */
848 
849 	data->dialog = g_object_new (GTK_TYPE_DIALOG,
850 				     "title", _("Rename"),
851 				     "transient-for", GTK_WINDOW (browser),
852 				     "modal", FALSE,
853 				     "destroy-with-parent", FALSE,
854 				     "use-header-bar", _gtk_settings_get_dialogs_use_header (),
855 				     NULL);
856 	gtk_container_add (GTK_CONTAINER (gtk_dialog_get_content_area (GTK_DIALOG (data->dialog))),
857 			   _gtk_builder_get_widget (data->builder, "dialog_content"));
858 	gtk_dialog_add_buttons (GTK_DIALOG (data->dialog),
859 				_GTK_LABEL_CANCEL, GTK_RESPONSE_CANCEL,
860 				_("_Rename"), GTK_RESPONSE_OK,
861 				NULL);
862 	_gtk_dialog_add_class_to_response (GTK_DIALOG (data->dialog), GTK_RESPONSE_OK, GTK_STYLE_CLASS_SUGGESTED_ACTION);
863 
864 	gth_browser_set_dialog (browser, "rename_series", data->dialog);
865 	g_object_set_data (G_OBJECT (data->dialog), "dialog_data", data);
866 
867 	/* Set widgets data. */
868 
869 	data->list_store = gtk_list_store_new (PREVIEW_NUM_COLUMNS,
870 					       G_TYPE_STRING,
871 					       G_TYPE_STRING);
872 	data->list_view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (data->list_store));
873 	g_object_unref (data->list_store);
874 
875 	renderer = gtk_cell_renderer_text_new ();
876 	g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
877 	column = gtk_tree_view_column_new_with_attributes (_("Old Name"),
878 							   renderer,
879 							   "text", PREVIEW_OLD_NAME_COLUMN,
880 							   NULL);
881 	gtk_tree_view_column_set_expand (GTK_TREE_VIEW_COLUMN (column), TRUE);
882 	gtk_tree_view_column_set_resizable (GTK_TREE_VIEW_COLUMN (column), TRUE);
883 	gtk_tree_view_column_set_sizing (GTK_TREE_VIEW_COLUMN (column), GTK_TREE_VIEW_COLUMN_AUTOSIZE);
884 	gtk_tree_view_append_column (GTK_TREE_VIEW (data->list_view), column);
885 
886 	renderer = gtk_cell_renderer_text_new ();
887 	g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
888 	column = gtk_tree_view_column_new_with_attributes (_("New Name"),
889 							   renderer,
890 							   "text", PREVIEW_NEW_NAME_COLUMN,
891 							   NULL);
892 	gtk_tree_view_column_set_expand (GTK_TREE_VIEW_COLUMN (column), TRUE);
893 	gtk_tree_view_column_set_resizable (GTK_TREE_VIEW_COLUMN (column), TRUE);
894 	gtk_tree_view_column_set_sizing (GTK_TREE_VIEW_COLUMN (column), GTK_TREE_VIEW_COLUMN_AUTOSIZE);
895 	gtk_tree_view_append_column (GTK_TREE_VIEW (data->list_view), column);
896 
897 	gtk_widget_show (data->list_view);
898 	gtk_widget_set_vexpand (data->list_view, TRUE);
899 	gtk_container_add (GTK_CONTAINER (GET_WIDGET ("preview_scrolledwindow")), data->list_view);
900 
901 	gtk_label_set_mnemonic_widget (GTK_LABEL (GET_WIDGET ("preview_label")), data->list_view);
902 
903 	if (data->file_list->next != NULL)
904 		gtk_entry_set_text (GTK_ENTRY (GET_WIDGET ("template_entry")),
905 				    g_settings_get_string (data->settings, PREF_RENAME_SERIES_TEMPLATE));
906 
907 	start_at = g_settings_get_int (data->settings, PREF_RENAME_SERIES_START_AT);
908 	if (start_at < 0)
909 		start_at = DEFAULT_START_AT;
910 	gtk_spin_button_set_value (GTK_SPIN_BUTTON (GET_WIDGET ("start_at_spinbutton")),  start_at * 1.0);
911 
912 	/* sort by */
913 
914 	data->sort_model = gtk_list_store_new (SORT_NUM_COLUMNS,
915 					       G_TYPE_POINTER,
916 					       G_TYPE_STRING);
917 	data->sort_combobox = gtk_combo_box_new_with_model (GTK_TREE_MODEL (data->sort_model));
918 	g_object_unref (data->sort_model);
919 	renderer = gtk_cell_renderer_text_new ();
920 	gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (data->sort_combobox), renderer, TRUE);
921 	gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (data->sort_combobox),
922 					renderer,
923 					"text", SORT_NAME_COLUMN,
924 					NULL);
925 
926 	sort_by = g_settings_get_string (data->settings, PREF_RENAME_SERIES_SORT_BY);
927 	found = FALSE;
928 
929 	sort_types = gth_main_get_all_sort_types ();
930 	for (i = 0, scan = sort_types; scan; scan = scan->next, i++) {
931 		GthFileDataSort *sort_type = scan->data;
932 		GtkTreeIter      iter;
933 
934 		gtk_list_store_append (data->sort_model, &iter);
935 		gtk_list_store_set (data->sort_model, &iter,
936 				    SORT_DATA_COLUMN, sort_type,
937 				    SORT_NAME_COLUMN, sort_type->display_name,
938 				    -1);
939 
940 		if (strcmp (sort_by, sort_type->name) == 0) {
941 			gtk_combo_box_set_active_iter (GTK_COMBO_BOX (data->sort_combobox), &iter);
942 			found = TRUE;
943 		}
944 	}
945 	g_list_free (sort_types);
946 	g_free (sort_by);
947 
948 	if (!found)
949 		gtk_combo_box_set_active (GTK_COMBO_BOX (data->sort_combobox), 0);
950 
951 	gtk_widget_show (data->sort_combobox);
952 	gtk_box_pack_start (GTK_BOX (GET_WIDGET ("sort_by_box")), data->sort_combobox, FALSE, FALSE, 0);
953 	gtk_label_set_mnemonic_widget (GTK_LABEL (GET_WIDGET ("sort_by_label")), data->sort_combobox);
954 
955 	/* reverse order */
956 
957 	gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (GET_WIDGET ("reverse_order_checkbutton")),
958 				      g_settings_get_boolean (data->settings, PREF_RENAME_SERIES_REVERSE_ORDER));
959 
960 	/* change case */
961 
962 	change_case = g_settings_get_enum (data->settings, PREF_RENAME_SERIES_CHANGE_CASE);
963 	if ((change_case < GTH_CHANGE_CASE_NONE) || (change_case > GTH_CHANGE_CASE_UPPER))
964 		change_case = DEFAULT_CHANGE_CASE;
965 
966 	data->change_case_combobox = _gtk_combo_box_new_with_texts (_("Keep original case"),
967 								    _("Convert to lower-case"),
968 								    _("Convert to upper-case"),
969 								    NULL);
970 	gtk_combo_box_set_active (GTK_COMBO_BOX (data->change_case_combobox), change_case);
971 	gtk_widget_show (data->change_case_combobox);
972 	gtk_box_pack_start (GTK_BOX (GET_WIDGET ("change_case_box")), data->change_case_combobox, FALSE, FALSE, 0);
973 	gtk_label_set_mnemonic_widget (GTK_LABEL (GET_WIDGET ("change_case_label")), data->change_case_combobox);
974 
975 	/* Set the signals handlers. */
976 
977 	g_signal_connect (data->dialog,
978 			  "delete-event",
979 			  G_CALLBACK (gtk_true),
980 			  NULL);
981 	g_signal_connect (data->dialog,
982 			  "response",
983 			  G_CALLBACK (dialog_response_cb),
984 			  data);
985 	g_signal_connect (GET_WIDGET ("template_entry"),
986   			  "icon-release",
987   			  G_CALLBACK (template_entry_icon_release_cb),
988   			  data);
989 	g_signal_connect (GET_WIDGET ("template_entry"),
990 			  "changed",
991 			  G_CALLBACK (update_preview_cb),
992 			  data);
993 	g_signal_connect (GET_WIDGET("template_entry"),
994 			  "activate",
995 			  G_CALLBACK (return_pressed_callback),
996 			  data);
997 	g_signal_connect (GET_WIDGET ("start_at_spinbutton"),
998 			  "value_changed",
999 			  G_CALLBACK (update_preview_cb),
1000 			  data);
1001 	g_signal_connect (data->sort_combobox,
1002 			  "changed",
1003 			  G_CALLBACK (update_preview_cb),
1004 			  data);
1005 	g_signal_connect (data->change_case_combobox,
1006                           "changed",
1007                           G_CALLBACK (update_preview_cb),
1008                           data);
1009 	g_signal_connect (GET_WIDGET ("reverse_order_checkbutton"),
1010 			  "toggled",
1011 			  G_CALLBACK (update_preview_cb),
1012 			  data);
1013         g_signal_connect (GET_WIDGET ("edit_template_button"),
1014                           "clicked",
1015                           G_CALLBACK (edit_template_button_clicked_cb),
1016                           data);
1017 
1018 	/* Run dialog. */
1019 
1020 	gtk_window_set_transient_for (GTK_WINDOW (data->dialog), GTK_WINDOW (browser));
1021 	gtk_window_set_modal (GTK_WINDOW (data->dialog), FALSE);
1022 	gtk_widget_show (data->dialog);
1023 
1024 	dlg_rename_series_update_preview (data);
1025 }
1026