1 /* EasyTAG - tag editor for audio files
2  * Copyright (C) 2013-2015  David King <amigadave@amigadave.com>
3  *
4  * This program is free software; you can redistribute it and/or modify it
5  * under the terms of the GNU General Public License as published by the Free
6  * Software Foundation; either version 2 of the License, or (at your option)
7  * any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but WITHOUT
10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
12  * more details.
13  *
14  * You should have received a copy of the GNU General Public License along with
15  * this program; if not, write to the Free Software Foundation, Inc., 51
16  * Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17  */
18 
19 #include "config.h"
20 
21 #include "search_dialog.h"
22 
23 #include <glib/gi18n.h>
24 
25 #include "application_window.h"
26 #include "browser.h"
27 #include "charset.h"
28 #include "easytag.h"
29 #include "log.h"
30 #include "misc.h"
31 #include "picture.h"
32 #include "scan_dialog.h"
33 #include "setting.h"
34 
35 typedef struct
36 {
37     GtkWidget *search_find_button;
38     GtkWidget *search_string_combo;
39     GtkListStore *search_string_model;
40     GtkWidget *search_filename_check;
41     GtkWidget *search_tag_check;
42     GtkWidget *search_case_check;
43     GtkWidget *search_results_view;
44     GtkListStore *search_results_model;
45     GtkWidget *status_bar;
46     guint status_bar_context;
47 } EtSearchDialogPrivate;
48 
49 G_DEFINE_TYPE_WITH_PRIVATE (EtSearchDialog, et_search_dialog, GTK_TYPE_DIALOG)
50 
51 enum
52 {
53     // Columns for titles
54     SEARCH_RESULT_FILENAME = 0,
55     SEARCH_RESULT_TITLE,
56     SEARCH_RESULT_ARTIST,
57     SEARCH_RESULT_ALBUM_ARTIST,
58     SEARCH_RESULT_ALBUM,
59     SEARCH_RESULT_DISC_NUMBER,
60     SEARCH_RESULT_YEAR,
61     SEARCH_RESULT_TRACK,
62     SEARCH_RESULT_GENRE,
63     SEARCH_RESULT_COMMENT,
64     SEARCH_RESULT_COMPOSER,
65     SEARCH_RESULT_ORIG_ARTIST,
66     SEARCH_RESULT_COPYRIGHT,
67     SEARCH_RESULT_URL,
68     SEARCH_RESULT_ENCODED_BY,
69 
70     // Columns for pango style (normal/bold)
71     SEARCH_RESULT_FILENAME_WEIGHT,
72     SEARCH_RESULT_TITLE_WEIGHT,
73     SEARCH_RESULT_ARTIST_WEIGHT,
74     SEARCH_RESULT_ALBUM_ARTIST_WEIGHT,
75     SEARCH_RESULT_ALBUM_WEIGHT,
76     SEARCH_RESULT_DISC_NUMBER_WEIGHT,
77     SEARCH_RESULT_YEAR_WEIGHT,
78     SEARCH_RESULT_TRACK_WEIGHT,
79     SEARCH_RESULT_GENRE_WEIGHT,
80     SEARCH_RESULT_COMMENT_WEIGHT,
81     SEARCH_RESULT_COMPOSER_WEIGHT,
82     SEARCH_RESULT_ORIG_ARTIST_WEIGHT,
83     SEARCH_RESULT_COPYRIGHT_WEIGHT,
84     SEARCH_RESULT_URL_WEIGHT,
85     SEARCH_RESULT_ENCODED_BY_WEIGHT,
86 
87     // Columns for color (normal/red)
88     SEARCH_RESULT_FILENAME_FOREGROUND,
89     SEARCH_RESULT_TITLE_FOREGROUND,
90     SEARCH_RESULT_ARTIST_FOREGROUND,
91     SEARCH_RESULT_ALBUM_ARTIST_FOREGROUND,
92     SEARCH_RESULT_ALBUM_FOREGROUND,
93     SEARCH_RESULT_DISC_NUMBER_FOREGROUND,
94     SEARCH_RESULT_YEAR_FOREGROUND,
95     SEARCH_RESULT_TRACK_FOREGROUND,
96     SEARCH_RESULT_GENRE_FOREGROUND,
97     SEARCH_RESULT_COMMENT_FOREGROUND,
98     SEARCH_RESULT_COMPOSER_FOREGROUND,
99     SEARCH_RESULT_ORIG_ARTIST_FOREGROUND,
100     SEARCH_RESULT_COPYRIGHT_FOREGROUND,
101     SEARCH_RESULT_URL_FOREGROUND,
102     SEARCH_RESULT_ENCODED_BY_FOREGROUND,
103 
104     SEARCH_RESULT_POINTER,
105     SEARCH_COLUMN_COUNT
106 };
107 
108 /*
109  * Callback to select-row event
110  * Select all results that are selected in the search result list also in the browser list
111  */
112 static void
Search_Result_List_Row_Selected(GtkTreeSelection * selection,gpointer user_data)113 Search_Result_List_Row_Selected (GtkTreeSelection *selection,
114                                  gpointer user_data)
115 {
116     EtSearchDialogPrivate *priv;
117     GList       *selectedRows;
118     GList *l;
119     ET_File     *ETFile;
120     GtkTreeIter  currentFile;
121 
122     priv = et_search_dialog_get_instance_private (ET_SEARCH_DIALOG (user_data));
123 
124     selectedRows = gtk_tree_selection_get_selected_rows(selection, NULL);
125 
126     /* We might be called with no rows selected */
127     if (!selectedRows)
128     {
129         return;
130     }
131 
132     /* Unselect files in the main list before re-selecting them... */
133     et_application_window_browser_unselect_all (ET_APPLICATION_WINDOW (MainWindow));
134 
135     for (l = selectedRows; l != NULL; l = g_list_next (l))
136     {
137         if (gtk_tree_model_get_iter (GTK_TREE_MODEL (priv->search_results_model),
138                                      &currentFile, (GtkTreePath *)l->data))
139         {
140             gtk_tree_model_get(GTK_TREE_MODEL(priv->search_results_model), &currentFile,
141                                SEARCH_RESULT_POINTER, &ETFile, -1);
142             /* Select the files (but don't display them to increase speed). */
143             et_application_window_browser_select_file_by_et_file (ET_APPLICATION_WINDOW (MainWindow),
144                                                                   ETFile,
145                                                                   TRUE);
146             /* Display only the last file (to increase speed). */
147             if (!selectedRows->next)
148             {
149                 et_application_window_select_file_by_et_file (ET_APPLICATION_WINDOW (MainWindow),
150                                                               ETFile);
151             }
152         }
153     }
154 
155     g_list_free_full (selectedRows, (GDestroyNotify)gtk_tree_path_free);
156 }
157 
158 /*
159  * Add_Row_To_Search_Result_List:
160  * @self: an #EtSearchDialog
161  * @ETFile: a file with tags in which to search
162  * @string_to_search: the search term
163  *
164  * Search for the given @string_to_search in tags and the filename from
165  * @ETFile. Add the result row, corresctly-formatted to highlight matches, to
166  * the tree view in @self.
167  */
168 static void
Add_Row_To_Search_Result_List(EtSearchDialog * self,const ET_File * ETFile,const gchar * string_to_search)169 Add_Row_To_Search_Result_List (EtSearchDialog *self,
170                                const ET_File *ETFile,
171                                const gchar *string_to_search)
172 {
173     EtSearchDialogPrivate *priv;
174     const gchar *haystacks[15]; /* 15 columns to display. */
175     gint weights[15] = { PANGO_WEIGHT_NORMAL, PANGO_WEIGHT_NORMAL,
176                          PANGO_WEIGHT_NORMAL, PANGO_WEIGHT_NORMAL,
177                          PANGO_WEIGHT_NORMAL, PANGO_WEIGHT_NORMAL,
178                          PANGO_WEIGHT_NORMAL, PANGO_WEIGHT_NORMAL,
179                          PANGO_WEIGHT_NORMAL, PANGO_WEIGHT_NORMAL,
180                          PANGO_WEIGHT_NORMAL, PANGO_WEIGHT_NORMAL,
181                          PANGO_WEIGHT_NORMAL, PANGO_WEIGHT_NORMAL,
182                          PANGO_WEIGHT_NORMAL };
183     GdkRGBA *colors[15] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
184                             NULL, NULL, NULL, NULL, NULL, NULL, NULL};
185     gchar *display_basename;
186     const gchar *track;
187     const gchar *track_total;
188     const gchar *disc_number;
189     const gchar *disc_total;
190     gchar *discs = NULL;
191     gchar *tracks = NULL;
192     gboolean case_sensitive;
193     gsize column;
194 
195     priv = et_search_dialog_get_instance_private (self);
196 
197     if (!ETFile || !string_to_search)
198         return;
199 
200     case_sensitive = g_settings_get_boolean (MainSettings, "search-case-sensitive");
201 
202     /* Most fields can be taken from the tag as-is. */
203     haystacks[SEARCH_RESULT_TITLE] = ((File_Tag *)ETFile->FileTag->data)->title;
204     haystacks[SEARCH_RESULT_ARTIST] = ((File_Tag *)ETFile->FileTag->data)->artist;
205     haystacks[SEARCH_RESULT_ALBUM_ARTIST] = ((File_Tag *)ETFile->FileTag->data)->album_artist;
206     haystacks[SEARCH_RESULT_ALBUM] = ((File_Tag *)ETFile->FileTag->data)->album;
207     haystacks[SEARCH_RESULT_YEAR] = ((File_Tag *)ETFile->FileTag->data)->year;
208     haystacks[SEARCH_RESULT_GENRE] = ((File_Tag *)ETFile->FileTag->data)->genre;
209     haystacks[SEARCH_RESULT_COMMENT] = ((File_Tag *)ETFile->FileTag->data)->comment;
210     haystacks[SEARCH_RESULT_COMPOSER] = ((File_Tag *)ETFile->FileTag->data)->composer;
211     haystacks[SEARCH_RESULT_ORIG_ARTIST] = ((File_Tag *)ETFile->FileTag->data)->orig_artist;
212     haystacks[SEARCH_RESULT_COPYRIGHT] = ((File_Tag *)ETFile->FileTag->data)->copyright;
213     haystacks[SEARCH_RESULT_URL] = ((File_Tag *)ETFile->FileTag->data)->url;
214     haystacks[SEARCH_RESULT_ENCODED_BY] = ((File_Tag *)ETFile->FileTag->data)->encoded_by;
215 
216     /* Some fields need extra allocations. */
217     display_basename = g_path_get_basename (((File_Name *)ETFile->FileNameNew->data)->value_utf8);
218     haystacks[SEARCH_RESULT_FILENAME] = display_basename;
219 
220     /* Disc Number. */
221     disc_number = ((File_Tag *)ETFile->FileTag->data)->disc_number;
222     disc_total = ((File_Tag *)ETFile->FileTag->data)->disc_total;
223 
224     if (disc_number)
225     {
226         if (disc_total)
227         {
228             discs = g_strconcat (disc_number, "/", disc_total, NULL);
229             haystacks[SEARCH_RESULT_DISC_NUMBER] = discs;
230         }
231         else
232         {
233             haystacks[SEARCH_RESULT_DISC_NUMBER] = disc_number;
234         }
235     }
236     else
237     {
238         haystacks[SEARCH_RESULT_DISC_NUMBER] = NULL;
239     }
240 
241     /* Track. */
242     track = ((File_Tag *)ETFile->FileTag->data)->track;
243     track_total = ((File_Tag *)ETFile->FileTag->data)->track_total;
244 
245     if (track)
246     {
247         if (track_total)
248         {
249             tracks = g_strconcat (track, "/", track_total, NULL);
250             haystacks[SEARCH_RESULT_TRACK] = tracks;
251         }
252         else
253         {
254             haystacks[SEARCH_RESULT_TRACK] = track;
255         }
256     }
257     else
258     {
259         haystacks[SEARCH_RESULT_TRACK] = NULL;
260     }
261 
262     /* Highlight the keywords in the result list. Don't display files in red if
263      * the searched string is '' (to display all files). */
264     for (column = 0; column < G_N_ELEMENTS (haystacks); column++)
265     {
266         gchar *needle;
267         gchar *haystack;
268 
269         /* Already checked if string_to_search is NULL. */
270         needle = g_utf8_normalize (string_to_search, -1, G_NORMALIZE_DEFAULT);
271         haystack = haystacks[column] ? g_utf8_normalize (haystacks[column], -1,
272                                                          G_NORMALIZE_DEFAULT)
273                                      : NULL;
274 
275         if (case_sensitive)
276         {
277             if (haystack && !et_str_empty (needle)
278                 && strstr (haystack, needle))
279             {
280 
281                 if (g_settings_get_boolean (MainSettings, "file-changed-bold"))
282                 {
283                     weights[column] = PANGO_WEIGHT_BOLD;
284                 }
285                 else
286                 {
287                     colors[column] = &RED;
288                 }
289             }
290         }
291         else
292         {
293             /* Search wasn't case-sensitive. */
294             gchar *list_text;
295             gchar *string_to_search2;
296 
297             if (!haystack)
298             {
299                 g_free (needle);
300                 continue;
301             }
302 
303             string_to_search2 = g_utf8_casefold (needle, -1);
304             list_text = g_utf8_casefold (haystack, -1);
305 
306             if (!et_str_empty (string_to_search2)
307                 && strstr (list_text, string_to_search2))
308             {
309                 if (g_settings_get_boolean (MainSettings, "file-changed-bold"))
310                 {
311                     weights[column] = PANGO_WEIGHT_BOLD;
312                 }
313                 else
314                 {
315                     colors[column] = &RED;
316                 }
317             }
318 
319             g_free(list_text);
320             g_free(string_to_search2);
321         }
322 
323         g_free (haystack);
324         g_free (needle);
325     }
326 
327     /* Load the row in the list. */
328     gtk_list_store_insert_with_values (priv->search_results_model, NULL, G_MAXINT,
329                                        SEARCH_RESULT_FILENAME, haystacks[SEARCH_RESULT_FILENAME],
330                                        SEARCH_RESULT_TITLE, haystacks[SEARCH_RESULT_TITLE],
331                                        SEARCH_RESULT_ARTIST, haystacks[SEARCH_RESULT_ARTIST],
332                                        SEARCH_RESULT_ALBUM_ARTIST,haystacks[SEARCH_RESULT_ALBUM_ARTIST],
333                                        SEARCH_RESULT_ALBUM, haystacks[SEARCH_RESULT_ALBUM],
334                                        SEARCH_RESULT_DISC_NUMBER, haystacks[SEARCH_RESULT_DISC_NUMBER],
335                                        SEARCH_RESULT_YEAR, haystacks[SEARCH_RESULT_YEAR],
336                                        SEARCH_RESULT_TRACK, haystacks[SEARCH_RESULT_TRACK],
337                                        SEARCH_RESULT_GENRE, haystacks[SEARCH_RESULT_GENRE],
338                                        SEARCH_RESULT_COMMENT, haystacks[SEARCH_RESULT_COMMENT],
339                                        SEARCH_RESULT_COMPOSER, haystacks[SEARCH_RESULT_COMPOSER],
340                                        SEARCH_RESULT_ORIG_ARTIST, haystacks[SEARCH_RESULT_ORIG_ARTIST],
341                                        SEARCH_RESULT_COPYRIGHT, haystacks[SEARCH_RESULT_COPYRIGHT],
342                                        SEARCH_RESULT_URL, haystacks[SEARCH_RESULT_URL],
343                                        SEARCH_RESULT_ENCODED_BY, haystacks[SEARCH_RESULT_ENCODED_BY],
344 
345                                        SEARCH_RESULT_FILENAME_WEIGHT, weights[SEARCH_RESULT_FILENAME],
346                                        SEARCH_RESULT_TITLE_WEIGHT, weights[SEARCH_RESULT_TITLE],
347                                        SEARCH_RESULT_ARTIST_WEIGHT, weights[SEARCH_RESULT_ARTIST],
348                                        SEARCH_RESULT_ALBUM_ARTIST_WEIGHT, weights[SEARCH_RESULT_ALBUM_ARTIST],
349                                        SEARCH_RESULT_ALBUM_WEIGHT, weights[SEARCH_RESULT_ALBUM],
350                                        SEARCH_RESULT_DISC_NUMBER_WEIGHT, weights[SEARCH_RESULT_DISC_NUMBER],
351                                        SEARCH_RESULT_YEAR_WEIGHT, weights[SEARCH_RESULT_YEAR],
352                                        SEARCH_RESULT_TRACK_WEIGHT, weights[SEARCH_RESULT_TRACK],
353                                        SEARCH_RESULT_GENRE_WEIGHT, weights[SEARCH_RESULT_GENRE],
354                                        SEARCH_RESULT_COMMENT_WEIGHT, weights[SEARCH_RESULT_COMMENT],
355                                        SEARCH_RESULT_COMPOSER_WEIGHT, weights[SEARCH_RESULT_COMPOSER],
356                                        SEARCH_RESULT_ORIG_ARTIST_WEIGHT, weights[SEARCH_RESULT_ORIG_ARTIST],
357                                        SEARCH_RESULT_COPYRIGHT_WEIGHT, weights[SEARCH_RESULT_COPYRIGHT],
358                                        SEARCH_RESULT_URL_WEIGHT, weights[SEARCH_RESULT_URL],
359                                        SEARCH_RESULT_ENCODED_BY_WEIGHT, weights[SEARCH_RESULT_ENCODED_BY],
360 
361                                        SEARCH_RESULT_FILENAME_FOREGROUND, colors[SEARCH_RESULT_FILENAME],
362                                        SEARCH_RESULT_TITLE_FOREGROUND, colors[SEARCH_RESULT_TITLE],
363                                        SEARCH_RESULT_ARTIST_FOREGROUND, colors[SEARCH_RESULT_ARTIST],
364                                        SEARCH_RESULT_ALBUM_ARTIST_FOREGROUND, colors[SEARCH_RESULT_ALBUM_ARTIST],
365                                        SEARCH_RESULT_ALBUM_FOREGROUND, colors[SEARCH_RESULT_ALBUM],
366                                        SEARCH_RESULT_DISC_NUMBER_FOREGROUND, colors[SEARCH_RESULT_DISC_NUMBER],
367                                        SEARCH_RESULT_YEAR_FOREGROUND, colors[SEARCH_RESULT_YEAR],
368                                        SEARCH_RESULT_TRACK_FOREGROUND, colors[SEARCH_RESULT_TRACK],
369                                        SEARCH_RESULT_GENRE_FOREGROUND, colors[SEARCH_RESULT_GENRE],
370                                        SEARCH_RESULT_COMMENT_FOREGROUND, colors[SEARCH_RESULT_COMMENT],
371                                        SEARCH_RESULT_COMPOSER_FOREGROUND, colors[SEARCH_RESULT_COMPOSER],
372                                        SEARCH_RESULT_ORIG_ARTIST_FOREGROUND, colors[SEARCH_RESULT_ORIG_ARTIST],
373                                        SEARCH_RESULT_COPYRIGHT_FOREGROUND, colors[SEARCH_RESULT_COPYRIGHT],
374                                        SEARCH_RESULT_URL_FOREGROUND, colors[SEARCH_RESULT_URL],
375                                        SEARCH_RESULT_ENCODED_BY_FOREGROUND, colors[SEARCH_RESULT_ENCODED_BY],
376 
377                                        SEARCH_RESULT_POINTER, ETFile, -1);
378 
379     /* Frees allocated data. */
380     g_free (display_basename);
381     g_free (discs);
382     g_free (tracks);
383 }
384 
385 /*
386  * This function and the one below could do with improving
387  * as we are looking up tag data twice (once when searching, once when adding to list)
388  */
389 /*
390  * Search_File:
391  * @search_button: the search button which was clicked
392  * @user_data: the #EtSearchDialog which contains @search_button
393  *
394  * Search for the search term (in the search entry of @user_data) in the list
395  * of open files.
396  */
397 static void
Search_File(GtkWidget * search_button,gpointer user_data)398 Search_File (GtkWidget *search_button,
399              gpointer user_data)
400 {
401     EtSearchDialog *self;
402     EtSearchDialogPrivate *priv;
403     const gchar *string_to_search = NULL;
404     GList *l;
405     const ET_File *ETFile;
406     gchar *msg;
407     gint resultCount = 0;
408 
409     self = ET_SEARCH_DIALOG (user_data);
410     priv = et_search_dialog_get_instance_private (self);
411 
412     if (!priv->search_string_combo || !priv->search_filename_check || !priv->search_tag_check || !priv->search_results_view)
413         return;
414 
415     string_to_search = gtk_entry_get_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (priv->search_string_combo))));
416 
417     if (!string_to_search)
418         return;
419 
420     Add_String_To_Combo_List (priv->search_string_model, string_to_search);
421 
422     gtk_widget_set_sensitive (GTK_WIDGET (search_button), FALSE);
423     gtk_list_store_clear (priv->search_results_model);
424     gtk_statusbar_push (GTK_STATUSBAR (priv->status_bar),
425                         priv->status_bar_context, "");
426 
427     for (l = ETCore->ETFileList; l != NULL; l = g_list_next (l))
428     {
429         ETFile = (ET_File *)l->data;
430 
431         /* Search in the filename. */
432         if (g_settings_get_boolean (MainSettings, "search-filename"))
433         {
434             const gchar *filename_utf8 = ((File_Name *)ETFile->FileNameNew->data)->value_utf8;
435             gchar *basename;
436             gchar *haystack;
437             gchar *needle;
438 
439             basename = g_path_get_basename (filename_utf8);
440 
441             /* To search without case sensitivity. */
442             if (!g_settings_get_boolean (MainSettings, "search-case-sensitive"))
443             {
444                 haystack = g_utf8_casefold (basename, -1);
445                 needle = g_utf8_casefold (string_to_search, -1);
446             }
447             else
448             {
449                 haystack = g_utf8_normalize (basename, -1, G_NORMALIZE_DEFAULT);
450                 needle = g_utf8_normalize (string_to_search, -1,
451                                            G_NORMALIZE_DEFAULT);
452             }
453 
454             g_free (basename);
455 
456             if (haystack && strstr (haystack, needle))
457             {
458                 Add_Row_To_Search_Result_List (self, ETFile, needle);
459                 g_free (haystack);
460                 g_free (needle);
461                 continue;
462             }
463 
464             g_free (haystack);
465             g_free (needle);
466         }
467 
468         /* Search in the tag. */
469         if (g_settings_get_boolean (MainSettings, "search-tag"))
470         {
471             gchar *title2, *artist2, *album_artist2, *album2, *disc_number2,
472                   *disc_total2, *year2, *track2, *track_total2, *genre2,
473                   *comment2, *composer2, *orig_artist2, *copyright2, *url2,
474                   *encoded_by2;
475             gchar *needle;
476             const File_Tag *FileTag = (File_Tag *)ETFile->FileTag->data;
477 
478             if (!g_settings_get_boolean (MainSettings, "search-case-sensitive"))
479             {
480                 /* To search without case sensitivity. */
481                 title2 = FileTag->title ? g_utf8_casefold (FileTag->title, -1)
482                                         : NULL;
483                 artist2 = FileTag->artist ? g_utf8_casefold (FileTag->artist,
484                                                              -1)
485                                           : NULL;
486                 album_artist2 = FileTag->album_artist ? g_utf8_casefold (FileTag->album_artist,
487                                                                          -1)
488                                           : NULL;
489                 album2 = FileTag->album ? g_utf8_casefold (FileTag->album, -1)
490                                           : NULL;
491                 disc_number2 = FileTag->disc_number ? g_utf8_casefold (FileTag->disc_number,
492                                                                        -1)
493                                                     : NULL;
494                 disc_total2 = FileTag->disc_total ? g_utf8_casefold (FileTag->disc_total,
495                                                                      -1)
496                                                   : NULL;
497                 year2 = FileTag->year ? g_utf8_casefold (FileTag->year, -1)
498                                       : NULL;
499                 track2 = FileTag->track ? g_utf8_casefold (FileTag->track, -1)
500                                         : NULL;
501                 track_total2 = FileTag->track_total ? g_utf8_casefold (FileTag->track_total,
502                                                                        -1)
503                                                     : NULL;
504                 genre2 = FileTag->genre ? g_utf8_casefold (FileTag->genre, -1)
505                                         : NULL;
506                 comment2 = FileTag->comment ? g_utf8_casefold (FileTag->comment,
507                                                                -1)
508                                             : NULL;
509                 composer2 = FileTag->composer ? g_utf8_casefold (FileTag->composer,
510                                                                  -1)
511                                               : NULL;
512                 orig_artist2 = FileTag->orig_artist ? g_utf8_casefold (FileTag->orig_artist,
513                                                                        -1)
514                                                     : NULL;
515                 copyright2 = FileTag->copyright ? g_utf8_casefold (FileTag->copyright,
516                                                                    -1)
517                                                 : NULL;
518                 url2 = FileTag->url ? g_utf8_casefold (FileTag->url, -1)
519                                     : NULL;
520                 encoded_by2 = FileTag->encoded_by ? g_utf8_casefold (FileTag->encoded_by,
521                                                                      -1)
522                                                   : NULL;
523                 needle = g_utf8_casefold (string_to_search, -1);
524             }
525             else
526             {
527                 /* To search with case-sensitivity. */
528                 title2 = FileTag->disc_number ? g_utf8_normalize (FileTag->title,
529                                                                   -1,
530                                                                   G_NORMALIZE_DEFAULT)
531                                               : NULL;
532                 artist2 = FileTag->artist ? g_utf8_normalize (FileTag->artist,
533                                                               -1,
534                                                               G_NORMALIZE_DEFAULT)
535                                           : NULL;
536                 album_artist2 = FileTag->album_artist ? g_utf8_normalize (FileTag->album_artist,
537                                                                           -1,
538                                                                           G_NORMALIZE_DEFAULT)
539                                                       : NULL;
540                 album2 = FileTag->album ? g_utf8_normalize (FileTag->album, -1,
541                                                             G_NORMALIZE_DEFAULT)
542                                         : NULL;
543                 disc_number2 = FileTag->disc_number ? g_utf8_normalize (FileTag->disc_number,
544                                                                         -1,
545                                                                         G_NORMALIZE_DEFAULT)
546                                                      : NULL;
547                 disc_total2 = FileTag->disc_total ? g_utf8_normalize (FileTag->disc_total,
548                                                                       -1,
549                                                                       G_NORMALIZE_DEFAULT)
550                                                   : NULL;
551                 year2 = FileTag->year ? g_utf8_normalize (FileTag->year, -1,
552                                                           G_NORMALIZE_DEFAULT)
553                                       : NULL;
554                 track2 = FileTag->track ? g_utf8_normalize (FileTag->track, -1,
555                                                             G_NORMALIZE_DEFAULT)
556                                         : NULL;
557                 track_total2 = FileTag->track_total ? g_utf8_normalize (FileTag->track_total,
558                                                                         -1,
559                                                                         G_NORMALIZE_DEFAULT)
560                                                     : NULL;
561                 genre2 = FileTag->genre ? g_utf8_normalize (FileTag->genre, -1,
562                                                             G_NORMALIZE_DEFAULT)
563                                         : NULL;
564                 comment2 = FileTag->comment ? g_utf8_normalize (FileTag->comment,
565                                                                 -1,
566                                                                 G_NORMALIZE_DEFAULT)
567                                             : NULL;
568                 composer2 = FileTag->composer ? g_utf8_normalize (FileTag->composer,
569                                                                   -1,
570                                                                   G_NORMALIZE_DEFAULT)
571                                               : NULL;
572                 orig_artist2 = FileTag->orig_artist ? g_utf8_normalize (FileTag->orig_artist,
573                                                                         -1,
574                                                                         G_NORMALIZE_DEFAULT)
575                                                     : NULL;
576                 copyright2 = FileTag->copyright ? g_utf8_normalize (FileTag->copyright,
577                                                                     -1,
578                                                                     G_NORMALIZE_DEFAULT)
579                                                 : NULL;
580                 url2 = FileTag->url ? g_utf8_normalize (FileTag->url, -1,
581                                                         G_NORMALIZE_DEFAULT)
582                                     : NULL;
583                 encoded_by2 = FileTag->encoded_by ? g_utf8_normalize (FileTag->encoded_by,
584                                                                       -1,
585                                                                       G_NORMALIZE_DEFAULT)
586                                                   : NULL;
587                 needle = g_utf8_normalize (string_to_search, -1,
588                                            G_NORMALIZE_DEFAULT);
589             }
590 
591             if ((title2 && strstr (title2, needle))
592                 || (artist2 && strstr (artist2, needle))
593                 || (album_artist2 && strstr (album_artist2, needle))
594                 || (album2 && strstr (album2, needle))
595                 || (disc_number2 && strstr (disc_number2, needle))
596                 || (disc_total2 && strstr (disc_total2, needle))
597                 || (year2 && strstr (year2, needle))
598                 || (track2 && strstr (track2, needle))
599                 || (track_total2 && strstr (track_total2, needle))
600                 || (genre2 && strstr (genre2, needle))
601                 || (comment2 && strstr (comment2, needle))
602                 || (composer2 && strstr (composer2, needle))
603                 || (orig_artist2 && strstr (orig_artist2, needle))
604                 || (copyright2 && strstr (copyright2, needle))
605                 || (url2 && strstr (url2, needle))
606                 || (encoded_by2 && strstr (encoded_by2, needle)))
607             {
608                 Add_Row_To_Search_Result_List (self, ETFile, string_to_search);
609             }
610 
611             g_free (title2);
612             g_free (artist2);
613             g_free (album_artist2);
614             g_free (album2);
615             g_free (disc_number2);
616             g_free (disc_total2);
617             g_free (year2);
618             g_free (track2);
619             g_free (track_total2);
620             g_free (genre2);
621             g_free (comment2);
622             g_free (composer2);
623             g_free (orig_artist2);
624             g_free (copyright2);
625             g_free (url2);
626             g_free (encoded_by2);
627             g_free (needle);
628         }
629     }
630 
631     gtk_widget_set_sensitive (GTK_WIDGET (search_button), TRUE);
632 
633     /* Display the number of matches in the statusbar. */
634     resultCount = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (priv->search_results_model),
635                                                   NULL);
636     msg = g_strdup_printf (ngettext ("Found one file", "Found %d files",
637                            resultCount), resultCount);
638     gtk_statusbar_push (GTK_STATUSBAR (priv->status_bar),
639                         priv->status_bar_context, msg);
640     g_free (msg);
641 
642     /* Disable result list if no row inserted. */
643     if (resultCount > 0)
644     {
645         gtk_widget_set_sensitive (GTK_WIDGET (priv->search_results_view),
646                                   TRUE);
647     }
648     else
649     {
650         gtk_widget_set_sensitive (GTK_WIDGET (priv->search_results_view),
651                                   FALSE);
652     }
653 }
654 
655 static void
on_close_clicked(GtkButton * button,gpointer user_data)656 on_close_clicked (GtkButton *button, gpointer user_data)
657 {
658     EtSearchDialog *self;
659 
660     self = ET_SEARCH_DIALOG (user_data);
661 
662     et_search_dialog_apply_changes (self);
663     gtk_widget_hide (GTK_WIDGET (self));
664 }
665 
666 static gboolean
on_delete_event(GtkWidget * widget)667 on_delete_event (GtkWidget *widget)
668 {
669     et_search_dialog_apply_changes (ET_SEARCH_DIALOG (widget));
670 
671     return TRUE;
672 }
673 
674 /*
675  * The window to search keywords in the list of files.
676  */
677 static void
create_search_dialog(EtSearchDialog * self)678 create_search_dialog (EtSearchDialog *self)
679 {
680     EtSearchDialogPrivate *priv;
681 
682     priv = et_search_dialog_get_instance_private (self);
683 
684     /* Words to search. */
685     priv->search_string_model = gtk_list_store_new (MISC_COMBO_COUNT,
686                                                     G_TYPE_STRING);
687     gtk_combo_box_set_model (GTK_COMBO_BOX (priv->search_string_combo),
688                              GTK_TREE_MODEL (priv->search_string_model));
689     g_object_unref (priv->search_string_model);
690     /* History List. */
691     Load_Search_File_List (priv->search_string_model, MISC_COMBO_TEXT);
692     gtk_entry_set_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (priv->search_string_combo))),
693                         "");
694 
695     /* Set content of the clipboard if available. */
696     gtk_editable_paste_clipboard (GTK_EDITABLE (gtk_bin_get_child (GTK_BIN (priv->search_string_combo))));
697 
698     g_settings_bind (MainSettings, "search-filename",
699                      priv->search_filename_check, "active",
700                      G_SETTINGS_BIND_DEFAULT);
701     g_settings_bind (MainSettings, "search-tag", priv->search_tag_check,
702                      "active", G_SETTINGS_BIND_DEFAULT);
703 
704     /* Property of the search. */
705     g_settings_bind (MainSettings, "search-case-sensitive",
706                      priv->search_case_check, "active",
707                      G_SETTINGS_BIND_DEFAULT);
708 
709     /* Button to run the search. */
710     gtk_widget_grab_default (priv->search_find_button);
711     g_signal_connect (gtk_bin_get_child (GTK_BIN (priv->search_string_combo)),
712                       "activate", G_CALLBACK (Search_File), self);
713 
714     /* Status bar. */
715     priv->status_bar_context = gtk_statusbar_get_context_id (GTK_STATUSBAR (priv->status_bar),
716                                                              "Messages");
717     gtk_statusbar_push (GTK_STATUSBAR (priv->status_bar),
718                         priv->status_bar_context, _("Ready to search…"));
719 }
720 
721 /*
722  * For the configuration file...
723  */
724 void
et_search_dialog_apply_changes(EtSearchDialog * self)725 et_search_dialog_apply_changes (EtSearchDialog *self)
726 {
727     EtSearchDialogPrivate *priv;
728 
729     g_return_if_fail (ET_SEARCH_DIALOG (self));
730 
731     priv = et_search_dialog_get_instance_private (self);
732 
733     Save_Search_File_List (priv->search_string_model, MISC_COMBO_TEXT);
734 }
735 
736 static void
et_search_dialog_init(EtSearchDialog * self)737 et_search_dialog_init (EtSearchDialog *self)
738 {
739     gtk_widget_init_template (GTK_WIDGET (self));
740     create_search_dialog (self);
741 }
742 
743 static void
et_search_dialog_class_init(EtSearchDialogClass * klass)744 et_search_dialog_class_init (EtSearchDialogClass *klass)
745 {
746     GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
747 
748     gtk_widget_class_set_template_from_resource (widget_class,
749                                                  "/org/gnome/EasyTAG/search_dialog.ui");
750     gtk_widget_class_bind_template_child_private (widget_class, EtSearchDialog,
751                                                   search_find_button);
752     gtk_widget_class_bind_template_child_private (widget_class, EtSearchDialog,
753                                                   search_string_combo);
754     gtk_widget_class_bind_template_child_private (widget_class, EtSearchDialog,
755                                                   search_filename_check);
756     gtk_widget_class_bind_template_child_private (widget_class, EtSearchDialog,
757                                                   search_tag_check);
758     gtk_widget_class_bind_template_child_private (widget_class, EtSearchDialog,
759                                                   search_case_check);
760     gtk_widget_class_bind_template_child_private (widget_class, EtSearchDialog,
761                                                   search_results_model);
762     gtk_widget_class_bind_template_child_private (widget_class, EtSearchDialog,
763                                                   search_results_view);
764     gtk_widget_class_bind_template_child_private (widget_class, EtSearchDialog,
765                                                   status_bar);
766     gtk_widget_class_bind_template_callback (widget_class, on_close_clicked);
767     gtk_widget_class_bind_template_callback (widget_class, on_delete_event);
768     gtk_widget_class_bind_template_callback (widget_class, Search_File);
769     gtk_widget_class_bind_template_callback (widget_class,
770                                              Search_Result_List_Row_Selected);
771 }
772 
773 /*
774  * et_search_dialog_new:
775  *
776  * Create a new EtSearchDialog instance.
777  *
778  * Returns: a new #EtSearchDialog
779  */
780 EtSearchDialog *
et_search_dialog_new(GtkWindow * parent)781 et_search_dialog_new (GtkWindow *parent)
782 {
783     g_return_val_if_fail (GTK_WINDOW (parent), NULL);
784 
785     return g_object_new (ET_TYPE_SEARCH_DIALOG, "transient-for", parent, NULL);
786 }
787