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 ¤tFile, (GtkTreePath *)l->data))
139 {
140 gtk_tree_model_get(GTK_TREE_MODEL(priv->search_results_model), ¤tFile,
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