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, &regex_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, &regex_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, &currentIter))
1738     {
1739 
1740         while(TRUE)
1741         {
1742             gtk_tree_model_get(treemodel, &currentIter, 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), &currentIter))
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, &currentIter))
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, &currentFile, 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), &currentFile, &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, &currentFile, 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), &currentFile, &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