1 /* EasyTAG - Tag editor for audio files
2 * Copyright (C) 2014-2015 David King <amigadave@amigadave.com>
3 * Copyright (C) 2000-2003 Jerome Couderc <easytag@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, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20 #include "config.h"
21
22 #include "scan_dialog.h"
23
24 #include <string.h>
25 #include <stdlib.h>
26 #include <gdk/gdkkeysyms.h>
27 #include <glib/gi18n.h>
28
29 #include "application_window.h"
30 #include "easytag.h"
31 #include "enums.h"
32 #include "preferences_dialog.h"
33 #include "scan.h"
34 #include "setting.h"
35 #include "id3_tag.h"
36 #include "browser.h"
37 #include "log.h"
38 #include "misc.h"
39 #include "et_core.h"
40 #include "crc32.h"
41 #include "charset.h"
42
43 typedef struct
44 {
45 GtkListStore *rename_masks_model;
46 GtkListStore *fill_masks_model;
47
48 GtkWidget *mask_entry;
49 GtkWidget *mask_view;
50
51 GtkWidget *notebook;
52 GtkWidget *fill_grid;
53 GtkWidget *rename_grid;
54
55 GtkWidget *fill_combo;
56 GtkWidget *rename_combo;
57
58 GtkWidget *legend_grid;
59 GtkWidget *editor_grid;
60
61 GtkWidget *legend_toggle;
62 GtkWidget *mask_editor_toggle;
63
64 GtkWidget *process_filename_check;
65 GtkWidget *process_title_check;
66 GtkWidget *process_artist_check;
67 GtkWidget *process_album_artist_check;
68 GtkWidget *process_album_check;
69 GtkWidget *process_genre_check;
70 GtkWidget *process_comment_check;
71 GtkWidget *process_composer_check;
72 GtkWidget *process_orig_artist_check;
73 GtkWidget *process_copyright_check;
74 GtkWidget *process_url_check;
75 GtkWidget *process_encoded_by_check;
76
77 GtkWidget *convert_space_radio;
78 GtkWidget *convert_underscores_radio;
79 GtkWidget *convert_string_radio;
80 GtkWidget *convert_none_radio;
81 GtkWidget *convert_to_entry;
82 GtkWidget *convert_from_entry;
83 GtkWidget *convert_to_label;
84
85 GtkWidget *capitalize_all_radio;
86 GtkWidget *capitalize_lower_radio;
87 GtkWidget *capitalize_first_radio;
88 GtkWidget *capitalize_first_style_radio;
89 GtkWidget *capitalize_roman_check;
90
91 GtkWidget *spaces_remove_radio;
92 GtkWidget *spaces_insert_radio;
93 GtkWidget *spaces_insert_one_radio;
94
95 GtkWidget *fill_preview_label;
96 GtkWidget *rename_preview_label;
97 } EtScanDialogPrivate;
98
99 G_DEFINE_TYPE_WITH_PRIVATE (EtScanDialog, et_scan_dialog, GTK_TYPE_DIALOG)
100
101 /* Some predefined masks -- IMPORTANT: Null-terminate me! */
102 static const gchar *Scan_Masks [] =
103 {
104 "%a - %b"G_DIR_SEPARATOR_S"%n - %t",
105 "%a_-_%b"G_DIR_SEPARATOR_S"%n_-_%t",
106 "%a - %b (%y)"G_DIR_SEPARATOR_S"%n - %a - %t",
107 "%a_-_%b_(%y)"G_DIR_SEPARATOR_S"%n_-_%a_-_%t",
108 "%a - %b (%y) - %g"G_DIR_SEPARATOR_S"%n - %a - %t",
109 "%a_-_%b_(%y)_-_%g"G_DIR_SEPARATOR_S"%n_-_%a_-_%t",
110 "%a - %b"G_DIR_SEPARATOR_S"%n. %t",
111 "%a_-_%b"G_DIR_SEPARATOR_S"%n._%t",
112 "%a-%b"G_DIR_SEPARATOR_S"%n-%t",
113 "%b"G_DIR_SEPARATOR_S"%n. %a - %t",
114 "%b"G_DIR_SEPARATOR_S"%n._%a_-_%t",
115 "%b"G_DIR_SEPARATOR_S"%n - %a - %t",
116 "%b"G_DIR_SEPARATOR_S"%n_-_%a_-_%t",
117 "%b"G_DIR_SEPARATOR_S"%n-%a-%t",
118 "%a-%b"G_DIR_SEPARATOR_S"%n-%t",
119 "%a"G_DIR_SEPARATOR_S"%b"G_DIR_SEPARATOR_S"%n. %t",
120 "%g"G_DIR_SEPARATOR_S"%a"G_DIR_SEPARATOR_S"%b"G_DIR_SEPARATOR_S"%t",
121 "%a_-_%b-%n-%t-%y",
122 "%a - %b"G_DIR_SEPARATOR_S"%n. %t(%c)",
123 "%t",
124 "Track%n",
125 "Track%i %n",
126 NULL
127 };
128
129 static const gchar *Rename_File_Masks [] =
130 {
131 "%n - %a - %t",
132 "%n_-_%a_-_%t",
133 "%n. %a - %t",
134 "%n._%a_-_%t",
135 "%a - %b"G_DIR_SEPARATOR_S"%n - %t",
136 "%a_-_%b"G_DIR_SEPARATOR_S"%n_-_%t",
137 "%a - %b (%y) - %g"G_DIR_SEPARATOR_S"%n - %t",
138 "%a_-_%b_(%y)_-_%g"G_DIR_SEPARATOR_S"%n_-_%t",
139 "%n - %t",
140 "%n_-_%t",
141 "%n. %t",
142 "%n._%t",
143 "%n - %a - %b - %t",
144 "%n_-_%a_-_%b_-_%t",
145 "%a - %b - %t",
146 "%a_-_%b_-_%t",
147 "%a - %b - %n - %t",
148 "%a_-_%b_-_%n_-_%t",
149 "%a - %t",
150 "%a_-_%t",
151 "Track %n",
152 NULL
153 };
154
155 /**gchar *Rename_Directory_Masks [] =
156 {
157 "%a - %b",
158 "%a_-_%b",
159 "%a - %b (%y) - %g",
160 "%a_-_%b_(%y)_-_%g",
161 "VA - %b (%y)",
162 "VA_-_%b_(%y)",
163 NULL
164 };**/
165
166
167 typedef enum
168 {
169 UNKNOWN = 0, /* Default value when initialized */
170 LEADING_SEPARATOR, /* characters before the first code */
171 TRAILING_SEPARATOR, /* characters after the last code */
172 SEPARATOR, /* item is a separator between two codes */
173 DIRECTORY_SEPARATOR, /* item is a separator between two codes with character '/' (G_DIR_SEPARATOR) */
174 FIELD, /* item contains text (not empty) of entry */
175 EMPTY_FIELD /* item when entry contains no text */
176 } Mask_Item_Type;
177
178
179 enum {
180 MASK_EDITOR_TEXT,
181 MASK_EDITOR_COUNT
182 };
183
184 /*
185 * Used into Rename File Scanner
186 */
187 typedef struct _File_Mask_Item File_Mask_Item;
188 struct _File_Mask_Item
189 {
190 Mask_Item_Type type;
191 gchar *string;
192 };
193
194 /*
195 * Used into Scan Tag Scanner
196 */
197 typedef struct _Scan_Mask_Item Scan_Mask_Item;
198 struct _Scan_Mask_Item
199 {
200 gchar code; // The code of the mask without % (ex: %a => a)
201 gchar *string; // The string found by the scanner for the code defined the line above
202 };
203
204
205
206 /**************
207 * Prototypes *
208 **************/
209 static void Scan_Option_Button (void);
210 static void entry_check_scan_tag_mask (GtkEntry *entry, gpointer user_data);
211
212 static GList *Scan_Generate_New_Tag_From_Mask (ET_File *ETFile, gchar *mask);
213 static void Scan_Free_File_Rename_List (GList *list);
214 static void Scan_Free_File_Fill_Tag_List (GList *list);
215
216 static void et_scan_on_response (GtkDialog *dialog, gint response_id,
217 gpointer user_data);
218
219
220 /*************
221 * Functions *
222 *************/
223
224 /*
225 * Return the field of a 'File_Tag' structure corresponding to the mask code
226 */
227 static gchar **
Scan_Return_File_Tag_Field_From_Mask_Code(File_Tag * FileTag,gchar code)228 Scan_Return_File_Tag_Field_From_Mask_Code (File_Tag *FileTag, gchar code)
229 {
230 switch (code)
231 {
232 case 't': /* Title */
233 return &FileTag->title;
234 case 'a': /* Artist */
235 return &FileTag->artist;
236 case 'b': /* Album */
237 return &FileTag->album;
238 case 'd': /* Disc Number */
239 return &FileTag->disc_number;
240 case 'x': /* Total number of discs. */
241 return &FileTag->disc_total;
242 case 'y': /* Year */
243 return &FileTag->year;
244 case 'n': /* Track */
245 return &FileTag->track;
246 case 'l': /* Track Total */
247 return &FileTag->track_total;
248 case 'g': /* Genre */
249 return &FileTag->genre;
250 case 'c': /* Comment */
251 return &FileTag->comment;
252 case 'p': /* Composer */
253 return &FileTag->composer;
254 case 'o': /* Orig. Artist */
255 return &FileTag->orig_artist;
256 case 'r': /* Copyright */
257 return &FileTag->copyright;
258 case 'u': /* URL */
259 return &FileTag->url;
260 case 'e': /* Encoded by */
261 return &FileTag->encoded_by;
262 case 'z': /* Album Artist */
263 return &FileTag->album_artist;
264 case 'i': /* Ignored */
265 return NULL;
266 default:
267 Log_Print(LOG_ERROR,"Scanner: Invalid code '%%%c' found!",code);
268 return NULL;
269 }
270 }
271
272 static void
et_scan_dialog_set_file_tag_for_mask_item(File_Tag * file_tag,Scan_Mask_Item * item)273 et_scan_dialog_set_file_tag_for_mask_item (File_Tag *file_tag,
274 Scan_Mask_Item *item)
275 {
276 switch (item->code)
277 {
278 case 't':
279 et_file_tag_set_title (file_tag, item->string);
280 break;
281 case 'a':
282 et_file_tag_set_artist (file_tag, item->string);
283 break;
284 case 'b':
285 et_file_tag_set_album (file_tag, item->string);
286 break;
287 case 'd':
288 et_file_tag_set_disc_number (file_tag, item->string);
289 break;
290 case 'x':
291 et_file_tag_set_disc_total (file_tag, item->string);
292 break;
293 case 'y':
294 et_file_tag_set_year (file_tag, item->string);
295 break;
296 case 'n':
297 et_file_tag_set_track_number (file_tag, item->string);
298 break;
299 case 'l':
300 et_file_tag_set_track_total (file_tag, item->string);
301 break;
302 case 'g':
303 et_file_tag_set_genre (file_tag, item->string);
304 break;
305 case 'c':
306 et_file_tag_set_comment (file_tag, item->string);
307 break;
308 case 'p':
309 et_file_tag_set_composer (file_tag, item->string);
310 break;
311 case 'o':
312 et_file_tag_set_orig_artist (file_tag, item->string);
313 break;
314 case 'r':
315 et_file_tag_set_copyright (file_tag, item->string);
316 break;
317 case 'u':
318 et_file_tag_set_url (file_tag, item->string);
319 break;
320 case 'e':
321 et_file_tag_set_encoded_by (file_tag, item->string);
322 break;
323 case 'z':
324 et_file_tag_set_album_artist (file_tag, item->string);
325 break;
326 case 'i':
327 break;
328 default:
329 Log_Print (LOG_ERROR, "Scanner: Invalid code '%%%c' found!",
330 item->code);
331 break;
332 }
333 }
334
335 /*
336 * Uses the filename and path to fill tag information
337 * Note: mask and source are read from the right to the left
338 */
339 static void
Scan_Tag_With_Mask(EtScanDialog * self,ET_File * ETFile)340 Scan_Tag_With_Mask (EtScanDialog *self, ET_File *ETFile)
341 {
342 EtScanDialogPrivate *priv;
343 GList *fill_tag_list = NULL;
344 GList *l;
345 gchar *mask; // The 'mask' in the entry
346 gchar *filename_utf8;
347 File_Tag *FileTag;
348
349 g_return_if_fail (ETFile != NULL);
350
351 priv = et_scan_dialog_get_instance_private (self);
352
353 mask = g_strdup (gtk_entry_get_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (priv->fill_combo)))));
354 if (!mask) return;
355
356 // Create a new File_Tag item
357 FileTag = et_file_tag_new ();
358 et_file_tag_copy_into (FileTag, ETFile->FileTag->data);
359
360 // Process this mask with file
361 fill_tag_list = Scan_Generate_New_Tag_From_Mask(ETFile,mask);
362
363 for (l = fill_tag_list; l != NULL; l = g_list_next (l))
364 {
365 Scan_Mask_Item *mask_item = l->data;
366
367 /* We display the text affected to the code. */
368 if (g_settings_get_boolean (MainSettings, "fill-overwrite-tag-fields"))
369 {
370 et_scan_dialog_set_file_tag_for_mask_item (FileTag, mask_item);
371 }
372
373 }
374
375 Scan_Free_File_Fill_Tag_List(fill_tag_list);
376
377 /* Set the default text to comment. */
378 if (g_settings_get_boolean (MainSettings, "fill-set-default-comment")
379 && (g_settings_get_boolean (MainSettings, "fill-overwrite-tag-fields")
380 || et_str_empty (FileTag->comment)))
381 {
382 gchar *default_comment = g_settings_get_string (MainSettings,
383 "fill-default-comment");
384 et_file_tag_set_comment (FileTag, default_comment);
385 g_free (default_comment);
386 }
387
388 /* Set CRC-32 value as default comment (for files with ID3 tag only). */
389 if (g_settings_get_boolean (MainSettings, "fill-crc32-comment")
390 && (g_settings_get_boolean (MainSettings, "fill-overwrite-tag-fields")
391 || et_str_empty (FileTag->comment)))
392 {
393 GFile *file;
394 GError *error = NULL;
395 guint32 crc32_value;
396 gchar *buffer;
397
398 if (ETFile->ETFileDescription == ID3_TAG)
399 {
400 file = g_file_new_for_path (((File_Name *)((GList *)ETFile->FileNameNew)->data)->value);
401
402 if (crc32_file_with_ID3_tag (file, &crc32_value, &error))
403 {
404 buffer = g_strdup_printf ("%.8" G_GUINT32_FORMAT,
405 crc32_value);
406 et_file_tag_set_comment (FileTag, buffer);
407 g_free(buffer);
408 }
409 else
410 {
411 Log_Print (LOG_ERROR,
412 _("Cannot calculate CRC value of file ‘%s’"),
413 error->message);
414 g_error_free (error);
415 }
416
417 g_object_unref (file);
418 }
419 }
420
421
422 // Save changes of the 'File_Tag' item
423 ET_Manage_Changes_Of_File_Data(ETFile,NULL,FileTag);
424
425 g_free(mask);
426 et_application_window_status_bar_message (ET_APPLICATION_WINDOW (MainWindow),
427 _("Tag successfully scanned"),
428 TRUE);
429 filename_utf8 = g_path_get_basename( ((File_Name *)ETFile->FileNameNew->data)->value_utf8 );
430 Log_Print (LOG_OK, _("Tag successfully scanned ‘%s’"), filename_utf8);
431 g_free(filename_utf8);
432 }
433
434 static GList *
Scan_Generate_New_Tag_From_Mask(ET_File * ETFile,gchar * mask)435 Scan_Generate_New_Tag_From_Mask (ET_File *ETFile, gchar *mask)
436 {
437 GList *fill_tag_list = NULL;
438 gchar *filename_utf8;
439 gchar *tmp;
440 gchar *buf;
441 gchar *separator;
442 gchar *string;
443 gsize len, i, loop=0;
444 gchar **mask_splitted;
445 gchar **file_splitted;
446 guint mask_splitted_number;
447 guint file_splitted_number;
448 guint mask_splitted_index;
449 guint file_splitted_index;
450 Scan_Mask_Item *mask_item;
451 EtConvertSpaces convert_mode;
452
453 g_return_val_if_fail (ETFile != NULL && mask != NULL, NULL);
454
455 filename_utf8 = g_strdup(((File_Name *)((GList *)ETFile->FileNameNew)->data)->value_utf8);
456 if (!filename_utf8) return NULL;
457
458 // Remove extension of file (if found)
459 tmp = strrchr(filename_utf8,'.');
460 for (i = 0; i <= ET_FILE_DESCRIPTION_SIZE; i++)
461 {
462 if ( strcasecmp(tmp,ETFileDescription[i].Extension)==0 )
463 {
464 *tmp = 0; //strrchr(source,'.') = 0;
465 break;
466 }
467 }
468
469 if (i==ET_FILE_DESCRIPTION_SIZE)
470 {
471 gchar *tmp1 = g_path_get_basename(filename_utf8);
472 Log_Print (LOG_ERROR,
473 _("The extension ‘%s’ was not found in filename ‘%s’"), tmp,
474 tmp1);
475 g_free(tmp1);
476 }
477
478 /* Replace characters into mask and filename before parsing. */
479 convert_mode = g_settings_get_enum (MainSettings, "fill-convert-spaces");
480
481 switch (convert_mode)
482 {
483 case ET_CONVERT_SPACES_SPACES:
484 Scan_Convert_Underscore_Into_Space (mask);
485 Scan_Convert_Underscore_Into_Space (filename_utf8);
486 Scan_Convert_P20_Into_Space (mask);
487 Scan_Convert_P20_Into_Space (filename_utf8);
488 break;
489 case ET_CONVERT_SPACES_UNDERSCORES:
490 Scan_Convert_Space_Into_Underscore (mask);
491 Scan_Convert_Space_Into_Underscore (filename_utf8);
492 break;
493 case ET_CONVERT_SPACES_NO_CHANGE:
494 break;
495 /* FIXME: Check if this is intentional. */
496 case ET_CONVERT_SPACES_REMOVE:
497 default:
498 g_assert_not_reached ();
499 }
500
501 // Split the Scanner mask
502 mask_splitted = g_strsplit(mask,G_DIR_SEPARATOR_S,0);
503 // Get number of arguments into 'mask_splitted'
504 for (mask_splitted_number=0;mask_splitted[mask_splitted_number];mask_splitted_number++);
505
506 // Split the File Path
507 file_splitted = g_strsplit(filename_utf8,G_DIR_SEPARATOR_S,0);
508 // Get number of arguments into 'file_splitted'
509 for (file_splitted_number=0;file_splitted[file_splitted_number];file_splitted_number++);
510
511 // Set the starting position for each tab
512 if (mask_splitted_number <= file_splitted_number)
513 {
514 mask_splitted_index = 0;
515 file_splitted_index = file_splitted_number - mask_splitted_number;
516 }else
517 {
518 mask_splitted_index = mask_splitted_number - file_splitted_number;
519 file_splitted_index = 0;
520 }
521
522 loop = 0;
523 while ( mask_splitted[mask_splitted_index]!= NULL && file_splitted[file_splitted_index]!=NULL )
524 {
525 gchar *mask_seq = mask_splitted[mask_splitted_index];
526 gchar *file_seq = file_splitted[file_splitted_index];
527 gchar *file_seq_utf8 = g_filename_display_name (file_seq);
528
529 //g_print(">%d> seq '%s' '%s'\n",loop,mask_seq,file_seq);
530 while (!et_str_empty (mask_seq))
531 {
532
533 /*
534 * Determine (first) code and destination
535 */
536 if ( (tmp=strchr(mask_seq,'%')) == NULL || strlen(tmp) < 2 )
537 {
538 break;
539 }
540
541 /*
542 * Allocate a new iten for the fill_tag_list
543 */
544 mask_item = g_slice_new0 (Scan_Mask_Item);
545
546 // Get the code (used to determine the corresponding target entry)
547 mask_item->code = tmp[1];
548
549 /*
550 * Delete text before the code
551 */
552 if ( (len = strlen(mask_seq) - strlen(tmp)) > 0 )
553 {
554 // Get this text in 'mask_seq'
555 buf = g_strndup(mask_seq,len);
556 // We remove it in 'mask_seq'
557 mask_seq = mask_seq + len;
558 // Find the same text at the begining of 'file_seq' ?
559 if ( (strstr(file_seq,buf)) == file_seq )
560 {
561 file_seq = file_seq + len; // We remove it
562 }else
563 {
564 Log_Print (LOG_ERROR,
565 _("Cannot find separator ‘%s’ within ‘%s’"),
566 buf, file_seq_utf8);
567 }
568 g_free(buf);
569 }
570
571 // Remove the current code into 'mask_seq'
572 mask_seq = mask_seq + 2;
573
574 /*
575 * Determine separator between two code or trailing text (after code)
576 */
577 if (!et_str_empty (mask_seq))
578 {
579 if ( (tmp=strchr(mask_seq,'%')) == NULL || strlen(tmp) < 2 )
580 {
581 // No more code found
582 len = strlen(mask_seq);
583 }else
584 {
585 len = strlen(mask_seq) - strlen(tmp);
586 }
587 separator = g_strndup(mask_seq,len);
588
589 // Remove the current separator in 'mask_seq'
590 mask_seq = mask_seq + len;
591
592 // Try to find the separator in 'file_seq'
593 if ( (tmp=strstr(file_seq,separator)) == NULL )
594 {
595 Log_Print (LOG_ERROR,
596 _("Cannot find separator ‘%s’ within ‘%s’"),
597 separator, file_seq_utf8);
598 separator[0] = 0; // Needed to avoid error when calculting 'len' below
599 }
600
601 // Get the string affected to the code (or the corresponding entry field)
602 len = strlen(file_seq) - (tmp!=NULL?strlen(tmp):0);
603 string = g_strndup(file_seq,len);
604
605 // Remove the current separator in 'file_seq'
606 file_seq = file_seq + strlen(string) + strlen(separator);
607 g_free(separator);
608
609 // We get the text affected to the code
610 mask_item->string = string;
611 }else
612 {
613 // We display the remaining text, affected to the code (no more data in 'mask_seq')
614 mask_item->string = g_strdup(file_seq);
615 }
616
617 // Add the filled mask_iten to the list
618 fill_tag_list = g_list_append(fill_tag_list,mask_item);
619 }
620
621 g_free(file_seq_utf8);
622
623 // Next sequences
624 mask_splitted_index++;
625 file_splitted_index++;
626 loop++;
627 }
628
629 g_free(filename_utf8);
630 g_strfreev(mask_splitted);
631 g_strfreev(file_splitted);
632
633 // The 'fill_tag_list' must be freed after use
634 return fill_tag_list;
635 }
636
637 static void
Scan_Fill_Tag_Generate_Preview(EtScanDialog * self)638 Scan_Fill_Tag_Generate_Preview (EtScanDialog *self)
639 {
640 EtScanDialogPrivate *priv;
641 gchar *mask = NULL;
642 gchar *preview_text = NULL;
643 GList *fill_tag_list = NULL;
644 GList *l;
645
646 priv = et_scan_dialog_get_instance_private (self);
647
648 if (!ETCore->ETFileDisplayedList
649 || gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook)) != ET_SCAN_MODE_FILL_TAG)
650 return;
651
652 mask = g_strdup (gtk_entry_get_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (priv->fill_combo)))));
653 if (!mask)
654 return;
655
656 preview_text = g_strdup("");
657 fill_tag_list = Scan_Generate_New_Tag_From_Mask(ETCore->ETFileDisplayed,mask);
658 for (l = fill_tag_list; l != NULL; l = g_list_next (l))
659 {
660 Scan_Mask_Item *mask_item = l->data;
661 gchar *tmp_code = g_strdup_printf("%c",mask_item->code);
662 gchar *tmp_string = g_markup_printf_escaped("%s",mask_item->string); // To avoid problem with strings containing characters like '&'
663 gchar *tmp_preview_text = preview_text;
664
665 preview_text = g_strconcat(tmp_preview_text,"<b>","%",tmp_code," = ",
666 "</b>","<i>",tmp_string,"</i>",NULL);
667 g_free(tmp_code);
668 g_free(tmp_string);
669 g_free(tmp_preview_text);
670
671 tmp_preview_text = preview_text;
672 preview_text = g_strconcat(tmp_preview_text," || ",NULL);
673 g_free(tmp_preview_text);
674 }
675
676 Scan_Free_File_Fill_Tag_List(fill_tag_list);
677
678 if (GTK_IS_LABEL (priv->fill_preview_label))
679 {
680 if (preview_text)
681 {
682 //gtk_label_set_text(GTK_LABEL(priv->fill_preview_label),preview_text);
683 gtk_label_set_markup (GTK_LABEL (priv->fill_preview_label),
684 preview_text);
685 } else
686 {
687 gtk_label_set_text (GTK_LABEL (priv->fill_preview_label), "");
688 }
689
690 /* Force the window to be redrawn. */
691 gtk_widget_queue_resize (GTK_WIDGET (self));
692 }
693
694 g_free(mask);
695 g_free(preview_text);
696 }
697
698 static void
Scan_Rename_File_Generate_Preview(EtScanDialog * self)699 Scan_Rename_File_Generate_Preview (EtScanDialog *self)
700 {
701 EtScanDialogPrivate *priv;
702 gchar *preview_text = NULL;
703 gchar *mask = NULL;
704
705 priv = et_scan_dialog_get_instance_private (self);
706
707 if (!ETCore->ETFileDisplayed || !priv->rename_combo
708 || !priv->rename_preview_label)
709 {
710 return;
711 }
712
713 if (gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook)) != ET_SCAN_MODE_RENAME_FILE)
714 return;
715
716 mask = g_strdup (gtk_entry_get_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (priv->rename_combo)))));
717 if (!mask)
718 return;
719
720 preview_text = et_scan_generate_new_filename_from_mask (ETCore->ETFileDisplayed,
721 mask, FALSE);
722
723 if (GTK_IS_LABEL (priv->rename_preview_label))
724 {
725 if (preview_text)
726 {
727 //gtk_label_set_text(GTK_LABEL(priv->rename_preview_label),preview_text);
728 gchar *tmp_string = g_markup_printf_escaped("%s",preview_text); // To avoid problem with strings containing characters like '&'
729 gchar *str = g_strdup_printf("<i>%s</i>",tmp_string);
730 gtk_label_set_markup (GTK_LABEL (priv->rename_preview_label), str);
731 g_free(tmp_string);
732 g_free(str);
733 } else
734 {
735 gtk_label_set_text (GTK_LABEL (priv->rename_preview_label), "");
736 }
737
738 /* Force the window to be redrawn. */
739 gtk_widget_queue_resize (GTK_WIDGET (self));
740 }
741
742 g_free(mask);
743 g_free(preview_text);
744 }
745
746
747 void
et_scan_dialog_update_previews(EtScanDialog * self)748 et_scan_dialog_update_previews (EtScanDialog *self)
749 {
750 g_return_if_fail (ET_SCAN_DIALOG (self));
751
752 Scan_Fill_Tag_Generate_Preview (self);
753 Scan_Rename_File_Generate_Preview (self);
754 }
755
756 static void
Scan_Free_File_Fill_Tag_List(GList * list)757 Scan_Free_File_Fill_Tag_List (GList *list)
758 {
759 GList *l;
760
761 list = g_list_first (list);
762
763 for (l = list; l != NULL; l = g_list_next (l))
764 {
765 if (l->data)
766 {
767 g_free (((Scan_Mask_Item *)l->data)->string);
768 g_slice_free (Scan_Mask_Item, l->data);
769 }
770 }
771
772 g_list_free (list);
773 }
774
775
776
777 /**************************
778 * Scanner To Rename File *
779 **************************/
780 /*
781 * Uses tag information (displayed into tag entries) to rename file
782 * Note: mask and source are read from the right to the left.
783 * Note1: a mask code may be used severals times...
784 */
785 static void
Scan_Rename_File_With_Mask(EtScanDialog * self,ET_File * ETFile)786 Scan_Rename_File_With_Mask (EtScanDialog *self, ET_File *ETFile)
787 {
788 EtScanDialogPrivate *priv;
789 gchar *filename_generated_utf8 = NULL;
790 gchar *filename_generated = NULL;
791 gchar *filename_new_utf8 = NULL;
792 gchar *mask = NULL;
793 File_Name *FileName;
794
795 g_return_if_fail (ETFile != NULL);
796
797 priv = et_scan_dialog_get_instance_private (self);
798
799 mask = g_strdup (gtk_entry_get_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (priv->rename_combo)))));
800 if (!mask) return;
801
802 // Note : if the first character is '/', we have a path with the filename,
803 // else we have only the filename. The both are in UTF-8.
804 filename_generated_utf8 = et_scan_generate_new_filename_from_mask (ETFile,
805 mask,
806 FALSE);
807 g_free(mask);
808
809 if (et_str_empty (filename_generated_utf8))
810 {
811 g_free (filename_generated_utf8);
812 return;
813 }
814
815 // Convert filename to file-system encoding
816 filename_generated = filename_from_display(filename_generated_utf8);
817 if (!filename_generated)
818 {
819 GtkWidget *msgdialog;
820 msgdialog = gtk_message_dialog_new (GTK_WINDOW (self),
821 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
822 GTK_MESSAGE_ERROR,
823 GTK_BUTTONS_CLOSE,
824 _("Could not convert filename ‘%s’ into system filename encoding"),
825 filename_generated_utf8);
826 gtk_window_set_title(GTK_WINDOW(msgdialog),_("Filename translation"));
827
828 gtk_dialog_run(GTK_DIALOG(msgdialog));
829 gtk_widget_destroy(msgdialog);
830 g_free(filename_generated_utf8);
831 return;
832 }
833
834 /* Build the filename with the full path or relative to old path */
835 filename_new_utf8 = et_file_generate_name (ETFile,
836 filename_generated_utf8);
837 g_free(filename_generated);
838 g_free(filename_generated_utf8);
839
840 /* Set the new filename */
841 /* Create a new 'File_Name' item. */
842 FileName = et_file_name_new ();
843 // Save changes of the 'File_Name' item
844 ET_Set_Filename_File_Name_Item(FileName,filename_new_utf8,NULL);
845
846 ET_Manage_Changes_Of_File_Data(ETFile,FileName,NULL);
847 g_free(filename_new_utf8);
848
849 et_application_window_status_bar_message (ET_APPLICATION_WINDOW (MainWindow),
850 _("New filename successfully scanned"),
851 TRUE);
852
853 filename_new_utf8 = g_path_get_basename(((File_Name *)ETFile->FileNameNew->data)->value_utf8);
854 Log_Print (LOG_OK, _("New filename successfully scanned ‘%s’"),
855 filename_new_utf8);
856 g_free(filename_new_utf8);
857
858 return;
859 }
860
861 /*
862 * Build the new filename using tag + mask
863 * Used also to rename the directory (from the browser)
864 * @param ETFile : the etfile to process
865 * @param mask : the pattern to parse
866 * @param no_dir_check_or_conversion : if FALSE, disable checking of a directory
867 * in the mask, and don't convert "illegal" characters. This is used in the
868 * function "Write_Playlist" for the content of the playlist.
869 * Returns filename in UTF-8
870 */
871 gchar *
et_scan_generate_new_filename_from_mask(const ET_File * ETFile,const gchar * mask,gboolean no_dir_check_or_conversion)872 et_scan_generate_new_filename_from_mask (const ET_File *ETFile,
873 const gchar *mask,
874 gboolean no_dir_check_or_conversion)
875 {
876 gchar *tmp;
877 gchar **source = NULL;
878 gchar *path_utf8_cur = NULL;
879 gchar *filename_new_utf8 = NULL;
880 gchar *filename_tmp = NULL;
881 GList *rename_file_list = NULL;
882 GList *l;
883 File_Mask_Item *mask_item;
884 File_Mask_Item *mask_item_prev;
885 File_Mask_Item *mask_item_next;
886 gint counter = 0;
887
888 g_return_val_if_fail (ETFile != NULL && mask != NULL, NULL);
889
890 /*
891 * Check for a directory in the mask
892 */
893 if (!no_dir_check_or_conversion)
894 {
895 if (g_path_is_absolute(mask))
896 {
897 // Absolute directory
898 }else if (strrchr(mask,G_DIR_SEPARATOR)!=NULL) // This is '/' on UNIX machines and '\' under Windows
899 {
900 // Relative path => set beginning of the path
901 path_utf8_cur = g_path_get_dirname( ((File_Name *)ETFile->FileNameCur->data)->value_utf8 );
902 }
903 }
904
905
906 /*
907 * Parse the codes to generate a list (1rst item = 1rst code)
908 */
909 while ( mask!=NULL && (tmp=strrchr(mask,'%'))!=NULL && strlen(tmp)>1 )
910 {
911 // Mask contains some characters after the code ('%b__')
912 if (strlen(tmp)>2)
913 {
914 mask_item = g_slice_new0 (File_Mask_Item);
915 if (counter)
916 {
917 if (strchr(tmp+2,G_DIR_SEPARATOR))
918 mask_item->type = DIRECTORY_SEPARATOR;
919 else
920 mask_item->type = SEPARATOR;
921 } else
922 {
923 mask_item->type = TRAILING_SEPARATOR;
924 }
925 mask_item->string = g_strdup(tmp+2);
926 rename_file_list = g_list_prepend(rename_file_list,mask_item);
927 }
928
929 // Now, parses the code to get the corresponding string (from tag)
930 source = Scan_Return_File_Tag_Field_From_Mask_Code((File_Tag *)ETFile->FileTag->data,tmp[1]);
931 mask_item = g_slice_new0 (File_Mask_Item);
932
933 if (source && !et_str_empty (*source))
934 {
935 mask_item->type = FIELD;
936 mask_item->string = g_strdup(*source);
937
938 // Replace invalid characters for this field
939 /* Do not replace characters in a playlist information field. */
940 if (!no_dir_check_or_conversion)
941 {
942 EtConvertSpaces convert_mode;
943
944 convert_mode = g_settings_get_enum (MainSettings,
945 "rename-convert-spaces");
946
947 switch (convert_mode)
948 {
949 case ET_CONVERT_SPACES_SPACES:
950 Scan_Convert_Underscore_Into_Space (mask_item->string);
951 Scan_Convert_P20_Into_Space (mask_item->string);
952 break;
953 case ET_CONVERT_SPACES_UNDERSCORES:
954 Scan_Convert_Space_Into_Underscore (mask_item->string);
955 break;
956 case ET_CONVERT_SPACES_REMOVE:
957 Scan_Remove_Spaces (mask_item->string);
958 break;
959 /* FIXME: Check that this is intended. */
960 case ET_CONVERT_SPACES_NO_CHANGE:
961 default:
962 g_assert_not_reached ();
963 }
964
965 /* This must occur after the space processing, to ensure that a
966 * trailing space cannot be present (if illegal characters are to be
967 * replaced). */
968 et_filename_prepare (mask_item->string,
969 g_settings_get_boolean (MainSettings,
970 "rename-replace-illegal-chars"));
971 }
972 }else
973 {
974 mask_item->type = EMPTY_FIELD;
975 mask_item->string = NULL;
976 }
977 rename_file_list = g_list_prepend(rename_file_list,mask_item);
978 *tmp = '\0'; // Cut parsed data of mask
979 counter++; // To indicate that we made at least one loop to identifiate 'separator' or 'trailing_separator'
980 }
981
982 // It may have some characters before the last remaining code ('__%a')
983 if (!et_str_empty (mask))
984 {
985 mask_item = g_slice_new0 (File_Mask_Item);
986 mask_item->type = LEADING_SEPARATOR;
987 mask_item->string = g_strdup(mask);
988 rename_file_list = g_list_prepend(rename_file_list,mask_item);
989 }
990
991 if (!rename_file_list) return NULL;
992
993 /*
994 * For Debugging : display the "rename_file_list" list
995 */
996 /***{
997 GList *list = g_list_first(rename_file_list);
998 gint i = 0;
999 g_print("## rename_file_list - start\n");
1000 while (list)
1001 {
1002 File_Mask_Item *mask_item = (File_Mask_Item *)list->data;
1003 Mask_Item_Type type = mask_item->type;
1004 gchar *string = mask_item->string;
1005
1006 //g_print("item %d : \n",i++);
1007 //g_print(" - type : '%s'\n",type==UNKNOWN?"UNKNOWN":type==LEADING_SEPARATOR?"LEADING_SEPARATOR":type==TRAILING_SEPARATOR?"TRAILING_SEPARATOR":type==SEPARATOR?"SEPARATOR":type==DIRECTORY_SEPARATOR?"DIRECTORY_SEPARATOR":type==FIELD?"FIELD":type==EMPTY_FIELD?"EMPTY_FIELD":"???");
1008 //g_print(" - string : '%s'\n",string);
1009 g_print("%d -> %s (%s) | ",i++,type==UNKNOWN?"UNKNOWN":type==LEADING_SEPARATOR?"LEADING_SEPARATOR":type==TRAILING_SEPARATOR?"TRAILING_SEPARATOR":type==SEPARATOR?"SEPARATOR":type==DIRECTORY_SEPARATOR?"DIRECTORY_SEPARATOR":type==FIELD?"FIELD":type==EMPTY_FIELD?"EMPTY_FIELD":"???",string);
1010
1011 list = list->next;
1012 }
1013 g_print("\n## rename_file_list - end\n\n");
1014 }***/
1015
1016 /*
1017 * Build the new filename with items placed into the list
1018 * (read the list from the end to the beginning)
1019 */
1020 filename_new_utf8 = g_strdup("");
1021
1022 for (l = g_list_last (rename_file_list); l != NULL;
1023 l = g_list_previous (l))
1024 {
1025 File_Mask_Item *mask_item2 = l->data;
1026
1027 /* Trailing mask characters. */
1028 if (mask_item2->type == TRAILING_SEPARATOR)
1029 {
1030 // Doesn't write it if previous field is empty
1031 if (l->prev
1032 && ((File_Mask_Item *)l->prev->data)->type != EMPTY_FIELD)
1033 {
1034 filename_tmp = filename_new_utf8;
1035 filename_new_utf8 = g_strconcat (mask_item2->string,
1036 filename_new_utf8, NULL);
1037 g_free(filename_tmp);
1038 }
1039 }
1040 else if (mask_item2->type == EMPTY_FIELD)
1041 // We don't concatenate the field value (empty) and the previous
1042 // separator (except leading separator) to the filename.
1043 // If the empty field is the 'first', we don't concatenate it, and the
1044 // next separator too.
1045 {
1046 if (l->prev)
1047 {
1048 // The empty field isn't the first.
1049 // If previous string is a separator, we don't use it, except if the next
1050 // string is a FIELD (not empty)
1051 mask_item_prev = l->prev->data;
1052 if ( mask_item_prev->type==SEPARATOR )
1053 {
1054 if (!(l->next
1055 && (mask_item_next = rename_file_list->next->data)
1056 && mask_item_next->type == FIELD))
1057 {
1058 l = l->prev;
1059 }
1060 }
1061 }else
1062 if (l->next && (mask_item_next = l->next->data)
1063 && mask_item_next->type == SEPARATOR)
1064 // We are at the 'beginning' of the mask (so empty field is the first)
1065 // and next field is a separator. As the separator may have been already added, we remove it
1066 {
1067 if ( filename_new_utf8 && mask_item_next->string && (strncmp(filename_new_utf8,mask_item_next->string,strlen(mask_item_next->string))==0) ) // To avoid crash if filename_new_utf8 is 'empty'
1068 {
1069 filename_tmp = filename_new_utf8;
1070 filename_new_utf8 = g_strdup(filename_new_utf8+strlen(mask_item_next->string));
1071 g_free(filename_tmp);
1072 }
1073 }
1074
1075 }else // SEPARATOR, FIELD, LEADING_SEPARATOR, DIRECTORY_SEPARATOR
1076 {
1077 filename_tmp = filename_new_utf8;
1078 filename_new_utf8 = g_strconcat (mask_item2->string,
1079 filename_new_utf8, NULL);
1080 g_free(filename_tmp);
1081 }
1082 }
1083
1084 // Free the list
1085 Scan_Free_File_Rename_List(rename_file_list);
1086
1087
1088 // Add current path if relative path entered
1089 if (path_utf8_cur)
1090 {
1091 filename_tmp = filename_new_utf8; // in UTF-8!
1092 filename_new_utf8 = g_build_filename (path_utf8_cur, filename_new_utf8,
1093 NULL);
1094 g_free(filename_tmp);
1095 g_free(path_utf8_cur);
1096 }
1097
1098 return filename_new_utf8; // in UTF-8!
1099 }
1100
1101 static void
Scan_Free_File_Rename_List(GList * list)1102 Scan_Free_File_Rename_List (GList *list)
1103 {
1104 GList *l;
1105
1106 for (l = list; l != NULL; l = g_list_next (l))
1107 {
1108 if (l->data)
1109 {
1110 g_free (((File_Mask_Item *)l->data)->string);
1111 g_slice_free (File_Mask_Item, l->data);
1112 }
1113 }
1114
1115 g_list_free (list);
1116 }
1117
1118 /*
1119 * Adds the current path of the file to the mask on the "Rename File Scanner" entry
1120 */
1121 static void
Scan_Rename_File_Prefix_Path(EtScanDialog * self)1122 Scan_Rename_File_Prefix_Path (EtScanDialog *self)
1123 {
1124 EtScanDialogPrivate *priv;
1125 gint pos;
1126 gchar *path_tmp;
1127 const gchar *combo_text;
1128 const ET_File *ETFile = ETCore->ETFileDisplayed;
1129 const gchar *filename_utf8_cur;
1130 gchar *path_utf8_cur;
1131
1132 if (!ETFile)
1133 {
1134 return;
1135 }
1136
1137 filename_utf8_cur = ((File_Name *)ETFile->FileNameCur->data)->value_utf8;
1138 priv = et_scan_dialog_get_instance_private (self);
1139
1140 // The path to prefix
1141 path_utf8_cur = g_path_get_dirname(filename_utf8_cur);
1142
1143 /* The current text in the combobox. */
1144 combo_text = gtk_entry_get_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (priv->rename_combo))));
1145
1146 // If the path already exists we don't add it again
1147 // Use g_utf8_collate_key instead of strncmp
1148 if (combo_text && path_utf8_cur
1149 && strncmp (combo_text, path_utf8_cur, strlen (path_utf8_cur)) != 0)
1150 {
1151 if (g_path_is_absolute (combo_text))
1152 {
1153 path_tmp = g_strdup(path_utf8_cur);
1154 } else
1155 {
1156 path_tmp = g_strconcat(path_utf8_cur,G_DIR_SEPARATOR_S,NULL);
1157 }
1158 pos = 0;
1159 gtk_editable_insert_text (GTK_EDITABLE (gtk_bin_get_child (GTK_BIN (priv->rename_combo))),
1160 path_tmp, -1, &pos);
1161 g_free(path_tmp);
1162 }
1163
1164 g_free(path_utf8_cur);
1165 }
1166
1167
1168 gchar *
et_scan_generate_new_directory_name_from_mask(const ET_File * ETFile,const gchar * mask,gboolean no_dir_check_or_conversion)1169 et_scan_generate_new_directory_name_from_mask (const ET_File *ETFile,
1170 const gchar *mask,
1171 gboolean no_dir_check_or_conversion)
1172 {
1173 return et_scan_generate_new_filename_from_mask (ETFile, mask,
1174 no_dir_check_or_conversion);
1175 }
1176
1177
1178 /*
1179 * Replace something with something else ;)
1180 * Here use Regular Expression, to search and replace.
1181 */
1182 static void
Scan_Convert_Character(EtScanDialog * self,gchar ** string)1183 Scan_Convert_Character (EtScanDialog *self, gchar **string)
1184 {
1185 EtScanDialogPrivate *priv;
1186 gchar *from;
1187 gchar *to;
1188 GRegex *regex;
1189 GError *regex_error = NULL;
1190 gchar *new_string;
1191
1192 priv = et_scan_dialog_get_instance_private (self);
1193
1194 from = gtk_editable_get_chars (GTK_EDITABLE (priv->convert_from_entry), 0,
1195 -1);
1196 to = gtk_editable_get_chars (GTK_EDITABLE (priv->convert_to_entry), 0, -1);
1197
1198 regex = g_regex_new (from, 0, 0, ®ex_error);
1199 if (regex_error != NULL)
1200 {
1201 goto handle_error;
1202 }
1203
1204 new_string = g_regex_replace (regex, *string, -1, 0, to, 0, ®ex_error);
1205 if (regex_error != NULL)
1206 {
1207 g_free (new_string);
1208 g_regex_unref (regex);
1209 goto handle_error;
1210 }
1211
1212 /* Success. */
1213 g_regex_unref (regex);
1214 g_free (*string);
1215 *string = new_string;
1216
1217 out:
1218 g_free (from);
1219 g_free (to);
1220 return;
1221
1222 handle_error:
1223 Log_Print (LOG_ERROR, _("Error while processing fields ‘%s’"),
1224 regex_error->message);
1225
1226 g_error_free (regex_error);
1227
1228 goto out;
1229 }
1230
1231 static void
Scan_Process_Fields_Functions(EtScanDialog * self,gchar ** string)1232 Scan_Process_Fields_Functions (EtScanDialog *self,
1233 gchar **string)
1234 {
1235 const EtProcessFieldsConvert process = g_settings_get_enum (MainSettings,
1236 "process-convert");
1237
1238 switch (process)
1239 {
1240 case ET_PROCESS_FIELDS_CONVERT_SPACES:
1241 Scan_Convert_Underscore_Into_Space (*string);
1242 Scan_Convert_P20_Into_Space (*string);
1243 break;
1244 case ET_PROCESS_FIELDS_CONVERT_UNDERSCORES:
1245 Scan_Convert_Space_Into_Underscore (*string);
1246 break;
1247 case ET_PROCESS_FIELDS_CONVERT_CHARACTERS:
1248 Scan_Convert_Character (self, string);
1249 break;
1250 case ET_PROCESS_FIELDS_CONVERT_NO_CHANGE:
1251 break;
1252 default:
1253 g_assert_not_reached ();
1254 break;
1255 }
1256
1257 if (g_settings_get_boolean (MainSettings, "process-insert-capital-spaces"))
1258 {
1259 gchar *res;
1260 res = Scan_Process_Fields_Insert_Space (*string);
1261 g_free (*string);
1262 *string = res;
1263 }
1264
1265 if (g_settings_get_boolean (MainSettings,
1266 "process-remove-duplicate-spaces"))
1267 {
1268 Scan_Process_Fields_Keep_One_Space (*string);
1269 }
1270
1271 if (g_settings_get_boolean (MainSettings, "process-uppercase-all"))
1272 {
1273 gchar *res;
1274 res = Scan_Process_Fields_All_Uppercase (*string);
1275 g_free (*string);
1276 *string = res;
1277 }
1278
1279 if (g_settings_get_boolean (MainSettings, "process-lowercase-all"))
1280 {
1281 gchar *res;
1282 res = Scan_Process_Fields_All_Downcase (*string);
1283 g_free (*string);
1284 *string = res;
1285 }
1286
1287 if (g_settings_get_boolean (MainSettings,
1288 "process-uppercase-first-letter"))
1289 {
1290 gchar *res;
1291 res = Scan_Process_Fields_Letter_Uppercase (*string);
1292 g_free (*string);
1293 *string = res;
1294 }
1295
1296 if (g_settings_get_boolean (MainSettings,
1297 "process-uppercase-first-letters"))
1298 {
1299 gboolean uppercase_preps;
1300 gboolean handle_roman;
1301
1302 uppercase_preps = g_settings_get_boolean (MainSettings,
1303 "process-uppercase-prepositions");
1304 handle_roman = g_settings_get_boolean (MainSettings,
1305 "process-detect-roman-numerals");
1306 Scan_Process_Fields_First_Letters_Uppercase (string, uppercase_preps,
1307 handle_roman);
1308 }
1309
1310 if (g_settings_get_boolean (MainSettings, "process-remove-spaces"))
1311 {
1312 Scan_Process_Fields_Remove_Space (*string);
1313 }
1314
1315 }
1316
1317
1318 /*****************************
1319 * Scanner To Process Fields *
1320 *****************************/
1321 /* See also functions : Convert_P20_And_Undescore_Into_Spaces, ... in easytag.c */
1322 static void
Scan_Process_Fields(EtScanDialog * self,ET_File * ETFile)1323 Scan_Process_Fields (EtScanDialog *self, ET_File *ETFile)
1324 {
1325 File_Name *FileName = NULL;
1326 File_Tag *FileTag = NULL;
1327 File_Name *st_filename;
1328 File_Tag *st_filetag;
1329 guint process_fields;
1330 gchar *filename_utf8;
1331 gchar *string;
1332
1333 g_return_if_fail (ETFile != NULL);
1334
1335 st_filename = (File_Name *)ETFile->FileNameNew->data;
1336 st_filetag = (File_Tag *)ETFile->FileTag->data;
1337 process_fields = g_settings_get_flags (MainSettings, "process-fields");
1338
1339 /* Process the filename */
1340 if (st_filename != NULL)
1341 {
1342 if (st_filename->value_utf8
1343 && (process_fields & ET_PROCESS_FIELD_FILENAME))
1344 {
1345 gchar *string_utf8;
1346 gchar *pos;
1347
1348 filename_utf8 = st_filename->value_utf8;
1349
1350 if (!FileName)
1351 FileName = et_file_name_new ();
1352
1353 string = g_path_get_basename(filename_utf8);
1354 // Remove the extension to set it to lower case (to avoid problem with undo)
1355 if ((pos=strrchr(string,'.'))!=NULL) *pos = 0;
1356
1357 Scan_Process_Fields_Functions (self, &string);
1358
1359 string_utf8 = et_file_generate_name (ETFile, string);
1360 ET_Set_Filename_File_Name_Item(FileName,string_utf8,NULL);
1361 g_free(string_utf8);
1362 g_free(string);
1363 }
1364 }
1365
1366 /* Process data of the tag */
1367 if (st_filetag != NULL)
1368 {
1369 /* Title field. */
1370 if (st_filetag->title
1371 && (process_fields & ET_PROCESS_FIELD_TITLE))
1372 {
1373 if (!FileTag)
1374 {
1375 FileTag = et_file_tag_new ();
1376 et_file_tag_copy_into (FileTag, ETFile->FileTag->data);
1377 }
1378
1379 string = g_strdup(st_filetag->title);
1380
1381 Scan_Process_Fields_Functions (self, &string);
1382
1383 et_file_tag_set_title (FileTag, string);
1384
1385 g_free(string);
1386 }
1387
1388 /* Artist field. */
1389 if (st_filetag->artist
1390 && (process_fields & ET_PROCESS_FIELD_ARTIST))
1391 {
1392 if (!FileTag)
1393 {
1394 FileTag = et_file_tag_new ();
1395 et_file_tag_copy_into (FileTag, ETFile->FileTag->data);
1396 }
1397
1398 string = g_strdup(st_filetag->artist);
1399
1400 Scan_Process_Fields_Functions (self, &string);
1401
1402 et_file_tag_set_artist (FileTag, string);
1403
1404 g_free(string);
1405 }
1406
1407 /* Album Artist field. */
1408 if (st_filetag->album_artist
1409 && (process_fields & ET_PROCESS_FIELD_ALBUM_ARTIST))
1410 {
1411 if (!FileTag)
1412 {
1413 FileTag = et_file_tag_new ();
1414 et_file_tag_copy_into (FileTag, ETFile->FileTag->data);
1415 }
1416
1417 string = g_strdup(st_filetag->album_artist);
1418
1419 Scan_Process_Fields_Functions (self, &string);
1420
1421 et_file_tag_set_album_artist (FileTag, string);
1422
1423 g_free(string);
1424 }
1425
1426 /* Album field. */
1427 if (st_filetag->album
1428 && (process_fields & ET_PROCESS_FIELD_ALBUM))
1429 {
1430 if (!FileTag)
1431 {
1432 FileTag = et_file_tag_new ();
1433 et_file_tag_copy_into (FileTag, ETFile->FileTag->data);
1434 }
1435
1436 string = g_strdup(st_filetag->album);
1437
1438 Scan_Process_Fields_Functions (self, &string);
1439
1440 et_file_tag_set_album (FileTag, string);
1441
1442 g_free(string);
1443 }
1444
1445 /* Genre field. */
1446 if (st_filetag->genre
1447 && (process_fields & ET_PROCESS_FIELD_GENRE))
1448 {
1449 if (!FileTag)
1450 {
1451 FileTag = et_file_tag_new ();
1452 et_file_tag_copy_into (FileTag, ETFile->FileTag->data);
1453 }
1454
1455 string = g_strdup(st_filetag->genre);
1456
1457 Scan_Process_Fields_Functions (self, &string);
1458
1459 et_file_tag_set_genre (FileTag, string);
1460
1461 g_free(string);
1462 }
1463
1464 /* Comment field. */
1465 if (st_filetag->comment
1466 && (process_fields & ET_PROCESS_FIELD_COMMENT))
1467 {
1468 if (!FileTag)
1469 {
1470 FileTag = et_file_tag_new ();
1471 et_file_tag_copy_into (FileTag, ETFile->FileTag->data);
1472 }
1473
1474 string = g_strdup(st_filetag->comment);
1475
1476 Scan_Process_Fields_Functions (self, &string);
1477
1478 et_file_tag_set_comment (FileTag, string);
1479
1480 g_free(string);
1481 }
1482
1483 /* Composer field. */
1484 if (st_filetag->composer
1485 && (process_fields & ET_PROCESS_FIELD_COMPOSER))
1486 {
1487 if (!FileTag)
1488 {
1489 FileTag = et_file_tag_new ();
1490 et_file_tag_copy_into (FileTag, ETFile->FileTag->data);
1491 }
1492
1493 string = g_strdup(st_filetag->composer);
1494
1495 Scan_Process_Fields_Functions (self, &string);
1496
1497 et_file_tag_set_composer (FileTag, string);
1498
1499 g_free(string);
1500 }
1501
1502 /* Original artist field. */
1503 if (st_filetag->orig_artist
1504 && (process_fields & ET_PROCESS_FIELD_ORIGINAL_ARTIST))
1505 {
1506 if (!FileTag)
1507 {
1508 FileTag = et_file_tag_new ();
1509 et_file_tag_copy_into (FileTag, ETFile->FileTag->data);
1510 }
1511
1512 string = g_strdup(st_filetag->orig_artist);
1513
1514 Scan_Process_Fields_Functions (self, &string);
1515
1516 et_file_tag_set_orig_artist (FileTag, string);
1517
1518 g_free(string);
1519 }
1520
1521 /* Copyright field. */
1522 if (st_filetag->copyright
1523 && (process_fields & ET_PROCESS_FIELD_COPYRIGHT))
1524 {
1525 if (!FileTag)
1526 {
1527 FileTag = et_file_tag_new ();
1528 et_file_tag_copy_into (FileTag, ETFile->FileTag->data);
1529 }
1530
1531 string = g_strdup(st_filetag->copyright);
1532
1533 Scan_Process_Fields_Functions (self, &string);
1534
1535 et_file_tag_set_copyright (FileTag, string);
1536
1537 g_free(string);
1538 }
1539
1540 /* URL field. */
1541 if (st_filetag->url
1542 && (process_fields & ET_PROCESS_FIELD_URL))
1543 {
1544 if (!FileTag)
1545 {
1546 FileTag = et_file_tag_new ();
1547 et_file_tag_copy_into (FileTag, ETFile->FileTag->data);
1548 }
1549
1550 string = g_strdup(st_filetag->url);
1551
1552 Scan_Process_Fields_Functions (self, &string);
1553
1554 et_file_tag_set_url (FileTag, string);
1555
1556 g_free(string);
1557 }
1558
1559 /* 'Encoded by' field. */
1560 if (st_filetag->encoded_by
1561 && (process_fields & ET_PROCESS_FIELD_ENCODED_BY))
1562 {
1563 if (!FileTag)
1564 {
1565 FileTag = et_file_tag_new ();
1566 et_file_tag_copy_into (FileTag, ETFile->FileTag->data);
1567 }
1568
1569 string = g_strdup(st_filetag->encoded_by);
1570
1571 Scan_Process_Fields_Functions (self, &string);
1572
1573 et_file_tag_set_encoded_by (FileTag, string);
1574
1575 g_free(string);
1576 }
1577 }
1578
1579 if (FileName && FileTag)
1580 {
1581 // Synchronize undo key of the both structures (used for the
1582 // undo functions, as they are generated as the same time)
1583 FileName->key = FileTag->key;
1584 }
1585 ET_Manage_Changes_Of_File_Data(ETFile,FileName,FileTag);
1586
1587 }
1588
1589 /******************
1590 * Scanner Window *
1591 ******************/
1592 /*
1593 * Function when you select an item of the option menu
1594 */
1595 static void
on_scan_mode_changed(EtScanDialog * self,const gchar * key,GSettings * settings)1596 on_scan_mode_changed (EtScanDialog *self,
1597 const gchar *key,
1598 GSettings *settings)
1599 {
1600 EtScanDialogPrivate *priv;
1601 EtScanMode mode;
1602
1603 priv = et_scan_dialog_get_instance_private (self);
1604
1605 mode = g_settings_get_enum (settings, key);
1606
1607 switch (mode)
1608 {
1609 case ET_SCAN_MODE_FILL_TAG:
1610 gtk_widget_show(priv->mask_editor_toggle);
1611 gtk_widget_show(priv->legend_toggle);
1612 gtk_tree_view_set_model (GTK_TREE_VIEW (priv->mask_view),
1613 GTK_TREE_MODEL (priv->fill_masks_model));
1614 Scan_Fill_Tag_Generate_Preview (self);
1615 g_signal_emit_by_name(G_OBJECT(priv->legend_toggle),"toggled"); /* To hide or show legend frame */
1616 g_signal_emit_by_name(G_OBJECT(priv->mask_editor_toggle),"toggled"); /* To hide or show mask editor frame */
1617 break;
1618
1619 case ET_SCAN_MODE_RENAME_FILE:
1620 gtk_widget_show(priv->mask_editor_toggle);
1621 gtk_widget_show(priv->legend_toggle);
1622 gtk_tree_view_set_model (GTK_TREE_VIEW (priv->mask_view),
1623 GTK_TREE_MODEL (priv->rename_masks_model));
1624 Scan_Rename_File_Generate_Preview (self);
1625 g_signal_emit_by_name(G_OBJECT(priv->legend_toggle),"toggled"); /* To hide or show legend frame */
1626 g_signal_emit_by_name(G_OBJECT(priv->mask_editor_toggle),"toggled"); /* To hide or show mask editor frame */
1627 break;
1628
1629 case ET_SCAN_MODE_PROCESS_FIELDS:
1630 gtk_widget_hide(priv->mask_editor_toggle);
1631 gtk_widget_hide(priv->legend_toggle);
1632 // Hide directly the frames to don't change state of the buttons!
1633 gtk_widget_hide (priv->legend_grid);
1634 gtk_widget_hide (priv->editor_grid);
1635
1636 gtk_tree_view_set_model (GTK_TREE_VIEW (priv->mask_view), NULL);
1637 break;
1638 default:
1639 g_assert_not_reached ();
1640 }
1641
1642 /* TODO: Either duplicate the legend and mask editor, or split the dialog.
1643 */
1644 if (mode == ET_SCAN_MODE_FILL_TAG || mode == ET_SCAN_MODE_RENAME_FILE)
1645 {
1646 GtkWidget *parent;
1647
1648 parent = gtk_widget_get_parent (priv->editor_grid);
1649
1650 if ((mode == ET_SCAN_MODE_RENAME_FILE && parent != priv->rename_grid)
1651 || (mode == ET_SCAN_MODE_FILL_TAG && parent != priv->fill_grid))
1652 {
1653 g_object_ref (priv->editor_grid);
1654 g_object_ref (priv->legend_grid);
1655 gtk_container_remove (GTK_CONTAINER (parent),
1656 priv->editor_grid);
1657 gtk_container_remove (GTK_CONTAINER (parent), priv->legend_grid);
1658
1659 if (mode == ET_SCAN_MODE_RENAME_FILE)
1660 {
1661 gtk_container_add (GTK_CONTAINER (priv->rename_grid),
1662 priv->editor_grid);
1663 gtk_container_add (GTK_CONTAINER (priv->rename_grid),
1664 priv->legend_grid);
1665 }
1666 else
1667 {
1668 gtk_container_add (GTK_CONTAINER (priv->fill_grid),
1669 priv->editor_grid);
1670 gtk_container_add (GTK_CONTAINER (priv->fill_grid),
1671 priv->legend_grid);
1672 }
1673
1674 g_object_unref (priv->editor_grid);
1675 g_object_unref (priv->legend_grid);
1676 }
1677 }
1678 }
1679
1680 static void
Mask_Editor_List_Add(EtScanDialog * self)1681 Mask_Editor_List_Add (EtScanDialog *self)
1682 {
1683 EtScanDialogPrivate *priv;
1684 gint i = 0;
1685 GtkTreeModel *treemodel;
1686 gchar *temp;
1687
1688 priv = et_scan_dialog_get_instance_private (self);
1689
1690 treemodel = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->mask_view));
1691
1692 if (gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook)) != ET_SCAN_MODE_FILL_TAG)
1693 {
1694 while(Scan_Masks[i])
1695 {
1696 temp = Try_To_Validate_Utf8_String(Scan_Masks[i]);
1697
1698 gtk_list_store_insert_with_values (GTK_LIST_STORE (treemodel),
1699 NULL, G_MAXINT,
1700 MASK_EDITOR_TEXT, temp, -1);
1701 g_free(temp);
1702 i++;
1703 }
1704 } else if (gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook)) == ET_SCAN_MODE_RENAME_FILE)
1705 {
1706 while(Rename_File_Masks[i])
1707 {
1708 temp = Try_To_Validate_Utf8_String(Rename_File_Masks[i]);
1709
1710 gtk_list_store_insert_with_values (GTK_LIST_STORE (treemodel),
1711 NULL, G_MAXINT,
1712 MASK_EDITOR_TEXT, temp, -1);
1713 g_free(temp);
1714 i++;
1715 }
1716 }
1717 }
1718
1719 /*
1720 * Clean up the currently displayed masks lists, ready for saving
1721 */
1722 static void
Mask_Editor_Clean_Up_Masks_List(EtScanDialog * self)1723 Mask_Editor_Clean_Up_Masks_List (EtScanDialog *self)
1724 {
1725 EtScanDialogPrivate *priv;
1726 gchar *text = NULL;
1727 gchar *text1 = NULL;
1728 GtkTreeIter currentIter;
1729 GtkTreeIter itercopy;
1730 GtkTreeModel *treemodel;
1731
1732 priv = et_scan_dialog_get_instance_private (self);
1733
1734 treemodel = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->mask_view));
1735
1736 /* Remove blank and duplicate items */
1737 if (gtk_tree_model_get_iter_first(treemodel, ¤tIter))
1738 {
1739
1740 while(TRUE)
1741 {
1742 gtk_tree_model_get(treemodel, ¤tIter, MASK_EDITOR_TEXT, &text, -1);
1743
1744 /* Check for blank entry */
1745 if (text && *text == '\0')
1746 {
1747 g_free(text);
1748
1749 if (!gtk_list_store_remove(GTK_LIST_STORE(treemodel), ¤tIter))
1750 break; /* No following entries */
1751 else
1752 continue; /* Go on to next entry, which the remove function already moved onto for us */
1753 }
1754
1755 /* Check for duplicate entries */
1756 itercopy = currentIter;
1757 if (!gtk_tree_model_iter_next(treemodel, &itercopy))
1758 {
1759 g_free(text);
1760 break;
1761 }
1762
1763 while(TRUE)
1764 {
1765 gtk_tree_model_get(treemodel, &itercopy, MASK_EDITOR_TEXT, &text1, -1);
1766 if (text1 && g_utf8_collate(text,text1) == 0)
1767 {
1768 g_free(text1);
1769
1770 if (!gtk_list_store_remove(GTK_LIST_STORE(treemodel), &itercopy))
1771 break; /* No following entries */
1772 else
1773 continue; /* Go on to next entry, which the remove function already set iter to for us */
1774
1775 }
1776 g_free(text1);
1777 if (!gtk_tree_model_iter_next(treemodel, &itercopy))
1778 break;
1779 }
1780
1781 g_free(text);
1782
1783 if (!gtk_tree_model_iter_next(treemodel, ¤tIter))
1784 break;
1785 }
1786 }
1787 }
1788
1789 /*
1790 * Save the currently displayed mask list in the mask editor
1791 */
1792 static void
Mask_Editor_List_Save(EtScanDialog * self)1793 Mask_Editor_List_Save (EtScanDialog *self)
1794 {
1795 EtScanDialogPrivate *priv;
1796
1797 priv = et_scan_dialog_get_instance_private (self);
1798
1799 Mask_Editor_Clean_Up_Masks_List (self);
1800
1801 if (gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook)) == ET_SCAN_MODE_FILL_TAG)
1802 {
1803 Save_Scan_Tag_Masks_List (priv->fill_masks_model, MASK_EDITOR_TEXT);
1804 }
1805 else if (gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook)) == ET_SCAN_MODE_RENAME_FILE)
1806 {
1807 Save_Rename_File_Masks_List(priv->rename_masks_model, MASK_EDITOR_TEXT);
1808 }
1809 }
1810
1811 static void
Process_Fields_First_Letters_Check_Button_Toggled(EtScanDialog * self)1812 Process_Fields_First_Letters_Check_Button_Toggled (EtScanDialog *self)
1813 {
1814 EtScanDialogPrivate *priv;
1815
1816 priv = et_scan_dialog_get_instance_private (self);
1817
1818 gtk_widget_set_sensitive (GTK_WIDGET (priv->capitalize_roman_check),
1819 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->capitalize_first_style_radio)));
1820 }
1821
1822 /*
1823 * Set sensitive state of the processing check boxes : if no one is selected => all disabled
1824 */
1825 static void
on_process_fields_changed(EtScanDialog * self,const gchar * key,GSettings * settings)1826 on_process_fields_changed (EtScanDialog *self,
1827 const gchar *key,
1828 GSettings *settings)
1829 {
1830 EtScanDialogPrivate *priv;
1831
1832 priv = et_scan_dialog_get_instance_private (self);
1833
1834 if (g_settings_get_flags (settings, key) != 0)
1835 {
1836 gtk_widget_set_sensitive (priv->convert_space_radio, TRUE);
1837 gtk_widget_set_sensitive (priv->convert_underscores_radio, TRUE);
1838 gtk_widget_set_sensitive (priv->convert_string_radio, TRUE);
1839 gtk_widget_set_sensitive (GTK_WIDGET (priv->convert_to_label), TRUE);
1840 // Activate the two entries only if the check box is activated, else keep them disabled
1841 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->convert_string_radio)))
1842 {
1843 gtk_widget_set_sensitive (priv->convert_to_entry, TRUE);
1844 gtk_widget_set_sensitive (priv->convert_from_entry, TRUE);
1845 }
1846 gtk_widget_set_sensitive (priv->capitalize_all_radio, TRUE);
1847 gtk_widget_set_sensitive (priv->capitalize_lower_radio, TRUE);
1848 gtk_widget_set_sensitive (priv->capitalize_first_radio, TRUE);
1849 gtk_widget_set_sensitive (priv->capitalize_first_style_radio, TRUE);
1850 Process_Fields_First_Letters_Check_Button_Toggled (self);
1851 gtk_widget_set_sensitive (priv->spaces_remove_radio, TRUE);
1852 gtk_widget_set_sensitive (priv->spaces_insert_radio, TRUE);
1853 gtk_widget_set_sensitive (priv->spaces_insert_one_radio, TRUE);
1854 }else
1855 {
1856 gtk_widget_set_sensitive (priv->convert_space_radio, FALSE);
1857 gtk_widget_set_sensitive (priv->convert_underscores_radio, FALSE);
1858 gtk_widget_set_sensitive (priv->convert_string_radio, FALSE);
1859 gtk_widget_set_sensitive (priv->convert_to_entry, FALSE);
1860 gtk_widget_set_sensitive (priv->convert_to_label, FALSE);
1861 gtk_widget_set_sensitive (priv->convert_from_entry, FALSE);
1862 gtk_widget_set_sensitive (priv->capitalize_all_radio, FALSE);
1863 gtk_widget_set_sensitive (priv->capitalize_lower_radio, FALSE);
1864 gtk_widget_set_sensitive (priv->capitalize_first_radio, FALSE);
1865 gtk_widget_set_sensitive (priv->capitalize_first_style_radio, FALSE);
1866 gtk_widget_set_sensitive (priv->capitalize_roman_check, FALSE);
1867 gtk_widget_set_sensitive (priv->spaces_remove_radio, FALSE);
1868 gtk_widget_set_sensitive (priv->spaces_insert_radio, FALSE);
1869 gtk_widget_set_sensitive (priv->spaces_insert_one_radio, FALSE);
1870 }
1871 }
1872
1873 static void
Scan_Toggle_Legend_Button(EtScanDialog * self)1874 Scan_Toggle_Legend_Button (EtScanDialog *self)
1875 {
1876 EtScanDialogPrivate *priv;
1877
1878 priv = et_scan_dialog_get_instance_private (self);
1879
1880 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->legend_toggle)))
1881 {
1882 gtk_widget_show_all (priv->legend_grid);
1883 }
1884 else
1885 {
1886 gtk_widget_hide (priv->legend_grid);
1887 }
1888 }
1889
1890 static void
Scan_Toggle_Mask_Editor_Button(EtScanDialog * self)1891 Scan_Toggle_Mask_Editor_Button (EtScanDialog *self)
1892 {
1893 EtScanDialogPrivate *priv;
1894 GtkTreeModel *treemodel;
1895 GtkTreeSelection *selection;
1896 GtkTreeIter iter;
1897
1898 priv = et_scan_dialog_get_instance_private (self);
1899
1900 if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->mask_editor_toggle)))
1901 {
1902 gtk_widget_show_all (priv->editor_grid);
1903
1904 // Select first row in list
1905 treemodel = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->mask_view));
1906 if (gtk_tree_model_get_iter_first(treemodel, &iter))
1907 {
1908 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->mask_view));
1909 gtk_tree_selection_unselect_all(selection);
1910 gtk_tree_selection_select_iter(selection, &iter);
1911 }
1912
1913 // Update status of the icon box cause prev instruction show it for all cases
1914 g_signal_emit_by_name (G_OBJECT (priv->mask_entry), "changed");
1915 }else
1916 {
1917 gtk_widget_hide (priv->editor_grid);
1918 }
1919 }
1920
1921 /*
1922 * Update the Mask List with the new value of the entry box
1923 */
1924 static void
Mask_Editor_Entry_Changed(EtScanDialog * self)1925 Mask_Editor_Entry_Changed (EtScanDialog *self)
1926 {
1927 EtScanDialogPrivate *priv;
1928 GtkTreeSelection *selection;
1929 GtkTreePath *firstSelected;
1930 GtkTreeModel *treemodel;
1931 GList *selectedRows;
1932 GtkTreeIter row;
1933 const gchar* text;
1934
1935 priv = et_scan_dialog_get_instance_private (self);
1936
1937 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->mask_view));
1938 treemodel = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->mask_view));
1939 selectedRows = gtk_tree_selection_get_selected_rows(selection, NULL);
1940
1941 if (!selectedRows)
1942 {
1943 return;
1944 }
1945
1946 firstSelected = (GtkTreePath *)g_list_first(selectedRows)->data;
1947 text = gtk_entry_get_text (GTK_ENTRY (priv->mask_entry));
1948
1949 if (gtk_tree_model_get_iter (treemodel, &row, firstSelected))
1950 {
1951 gtk_list_store_set(GTK_LIST_STORE(treemodel), &row, MASK_EDITOR_TEXT, text, -1);
1952 }
1953
1954 g_list_free_full (selectedRows, (GDestroyNotify)gtk_tree_path_free);
1955 }
1956
1957 /*
1958 * Callback from the mask edit list
1959 * Previously known as Mask_Editor_List_Select_Row
1960 */
1961 static void
Mask_Editor_List_Row_Selected(GtkTreeSelection * selection,EtScanDialog * self)1962 Mask_Editor_List_Row_Selected (GtkTreeSelection* selection, EtScanDialog *self)
1963 {
1964 EtScanDialogPrivate *priv;
1965 GList *selectedRows;
1966 gchar *text = NULL;
1967 GtkTreePath *lastSelected;
1968 GtkTreeIter lastFile;
1969 GtkTreeModel *treemodel;
1970 gboolean valid;
1971
1972 priv = et_scan_dialog_get_instance_private (self);
1973
1974 treemodel = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->mask_view));
1975
1976 /* We must block the function, else the previous selected row will be modified */
1977 g_signal_handlers_block_by_func (G_OBJECT (priv->mask_entry),
1978 G_CALLBACK (Mask_Editor_Entry_Changed),
1979 NULL);
1980
1981 selectedRows = gtk_tree_selection_get_selected_rows(selection, NULL);
1982
1983 /*
1984 * At some point, we might get called when no rows are selected?
1985 */
1986 if (!selectedRows)
1987 {
1988 g_signal_handlers_unblock_by_func (G_OBJECT (priv->mask_entry),
1989 G_CALLBACK (Mask_Editor_Entry_Changed),
1990 NULL);
1991 return;
1992 }
1993
1994 /* Get the text of the last selected row */
1995 lastSelected = (GtkTreePath *)g_list_last(selectedRows)->data;
1996
1997 valid= gtk_tree_model_get_iter(treemodel, &lastFile, lastSelected);
1998 if (valid)
1999 {
2000 gtk_tree_model_get(treemodel, &lastFile, MASK_EDITOR_TEXT, &text, -1);
2001
2002 if (text)
2003 {
2004 gtk_entry_set_text (GTK_ENTRY (priv->mask_entry), text);
2005 g_free(text);
2006 }
2007 }
2008
2009 g_signal_handlers_unblock_by_func (G_OBJECT (priv->mask_entry),
2010 G_CALLBACK (Mask_Editor_Entry_Changed),
2011 NULL);
2012
2013 g_list_free_full (selectedRows, (GDestroyNotify)gtk_tree_path_free);
2014 }
2015
2016 /*
2017 * Remove the selected rows from the mask editor list
2018 */
2019 static void
Mask_Editor_List_Remove(EtScanDialog * self)2020 Mask_Editor_List_Remove (EtScanDialog *self)
2021 {
2022 EtScanDialogPrivate *priv;
2023 GtkTreeSelection *selection;
2024 GtkTreeIter iter;
2025 GtkTreeModel *treemodel;
2026
2027 priv = et_scan_dialog_get_instance_private (self);
2028
2029 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->mask_view));
2030 treemodel = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->mask_view));
2031
2032 if (gtk_tree_selection_count_selected_rows(selection) == 0) {
2033 g_critical ("%s", "Remove: No row selected");
2034 return;
2035 }
2036
2037 if (!gtk_tree_model_get_iter_first(treemodel, &iter))
2038 return;
2039
2040 while (TRUE)
2041 {
2042 if (gtk_tree_selection_iter_is_selected(selection, &iter))
2043 {
2044 if (!gtk_list_store_remove(GTK_LIST_STORE(treemodel), &iter))
2045 {
2046 break;
2047 }
2048 } else
2049 {
2050 if (!gtk_tree_model_iter_next(treemodel, &iter))
2051 {
2052 break;
2053 }
2054 }
2055 }
2056 }
2057
2058 /*
2059 * Actions when the a key is pressed into the masks editor clist
2060 */
2061 static gboolean
Mask_Editor_List_Key_Press(GtkWidget * widget,GdkEvent * event,EtScanDialog * self)2062 Mask_Editor_List_Key_Press (GtkWidget *widget,
2063 GdkEvent *event,
2064 EtScanDialog *self)
2065 {
2066 if (event && event->type == GDK_KEY_PRESS)
2067 {
2068 GdkEventKey *kevent = (GdkEventKey *)event;
2069
2070 switch (kevent->keyval)
2071 {
2072 case GDK_KEY_Delete:
2073 Mask_Editor_List_Remove (self);
2074 return GDK_EVENT_STOP;
2075 break;
2076 default:
2077 /* Ignore all other keypresses. */
2078 break;
2079 }
2080 }
2081
2082 return GDK_EVENT_PROPAGATE;
2083 }
2084
2085 /*
2086 * Add a new mask to the list
2087 */
2088 static void
Mask_Editor_List_New(EtScanDialog * self)2089 Mask_Editor_List_New (EtScanDialog *self)
2090 {
2091 EtScanDialogPrivate *priv;
2092 gchar *text;
2093 GtkTreeIter iter;
2094 GtkTreeSelection *selection;
2095 GtkTreeModel *treemodel;
2096
2097 priv = et_scan_dialog_get_instance_private (self);
2098
2099 text = _("New_mask");
2100 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->mask_view));
2101 treemodel = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->mask_view));
2102
2103 gtk_list_store_insert(GTK_LIST_STORE(treemodel), &iter, 0);
2104 gtk_list_store_set(GTK_LIST_STORE(treemodel), &iter, MASK_EDITOR_TEXT, text, -1);
2105
2106 gtk_tree_selection_unselect_all(selection);
2107 gtk_tree_selection_select_iter(selection, &iter);
2108 }
2109
2110 /*
2111 * Move all selected rows up one place in the mask list
2112 */
2113 static void
Mask_Editor_List_Move_Up(EtScanDialog * self)2114 Mask_Editor_List_Move_Up (EtScanDialog *self)
2115 {
2116 EtScanDialogPrivate *priv;
2117 GtkTreeSelection *selection;
2118 GList *selectedRows;
2119 GList *l;
2120 GtkTreeIter currentFile;
2121 GtkTreeIter nextFile;
2122 GtkTreePath *currentPath;
2123 GtkTreeModel *treemodel;
2124
2125 priv = et_scan_dialog_get_instance_private (self);
2126
2127 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->mask_view));
2128 treemodel = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->mask_view));
2129 selectedRows = gtk_tree_selection_get_selected_rows(selection, NULL);
2130
2131 if (!selectedRows)
2132 {
2133 g_critical ("%s", "Move Up: No row selected");
2134 return;
2135 }
2136
2137 for (l = selectedRows; l != NULL; l = g_list_next (l))
2138 {
2139 currentPath = (GtkTreePath *)l->data;
2140 if (gtk_tree_model_get_iter(treemodel, ¤tFile, currentPath))
2141 {
2142 /* Find the entry above the node... */
2143 if (gtk_tree_path_prev(currentPath))
2144 {
2145 /* ...and if it exists, swap the two rows by iter */
2146 gtk_tree_model_get_iter(treemodel, &nextFile, currentPath);
2147 gtk_list_store_swap(GTK_LIST_STORE(treemodel), ¤tFile, &nextFile);
2148 }
2149 }
2150 }
2151
2152 g_list_free_full (selectedRows, (GDestroyNotify)gtk_tree_path_free);
2153 }
2154
2155 /*
2156 * Move all selected rows down one place in the mask list
2157 */
2158 static void
Mask_Editor_List_Move_Down(EtScanDialog * self)2159 Mask_Editor_List_Move_Down (EtScanDialog *self)
2160 {
2161 EtScanDialogPrivate *priv;
2162 GtkTreeSelection *selection;
2163 GList *selectedRows;
2164 GList *l;
2165 GtkTreeIter currentFile;
2166 GtkTreeIter nextFile;
2167 GtkTreePath *currentPath;
2168 GtkTreeModel *treemodel;
2169
2170 priv = et_scan_dialog_get_instance_private (self);
2171
2172 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->mask_view));
2173 treemodel = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->mask_view));
2174 selectedRows = gtk_tree_selection_get_selected_rows(selection, NULL);
2175
2176 if (!selectedRows)
2177 {
2178 g_critical ("%s", "Move Down: No row selected");
2179 return;
2180 }
2181
2182 for (l = selectedRows; l != NULL; l = g_list_next (l))
2183 {
2184 currentPath = (GtkTreePath *)l->data;
2185
2186 if (gtk_tree_model_get_iter(treemodel, ¤tFile, currentPath))
2187 {
2188 /* Find the entry below the node and swap the two nodes by iter */
2189 gtk_tree_path_next(currentPath);
2190 if (gtk_tree_model_get_iter(treemodel, &nextFile, currentPath))
2191 gtk_list_store_swap(GTK_LIST_STORE(treemodel), ¤tFile, &nextFile);
2192 }
2193 }
2194
2195 g_list_free_full (selectedRows, (GDestroyNotify)gtk_tree_path_free);
2196 }
2197
2198 /*
2199 * Set a row visible in the mask editor list (by scrolling the list)
2200 */
2201 static void
Mask_Editor_List_Set_Row_Visible(GtkTreeView * view,GtkTreeModel * treeModel,GtkTreeIter * rowIter)2202 Mask_Editor_List_Set_Row_Visible (GtkTreeView *view,
2203 GtkTreeModel *treeModel,
2204 GtkTreeIter *rowIter)
2205 {
2206 /*
2207 * TODO: Make this only scroll to the row if it is not visible
2208 * (like in easytag GTK1)
2209 * See function gtk_tree_view_get_visible_rect() ??
2210 */
2211 GtkTreePath *rowPath;
2212
2213 g_return_if_fail (treeModel != NULL);
2214
2215 rowPath = gtk_tree_model_get_path (treeModel, rowIter);
2216 gtk_tree_view_scroll_to_cell (view, rowPath, NULL, FALSE, 0, 0);
2217 gtk_tree_path_free (rowPath);
2218 }
2219
2220 /*
2221 * Duplicate a mask on the list
2222 */
2223 static void
Mask_Editor_List_Duplicate(EtScanDialog * self)2224 Mask_Editor_List_Duplicate (EtScanDialog *self)
2225 {
2226 EtScanDialogPrivate *priv;
2227 gchar *text = NULL;
2228 GList *selectedRows;
2229 GList *l;
2230 GList *toInsert = NULL;
2231 GtkTreeSelection *selection;
2232 GtkTreeIter rowIter;
2233 GtkTreeModel *treeModel;
2234
2235 priv = et_scan_dialog_get_instance_private (self);
2236
2237 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->mask_view));
2238 selectedRows = gtk_tree_selection_get_selected_rows(selection, NULL);
2239 treeModel = gtk_tree_view_get_model (GTK_TREE_VIEW (priv->mask_view));
2240
2241 if (!selectedRows)
2242 {
2243 g_critical ("%s", "Copy: No row selected");
2244 return;
2245 }
2246
2247 /* Loop through selected rows, duplicating them into a GList
2248 * We cannot directly insert because the paths in selectedRows
2249 * get out of date after an insertion */
2250 for (l = selectedRows; l != NULL; l = g_list_next (l))
2251 {
2252 if (gtk_tree_model_get_iter (treeModel, &rowIter,
2253 (GtkTreePath*)l->data))
2254 {
2255 gtk_tree_model_get(treeModel, &rowIter, MASK_EDITOR_TEXT, &text, -1);
2256 toInsert = g_list_prepend (toInsert, text);
2257 }
2258 }
2259
2260 for (l = toInsert; l != NULL; l = g_list_next (l))
2261 {
2262 gtk_list_store_insert_with_values (GTK_LIST_STORE(treeModel), &rowIter,
2263 0, MASK_EDITOR_TEXT,
2264 (gchar *)l->data, -1);
2265 }
2266
2267 /* Set focus to the last inserted line. */
2268 if (toInsert)
2269 {
2270 Mask_Editor_List_Set_Row_Visible (GTK_TREE_VIEW (priv->mask_view),
2271 treeModel, &rowIter);
2272 }
2273
2274 /* Free data no longer needed */
2275 g_list_free_full (selectedRows, (GDestroyNotify)gtk_tree_path_free);
2276 g_list_free_full (toInsert, (GDestroyNotify)g_free);
2277 }
2278
2279 static void
Process_Fields_Convert_Check_Button_Toggled(EtScanDialog * self,GtkWidget * object)2280 Process_Fields_Convert_Check_Button_Toggled (EtScanDialog *self, GtkWidget *object)
2281 {
2282 EtScanDialogPrivate *priv;
2283
2284 priv = et_scan_dialog_get_instance_private (self);
2285
2286 gtk_widget_set_sensitive (priv->convert_to_entry,
2287 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->convert_string_radio)));
2288 gtk_widget_set_sensitive (priv->convert_from_entry,
2289 gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (priv->convert_string_radio)));
2290 }
2291
2292 /* Make sure that the Show Scanner toggle action is updated. */
2293 static void
et_scan_on_hide(GtkWidget * widget,gpointer user_data)2294 et_scan_on_hide (GtkWidget *widget,
2295 gpointer user_data)
2296 {
2297 g_action_group_activate_action (G_ACTION_GROUP (MainWindow), "scanner",
2298 NULL);
2299 }
2300
2301 static void
init_process_field_check(GtkWidget * widget)2302 init_process_field_check (GtkWidget *widget)
2303 {
2304 g_object_set_data (G_OBJECT (widget), "flags-type",
2305 GSIZE_TO_POINTER (ET_TYPE_PROCESS_FIELD));
2306 g_object_set_data (G_OBJECT (widget), "flags-key",
2307 (gpointer) "process-fields");
2308 g_settings_bind_with_mapping (MainSettings, "process-fields", widget,
2309 "active", G_SETTINGS_BIND_DEFAULT,
2310 et_settings_flags_toggle_get,
2311 et_settings_flags_toggle_set, widget, NULL);
2312 }
2313
2314 static void
create_scan_dialog(EtScanDialog * self)2315 create_scan_dialog (EtScanDialog *self)
2316 {
2317 EtScanDialogPrivate *priv;
2318 GtkWidget *scan_button;
2319
2320 priv = et_scan_dialog_get_instance_private (self);
2321
2322 /* The window */
2323 gtk_dialog_add_buttons (GTK_DIALOG (self), _("_Close"),
2324 GTK_RESPONSE_CLOSE, NULL);
2325
2326 /* 'Scan selected files' button */
2327 scan_button = gtk_button_new_with_label (_("Scan Files"));
2328 gtk_widget_set_can_default (scan_button, TRUE);
2329 gtk_dialog_add_action_widget (GTK_DIALOG (self), scan_button,
2330 GTK_RESPONSE_APPLY);
2331 gtk_dialog_set_default_response (GTK_DIALOG (self), GTK_RESPONSE_APPLY);
2332 gtk_widget_show (scan_button);
2333 gtk_widget_set_tooltip_text (scan_button, _("Scan selected files"));
2334
2335 g_settings_bind_with_mapping (MainSettings, "scan-mode", priv->notebook,
2336 "page", G_SETTINGS_BIND_DEFAULT,
2337 et_settings_enum_get, et_settings_enum_set,
2338 GSIZE_TO_POINTER (ET_TYPE_SCAN_MODE), NULL);
2339 g_signal_connect_swapped (MainSettings, "changed::scan-mode",
2340 G_CALLBACK (on_scan_mode_changed),
2341 self);
2342
2343 /* Mask Editor button */
2344 g_settings_bind (MainSettings, "scan-mask-editor-show",
2345 priv->mask_editor_toggle, "active",
2346 G_SETTINGS_BIND_DEFAULT);
2347
2348 g_settings_bind (MainSettings, "scan-legend-show", priv->legend_toggle,
2349 "active", G_SETTINGS_BIND_DEFAULT);
2350
2351 /* Signal to generate preview (preview of the new tag values). */
2352 g_signal_connect_swapped (gtk_bin_get_child (GTK_BIN (priv->fill_combo)),
2353 "changed",
2354 G_CALLBACK (Scan_Fill_Tag_Generate_Preview),
2355 self);
2356
2357 /* Load masks into the combobox from a file. */
2358 Load_Scan_Tag_Masks_List (priv->fill_masks_model, MASK_EDITOR_TEXT,
2359 Scan_Masks);
2360 g_settings_bind (MainSettings, "scan-tag-default-mask",
2361 gtk_bin_get_child (GTK_BIN (priv->fill_combo)),
2362 "text", G_SETTINGS_BIND_DEFAULT);
2363 Add_String_To_Combo_List (priv->fill_masks_model,
2364 gtk_entry_get_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (priv->fill_combo)))));
2365
2366 /* Mask status icon. Signal connection to check if mask is correct in the
2367 * mask entry. */
2368 g_signal_connect (gtk_bin_get_child (GTK_BIN (priv->fill_combo)),
2369 "changed", G_CALLBACK (entry_check_scan_tag_mask),
2370 NULL);
2371
2372 /* Frame for Rename File. */
2373 /* Signal to generate preview (preview of the new filename). */
2374 g_signal_connect_swapped (gtk_bin_get_child (GTK_BIN (priv->rename_combo)),
2375 "changed",
2376 G_CALLBACK (Scan_Rename_File_Generate_Preview),
2377 self);
2378
2379 /* Load masks into the combobox from a file. */
2380 Load_Rename_File_Masks_List (priv->rename_masks_model, MASK_EDITOR_TEXT,
2381 Rename_File_Masks);
2382 g_settings_bind (MainSettings, "rename-file-default-mask",
2383 gtk_bin_get_child (GTK_BIN (priv->rename_combo)),
2384 "text", G_SETTINGS_BIND_DEFAULT);
2385 Add_String_To_Combo_List (priv->rename_masks_model,
2386 gtk_entry_get_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (priv->rename_combo)))));
2387
2388 /* Mask status icon. Signal connection to check if mask is correct to the
2389 * mask entry. */
2390 g_signal_connect (gtk_bin_get_child (GTK_BIN (priv->rename_combo)),
2391 "changed", G_CALLBACK (entry_check_rename_file_mask),
2392 NULL);
2393
2394 /* Group: select entry fields to process */
2395 init_process_field_check (priv->process_filename_check);
2396 init_process_field_check (priv->process_title_check);
2397 init_process_field_check (priv->process_artist_check);
2398 init_process_field_check (priv->process_album_artist_check);
2399 init_process_field_check (priv->process_album_check);
2400 init_process_field_check (priv->process_genre_check);
2401 init_process_field_check (priv->process_comment_check);
2402 init_process_field_check (priv->process_composer_check);
2403 init_process_field_check (priv->process_orig_artist_check);
2404 init_process_field_check (priv->process_copyright_check);
2405 init_process_field_check (priv->process_url_check);
2406 init_process_field_check (priv->process_encoded_by_check);
2407
2408 g_signal_connect_swapped (MainSettings, "changed::process-fields",
2409 G_CALLBACK (on_process_fields_changed), self);
2410
2411 /* Group: character conversion */
2412 g_settings_bind_with_mapping (MainSettings, "process-convert",
2413 priv->convert_space_radio, "active",
2414 G_SETTINGS_BIND_DEFAULT,
2415 et_settings_enum_radio_get,
2416 et_settings_enum_radio_set,
2417 priv->convert_space_radio, NULL);
2418 g_settings_bind_with_mapping (MainSettings, "process-convert",
2419 priv->convert_underscores_radio, "active",
2420 G_SETTINGS_BIND_DEFAULT,
2421 et_settings_enum_radio_get,
2422 et_settings_enum_radio_set,
2423 priv->convert_underscores_radio, NULL);
2424 g_settings_bind_with_mapping (MainSettings, "process-convert",
2425 priv->convert_string_radio, "active",
2426 G_SETTINGS_BIND_DEFAULT,
2427 et_settings_enum_radio_get,
2428 et_settings_enum_radio_set,
2429 priv->convert_string_radio, NULL);
2430 g_settings_bind_with_mapping (MainSettings, "process-convert",
2431 priv->convert_none_radio, "active",
2432 G_SETTINGS_BIND_DEFAULT,
2433 et_settings_enum_radio_get,
2434 et_settings_enum_radio_set,
2435 priv->convert_none_radio, NULL);
2436 g_settings_bind (MainSettings, "process-convert-characters-from",
2437 priv->convert_from_entry, "text",
2438 G_SETTINGS_BIND_DEFAULT);
2439 g_settings_bind (MainSettings, "process-convert-characters-to",
2440 priv->convert_to_entry, "text", G_SETTINGS_BIND_DEFAULT);
2441
2442 /* Group: capitalize, ... */
2443 g_settings_bind (MainSettings, "process-uppercase-all",
2444 priv->capitalize_all_radio, "active",
2445 G_SETTINGS_BIND_DEFAULT);
2446 g_settings_bind (MainSettings, "process-lowercase-all",
2447 priv->capitalize_lower_radio, "active",
2448 G_SETTINGS_BIND_DEFAULT);
2449 g_settings_bind (MainSettings, "process-uppercase-first-letter",
2450 priv->capitalize_first_radio, "active",
2451 G_SETTINGS_BIND_DEFAULT);
2452 g_settings_bind (MainSettings, "process-uppercase-first-letters",
2453 priv->capitalize_first_style_radio, "active",
2454 G_SETTINGS_BIND_DEFAULT);
2455 g_settings_bind (MainSettings, "process-detect-roman-numerals",
2456 priv->capitalize_roman_check, "active",
2457 G_SETTINGS_BIND_DEFAULT);
2458
2459 /* Group: insert/remove spaces */
2460 g_settings_bind (MainSettings, "process-remove-spaces",
2461 priv->spaces_remove_radio, "active",
2462 G_SETTINGS_BIND_DEFAULT);
2463 g_settings_bind (MainSettings, "process-insert-capital-spaces",
2464 priv->spaces_insert_radio, "active",
2465 G_SETTINGS_BIND_DEFAULT);
2466 g_settings_bind (MainSettings, "process-remove-duplicate-spaces",
2467 priv->spaces_insert_one_radio, "active",
2468 G_SETTINGS_BIND_DEFAULT);
2469 on_process_fields_changed (self, "process-fields", MainSettings);
2470
2471 /*
2472 * Frame to display codes legend
2473 */
2474 /* The buttons part */
2475 /* To initialize the mask status icon and visibility */
2476 g_signal_emit_by_name (gtk_bin_get_child (GTK_BIN (priv->fill_combo)),
2477 "changed");
2478 g_signal_emit_by_name (gtk_bin_get_child (GTK_BIN (priv->rename_combo)),
2479 "changed");
2480 g_signal_emit_by_name (priv->mask_entry, "changed");
2481 g_signal_emit_by_name (priv->legend_toggle, "toggled"); /* To hide legend frame */
2482 g_signal_emit_by_name (priv->mask_editor_toggle, "toggled"); /* To hide mask editor frame */
2483 g_signal_emit_by_name (priv->convert_string_radio, "toggled"); /* To enable / disable entries */
2484 g_signal_emit_by_name (priv->capitalize_roman_check, "toggled"); /* To enable / disable entries */
2485
2486 /* Activate the current menu in the option menu. */
2487 on_scan_mode_changed (self, "scan-mode", MainSettings);
2488 }
2489
2490 /*
2491 * Select the scanner to run for the current ETFile
2492 */
2493 void
Scan_Select_Mode_And_Run_Scanner(EtScanDialog * self,ET_File * ETFile)2494 Scan_Select_Mode_And_Run_Scanner (EtScanDialog *self, ET_File *ETFile)
2495 {
2496 EtScanDialogPrivate *priv;
2497 EtScanMode mode;
2498
2499 g_return_if_fail (ET_SCAN_DIALOG (self));
2500 g_return_if_fail (ETFile != NULL);
2501
2502 priv = et_scan_dialog_get_instance_private (self);
2503 mode = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook));
2504
2505 switch (mode)
2506 {
2507 case ET_SCAN_MODE_FILL_TAG:
2508 Scan_Tag_With_Mask (self, ETFile);
2509 break;
2510 case ET_SCAN_MODE_RENAME_FILE:
2511 Scan_Rename_File_With_Mask (self, ETFile);
2512 break;
2513 case ET_SCAN_MODE_PROCESS_FIELDS:
2514 Scan_Process_Fields (self, ETFile);
2515 break;
2516 default:
2517 g_assert_not_reached ();
2518 }
2519 }
2520
2521 /*
2522 * For the configuration file...
2523 */
2524 void
et_scan_dialog_apply_changes(EtScanDialog * self)2525 et_scan_dialog_apply_changes (EtScanDialog *self)
2526 {
2527 EtScanDialogPrivate *priv;
2528
2529 g_return_if_fail (ET_SCAN_DIALOG (self));
2530
2531 priv = et_scan_dialog_get_instance_private (self);
2532
2533 /* Save default masks. */
2534 Add_String_To_Combo_List (priv->fill_masks_model,
2535 gtk_entry_get_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (priv->fill_combo)))));
2536 Save_Rename_File_Masks_List (priv->fill_masks_model, MASK_EDITOR_TEXT);
2537
2538 Add_String_To_Combo_List (priv->rename_masks_model,
2539 gtk_entry_get_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (priv->rename_combo)))));
2540 Save_Rename_File_Masks_List (priv->rename_masks_model, MASK_EDITOR_TEXT);
2541 }
2542
2543
2544 /* Callback from Option button */
2545 static void
Scan_Option_Button(void)2546 Scan_Option_Button (void)
2547 {
2548 et_application_window_show_preferences_dialog_scanner (ET_APPLICATION_WINDOW (MainWindow));
2549 }
2550
2551
2552 /*
2553 * entry_check_rename_file_mask:
2554 * @entry: the entry for which to check the mask
2555 * @user_data: user data set when the signal was connected
2556 *
2557 * Display an icon in the entry if the current text contains an invalid mask
2558 * for scanning tags.
2559 */
2560 static void
entry_check_scan_tag_mask(GtkEntry * entry,gpointer user_data)2561 entry_check_scan_tag_mask (GtkEntry *entry, gpointer user_data)
2562 {
2563 gchar *tmp = NULL;
2564 gchar *mask = NULL;
2565 gint loop = 0;
2566
2567 g_return_if_fail (entry != NULL);
2568
2569 mask = g_strdup (gtk_entry_get_text (entry));
2570
2571 if (et_str_empty (mask))
2572 goto Bad_Mask;
2573
2574 while (mask)
2575 {
2576 if ( (tmp=strrchr(mask,'%'))==NULL )
2577 {
2578 if (loop==0)
2579 /* There is no code the first time => not accepted */
2580 goto Bad_Mask;
2581 else
2582 /* There is no more code => accepted */
2583 goto Good_Mask;
2584 }
2585 if ( strlen(tmp)>1
2586 && (tmp[1]=='a' || tmp[1]=='b' || tmp[1]=='c' || tmp[1]=='d' || tmp[1]=='p' ||
2587 tmp[1]=='r' || tmp[1]=='e' || tmp[1]=='g' || tmp[1]=='i' || tmp[1]=='l' ||
2588 tmp[1]=='o' || tmp[1]=='n' || tmp[1]=='t' || tmp[1]=='u' || tmp[1]=='y' ) )
2589 {
2590 /* Code is correct */
2591 *(mask+strlen(mask)-strlen(tmp)) = '\0';
2592 }else
2593 {
2594 goto Bad_Mask;
2595 }
2596
2597 /* Check the following code and separator */
2598 if ( (tmp=strrchr(mask,'%'))==NULL )
2599 /* There is no more code => accepted */
2600 goto Good_Mask;
2601
2602 if ( strlen(tmp)>2
2603 && (tmp[1]=='a' || tmp[1]=='b' || tmp[1]=='c' || tmp[1]=='d' || tmp[1]=='p' ||
2604 tmp[1]=='r' || tmp[1]=='e' || tmp[1]=='g' || tmp[1]=='i' || tmp[1]=='l' ||
2605 tmp[1]=='o' || tmp[1]=='n' || tmp[1]=='t' || tmp[1]=='u' || tmp[1]=='y' ) )
2606 {
2607 /* There is a separator and code is correct */
2608 *(mask+strlen(mask)-strlen(tmp)) = '\0';
2609 }else
2610 {
2611 goto Bad_Mask;
2612 }
2613 loop++;
2614 }
2615
2616 Bad_Mask:
2617 g_free(mask);
2618 gtk_entry_set_icon_from_icon_name (entry, GTK_ENTRY_ICON_SECONDARY,
2619 "emblem-unreadable");
2620 gtk_entry_set_icon_tooltip_text (entry, GTK_ENTRY_ICON_SECONDARY,
2621 _("Invalid scanner mask"));
2622 return;
2623
2624 Good_Mask:
2625 g_free(mask);
2626 gtk_entry_set_icon_from_icon_name (entry, GTK_ENTRY_ICON_SECONDARY,
2627 NULL);
2628 }
2629
2630 /*
2631 * entry_check_rename_file_mask:
2632 * @entry: the entry for which to check the mask
2633 * @user_data: user data set when the signal was connected
2634 *
2635 * Display an icon in the entry if the current text contains an invalid mask
2636 * for renaming files.
2637 */
2638 void
entry_check_rename_file_mask(GtkEntry * entry,gpointer user_data)2639 entry_check_rename_file_mask (GtkEntry *entry, gpointer user_data)
2640 {
2641 gchar *tmp = NULL;
2642 gchar *mask = NULL;
2643
2644 g_return_if_fail (entry != NULL);
2645
2646 mask = g_strdup (gtk_entry_get_text (entry));
2647
2648 if (et_str_empty (mask))
2649 goto Bad_Mask;
2650
2651 // Not a valid path....
2652 if ( strstr(mask,"//") != NULL
2653 || strstr(mask,"./") != NULL
2654 || strstr(mask,"data/") != NULL)
2655 goto Bad_Mask;
2656
2657 do
2658 {
2659 if ( (tmp=strrchr(mask,'%'))==NULL )
2660 {
2661 /* There is no more code. */
2662 /* No code in mask is accepted. */
2663 goto Good_Mask;
2664 }
2665 if ( strlen(tmp)>1
2666 && (tmp[1]=='a' || tmp[1]=='b' || tmp[1]=='c' || tmp[1]=='d' || tmp[1]=='p' ||
2667 tmp[1]=='r' || tmp[1]=='e' || tmp[1]=='g' || tmp[1]=='i' || tmp[1]=='l' ||
2668 tmp[1]=='o' || tmp[1]=='n' || tmp[1]=='t' || tmp[1]=='u' || tmp[1]=='y' ) )
2669 {
2670 /* The code is valid. */
2671 /* No separator is accepted. */
2672 *(mask+strlen(mask)-strlen(tmp)) = '\0';
2673 }else
2674 {
2675 goto Bad_Mask;
2676 }
2677 } while (mask);
2678
2679 Bad_Mask:
2680 g_free(mask);
2681 gtk_entry_set_icon_from_icon_name (entry, GTK_ENTRY_ICON_SECONDARY,
2682 "emblem-unreadable");
2683 gtk_entry_set_icon_tooltip_text (entry, GTK_ENTRY_ICON_SECONDARY,
2684 _("Invalid scanner mask"));
2685 return;
2686
2687 Good_Mask:
2688 g_free(mask);
2689 gtk_entry_set_icon_from_icon_name (entry, GTK_ENTRY_ICON_SECONDARY,
2690 NULL);
2691 }
2692
2693 void
et_scan_dialog_scan_selected_files(EtScanDialog * self)2694 et_scan_dialog_scan_selected_files (EtScanDialog *self)
2695 {
2696 gint progress_bar_index;
2697 gint selectcount;
2698 gchar progress_bar_text[30];
2699 double fraction;
2700 GList *selfilelist = NULL;
2701 GList *l;
2702 ET_File *etfile;
2703 EtApplicationWindow *window;
2704 GtkTreeSelection *selection;
2705
2706 g_return_if_fail (ETCore->ETFileDisplayedList != NULL);
2707
2708 window = ET_APPLICATION_WINDOW (MainWindow);
2709 et_application_window_update_et_file_from_ui (window);
2710
2711 /* Initialize status bar */
2712 selection = et_application_window_browser_get_selection (window);
2713 selectcount = gtk_tree_selection_count_selected_rows (selection);
2714 et_application_window_progress_set_fraction (window, 0.0);
2715 progress_bar_index = 0;
2716 g_snprintf(progress_bar_text, 30, "%d/%d", progress_bar_index, selectcount);
2717 et_application_window_progress_set_text (window, progress_bar_text);
2718
2719 /* Set to unsensitive all command buttons (except Quit button) */
2720 et_application_window_disable_command_actions (window);
2721
2722 progress_bar_index = 0;
2723
2724 selfilelist = gtk_tree_selection_get_selected_rows(selection, NULL);
2725
2726 for (l = selfilelist; l != NULL; l = g_list_next (l))
2727 {
2728 etfile = et_application_window_browser_get_et_file_from_path (window,
2729 l->data);
2730
2731 /* Run the current scanner. */
2732 Scan_Select_Mode_And_Run_Scanner (self, etfile);
2733
2734 fraction = (++progress_bar_index) / (double) selectcount;
2735 et_application_window_progress_set_fraction (window, fraction);
2736 g_snprintf(progress_bar_text, 30, "%d/%d", progress_bar_index, selectcount);
2737 et_application_window_progress_set_text (window, progress_bar_text);
2738
2739 /* Needed to refresh status bar */
2740 while (gtk_events_pending())
2741 gtk_main_iteration();
2742 }
2743
2744 g_list_free_full (selfilelist, (GDestroyNotify)gtk_tree_path_free);
2745
2746 /* Refresh the whole list (faster than file by file) to show changes. */
2747 et_application_window_browser_refresh_list (window);
2748
2749 /* Display the current file */
2750 et_application_window_display_et_file (window, ETCore->ETFileDisplayed);
2751
2752 /* To update state of command buttons */
2753 et_application_window_update_actions (window);
2754
2755 et_application_window_progress_set_text (window, "");
2756 et_application_window_progress_set_fraction (window, 0.0);
2757 et_application_window_status_bar_message (window,
2758 _("All tags have been scanned"),
2759 TRUE);
2760 }
2761
2762 /*
2763 * et_scan_on_response:
2764 * @dialog: the scanner window
2765 * @response_id: the #GtkResponseType corresponding to the dialog event
2766 * @user_data: user data set when the signal was connected
2767 *
2768 * Handle the response signal of the scanner dialog.
2769 */
2770 static void
et_scan_on_response(GtkDialog * dialog,gint response_id,gpointer user_data)2771 et_scan_on_response (GtkDialog *dialog, gint response_id, gpointer user_data)
2772 {
2773 switch (response_id)
2774 {
2775 case GTK_RESPONSE_APPLY:
2776 et_scan_dialog_scan_selected_files (ET_SCAN_DIALOG (dialog));
2777 break;
2778 case GTK_RESPONSE_CLOSE:
2779 gtk_widget_hide (GTK_WIDGET (dialog));
2780 break;
2781 case GTK_RESPONSE_DELETE_EVENT:
2782 break;
2783 default:
2784 g_assert_not_reached ();
2785 break;
2786 }
2787 }
2788
2789 static void
et_scan_dialog_init(EtScanDialog * self)2790 et_scan_dialog_init (EtScanDialog *self)
2791 {
2792 gtk_widget_init_template (GTK_WIDGET (self));
2793 create_scan_dialog (self);
2794 }
2795
2796 static void
et_scan_dialog_class_init(EtScanDialogClass * klass)2797 et_scan_dialog_class_init (EtScanDialogClass *klass)
2798 {
2799 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
2800
2801 gtk_widget_class_set_template_from_resource (widget_class,
2802 "/org/gnome/EasyTAG/scan_dialog.ui");
2803 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2804 notebook);
2805 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2806 fill_grid);
2807 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2808 fill_combo);
2809 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2810 rename_grid);
2811 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2812 rename_combo);
2813 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2814 mask_editor_toggle);
2815 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2816 fill_masks_model);
2817 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2818 rename_masks_model);
2819 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2820 mask_view);
2821 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2822 mask_entry);
2823 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2824 legend_grid);
2825 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2826 editor_grid);
2827 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2828 legend_toggle);
2829 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2830 mask_editor_toggle);
2831 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2832 process_filename_check);
2833 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2834 process_title_check);
2835 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2836 process_artist_check);
2837 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2838 process_album_artist_check);
2839 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2840 process_album_check);
2841 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2842 process_genre_check);
2843 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2844 process_comment_check);
2845 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2846 process_composer_check);
2847 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2848 process_orig_artist_check);
2849 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2850 process_copyright_check);
2851 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2852 process_url_check);
2853 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2854 process_encoded_by_check);
2855 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2856 convert_space_radio);
2857 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2858 convert_underscores_radio);
2859 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2860 convert_string_radio);
2861 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2862 convert_none_radio);
2863 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2864 convert_to_entry);
2865 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2866 convert_from_entry);
2867 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2868 convert_to_label);
2869 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2870 capitalize_all_radio);
2871 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2872 capitalize_lower_radio);
2873 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2874 capitalize_first_radio);
2875 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2876 capitalize_first_style_radio);
2877 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2878 capitalize_roman_check);
2879 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2880 spaces_remove_radio);
2881 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2882 spaces_insert_radio);
2883 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2884 spaces_insert_one_radio);
2885 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2886 fill_preview_label);
2887 gtk_widget_class_bind_template_child_private (widget_class, EtScanDialog,
2888 rename_preview_label);
2889 gtk_widget_class_bind_template_callback (widget_class,
2890 entry_check_scan_tag_mask);
2891 gtk_widget_class_bind_template_callback (widget_class, et_scan_on_hide);
2892 gtk_widget_class_bind_template_callback (widget_class,
2893 et_scan_on_response);
2894 gtk_widget_class_bind_template_callback (widget_class,
2895 Mask_Editor_Entry_Changed);
2896 gtk_widget_class_bind_template_callback (widget_class,
2897 Mask_Editor_List_Add);
2898 gtk_widget_class_bind_template_callback (widget_class,
2899 Mask_Editor_List_Add);
2900 gtk_widget_class_bind_template_callback (widget_class,
2901 Mask_Editor_List_Duplicate);
2902 gtk_widget_class_bind_template_callback (widget_class,
2903 Mask_Editor_List_Key_Press);
2904 gtk_widget_class_bind_template_callback (widget_class,
2905 Mask_Editor_List_Move_Down);
2906 gtk_widget_class_bind_template_callback (widget_class,
2907 Mask_Editor_List_Move_Up);
2908 gtk_widget_class_bind_template_callback (widget_class,
2909 Mask_Editor_List_New);
2910 gtk_widget_class_bind_template_callback (widget_class,
2911 Mask_Editor_List_Remove);
2912 gtk_widget_class_bind_template_callback (widget_class,
2913 Mask_Editor_List_Row_Selected);
2914 gtk_widget_class_bind_template_callback (widget_class,
2915 Mask_Editor_List_Save);
2916 gtk_widget_class_bind_template_callback (widget_class,
2917 Process_Fields_Convert_Check_Button_Toggled);
2918 gtk_widget_class_bind_template_callback (widget_class,
2919 Process_Fields_First_Letters_Check_Button_Toggled);
2920 gtk_widget_class_bind_template_callback (widget_class, Scan_Option_Button);
2921 gtk_widget_class_bind_template_callback (widget_class,
2922 Scan_Rename_File_Prefix_Path);
2923 gtk_widget_class_bind_template_callback (widget_class,
2924 Scan_Toggle_Legend_Button);
2925 gtk_widget_class_bind_template_callback (widget_class,
2926 Scan_Toggle_Mask_Editor_Button);
2927 }
2928
2929 /*
2930 * et_scan_dialog_new:
2931 *
2932 * Create a new EtScanDialog instance.
2933 *
2934 * Returns: a new #EtScanDialog
2935 */
2936 EtScanDialog *
et_scan_dialog_new(GtkWindow * parent)2937 et_scan_dialog_new (GtkWindow *parent)
2938 {
2939 g_return_val_if_fail (GTK_WINDOW (parent), NULL);
2940
2941 return g_object_new (ET_TYPE_SCAN_DIALOG, "transient-for", parent, NULL);
2942 }
2943