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 "load_files_dialog.h"
22 
23 #include <gdk/gdkkeysyms.h>
24 #include <glib/gi18n.h>
25 
26 #include "application_window.h"
27 #include "browser.h"
28 #include "charset.h"
29 #include "easytag.h"
30 #include "log.h"
31 #include "misc.h"
32 #include "picture.h"
33 #include "scan_dialog.h"
34 #include "setting.h"
35 
36 typedef struct
37 {
38     GtkWidget *file_chooser;
39     GtkWidget *file_load_button;
40     GtkWidget *file_content_view;
41     GtkListStore *file_content_model;
42     GtkWidget *content_reload;
43     GtkWidget *content_reload_menuitem;
44     GtkWidget *load_file_content_menu;
45     GtkWidget *file_name_view;
46     GtkListStore *file_name_model;
47     GtkWidget *load_file_name_menu;
48     GtkWidget *file_scanner_check;
49 
50     GtkWidget *file_entry;
51 } EtLoadFilesDialogPrivate;
52 
53 
54 G_DEFINE_TYPE_WITH_PRIVATE (EtLoadFilesDialog, et_load_files_dialog, GTK_TYPE_DIALOG)
55 
56 enum
57 {
58     LOAD_FILE_CONTENT_TEXT,
59     LOAD_FILE_CONTENT_COUNT
60 };
61 
62 enum
63 {
64     LOAD_FILE_NAME_TEXT,
65     LOAD_FILE_NAME_POINTER,
66     LOAD_FILE_NAME_COUNT
67 };
68 
69 /*
70  * Set the new filename of each file.
71  * Associate lines from priv->file_content_view with priv->file_name_view
72  */
73 static void
Load_Filename_Set_Filenames(EtLoadFilesDialog * self)74 Load_Filename_Set_Filenames (EtLoadFilesDialog *self)
75 {
76     EtLoadFilesDialogPrivate *priv;
77     gint row;
78     ET_File *ETFile = NULL;
79     File_Name *FileName;
80     gchar *list_text = NULL;
81     gint rowcount;
82     gboolean found;
83 
84     GtkTreePath *currentPath = NULL;
85     GtkTreeIter iter_name;
86     GtkTreeIter iter_content;
87 
88     priv = et_load_files_dialog_get_instance_private (self);
89 
90     if ( !ETCore->ETFileList || !priv->file_content_view || !priv->file_name_view)
91         return;
92 
93     et_application_window_update_et_file_from_ui (ET_APPLICATION_WINDOW (MainWindow));
94 
95     rowcount = MIN(gtk_tree_model_iter_n_children(GTK_TREE_MODEL(priv->file_name_model), NULL),
96                    gtk_tree_model_iter_n_children(GTK_TREE_MODEL(priv->file_content_model), NULL));
97 
98     for (row=0; row < rowcount; row++)
99     {
100         if (row == 0)
101             currentPath = gtk_tree_path_new_first();
102         else
103             gtk_tree_path_next(currentPath);
104 
105         found = gtk_tree_model_get_iter(GTK_TREE_MODEL(priv->file_name_model), &iter_name, currentPath);
106         if (found)
107             gtk_tree_model_get(GTK_TREE_MODEL(priv->file_name_model), &iter_name,
108                                LOAD_FILE_NAME_POINTER, &ETFile, -1);
109 
110         found = gtk_tree_model_get_iter(GTK_TREE_MODEL(priv->file_content_model), &iter_content, currentPath);
111         if (found)
112             gtk_tree_model_get(GTK_TREE_MODEL(priv->file_content_model), &iter_content,
113                                LOAD_FILE_CONTENT_TEXT, &list_text, -1);
114 
115         if (ETFile && !et_str_empty (list_text))
116         {
117             gchar *list_text_tmp;
118             gchar *filename_new_utf8;
119 
120             list_text_tmp = g_strdup(list_text);
121             et_filename_prepare (list_text_tmp,
122                                  g_settings_get_boolean (MainSettings,
123                                                          "rename-replace-illegal-chars"));
124 
125             /* Build the filename with the path */
126             filename_new_utf8 = et_file_generate_name (ETFile, list_text_tmp);
127             g_free(list_text_tmp);
128 
129             /* Set the new filename */
130             // Create a new 'File_Name' item
131             FileName = et_file_name_new ();
132             // Save changes of the 'File_Name' item
133             ET_Set_Filename_File_Name_Item(FileName,filename_new_utf8,NULL);
134             ET_Manage_Changes_Of_File_Data(ETFile,FileName,NULL);
135 
136             g_free(filename_new_utf8);
137 
138             /* Then run current scanner if requested. */
139             if (g_settings_get_boolean (MainSettings,
140                                         "load-filenames-run-scanner"))
141             {
142                 EtScanDialog *dialog;
143 
144                 dialog = ET_SCAN_DIALOG (et_application_window_get_scan_dialog (ET_APPLICATION_WINDOW (MainWindow)));
145 
146                 if (dialog)
147                 {
148                     Scan_Select_Mode_And_Run_Scanner (dialog, ETFile);
149                 }
150             }
151         }
152         g_free(list_text);
153     }
154 
155     gtk_tree_path_free(currentPath);
156 
157     et_application_window_browser_refresh_list (ET_APPLICATION_WINDOW (MainWindow));
158     et_application_window_display_et_file (ET_APPLICATION_WINDOW (MainWindow),
159                                            ETCore->ETFileDisplayed);
160 }
161 
162 /*
163  * on_response:
164  * @dialog: the dialog which emitted the response signal
165  * @response_id: the response ID
166  * @user_data: user data set when the signal was connected
167  *
168  * Signal handler for the load filenames from text file dialog.
169  */
170 static void
on_response(GtkDialog * dialog,gint response_id,gpointer user_data)171 on_response (GtkDialog *dialog, gint response_id, gpointer user_data)
172 {
173     switch (response_id)
174     {
175         case GTK_RESPONSE_APPLY:
176             Load_Filename_Set_Filenames (ET_LOAD_FILES_DIALOG (dialog));
177             break;
178         case GTK_RESPONSE_CANCEL:
179             gtk_widget_hide (GTK_WIDGET (dialog));
180             break;
181         case GTK_RESPONSE_DELETE_EVENT:
182             break;
183         default:
184             g_assert_not_reached ();
185     }
186 }
187 
188 /*
189  * To enable/disable sensitivity of the button 'Load'
190  */
191 static void
set_load_button_sensitivity(EtLoadFilesDialog * self,GtkWidget * chooser)192 set_load_button_sensitivity (EtLoadFilesDialog *self,
193                              GtkWidget *chooser)
194 {
195     EtLoadFilesDialogPrivate *priv;
196     GFile *file;
197     GFileInfo *info;
198     GError *error = NULL;
199 
200     g_return_if_fail (self != NULL && chooser != NULL);
201 
202     priv = et_load_files_dialog_get_instance_private (self);
203 
204     file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (chooser));
205 
206     if (!file)
207     {
208         gtk_widget_set_sensitive (priv->file_load_button, FALSE);
209         gtk_widget_set_sensitive (priv->content_reload, FALSE);
210         gtk_widget_set_sensitive (priv->content_reload_menuitem, FALSE);
211         return;
212     }
213 
214     info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_TYPE,
215                               G_FILE_QUERY_INFO_NONE, NULL, &error);
216     g_object_unref (file);
217 
218     if (info && G_FILE_TYPE_REGULAR == g_file_info_get_file_type (info))
219     {
220         gtk_widget_set_sensitive (GTK_WIDGET (priv->file_load_button), TRUE);
221         gtk_widget_set_sensitive (priv->content_reload, TRUE);
222         gtk_widget_set_sensitive (priv->content_reload_menuitem, TRUE);
223     }
224     else
225     {
226         gtk_widget_set_sensitive (GTK_WIDGET (priv->file_load_button), FALSE);
227         gtk_widget_set_sensitive (priv->content_reload, FALSE);
228         gtk_widget_set_sensitive (priv->content_reload_menuitem, FALSE);
229 
230         if (!info)
231         {
232             Log_Print (LOG_ERROR, _("Cannot query file information ‘%s’"),
233                        error->message);
234             g_error_free (error);
235             return;
236         }
237     }
238 
239     g_object_unref (info);
240 }
241 
242 /*
243  * Load content of the file into the priv->file_content_view list
244  */
245 static void
Load_File_Content(G_GNUC_UNUSED GtkButton * button,gpointer user_data)246 Load_File_Content (G_GNUC_UNUSED GtkButton *button, gpointer user_data)
247 {
248     EtLoadFilesDialogPrivate *priv;
249     GFile *file;
250     GFileInputStream *istream;
251     GDataInputStream *data;
252     GError *error = NULL;
253     gsize size_read;
254     gchar *path;
255     gchar *display_path;
256     gchar *line;
257     gchar *valid;
258 
259     priv = et_load_files_dialog_get_instance_private (ET_LOAD_FILES_DIALOG (user_data));
260     file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (priv->file_chooser));
261 
262     /* The file to read. */
263     path = g_file_get_path (file);
264     display_path = g_filename_display_name (path);
265     g_free (path);
266 
267     istream = g_file_read (file, NULL, &error);
268     g_object_unref (file);
269 
270     if (!istream)
271     {
272         Log_Print (LOG_ERROR, _("Cannot open file ‘%s’: %s"), display_path,
273                    error->message);
274         g_error_free (error);
275         g_object_unref (file);
276         g_free (display_path);
277         return;
278     }
279 
280     g_free (display_path);
281     data = g_data_input_stream_new (G_INPUT_STREAM (istream));
282     /* TODO: Find a safer alternative to _ANY. */
283     g_data_input_stream_set_newline_type (data,
284                                           G_DATA_STREAM_NEWLINE_TYPE_ANY);
285     gtk_list_store_clear (priv->file_content_model);
286 
287     while ((line = g_data_input_stream_read_line (data, &size_read, NULL,
288                                                   &error)))
289     {
290         /* FIXME: This should use the GLib filename encoding, not UTF-8. */
291         valid = Try_To_Validate_Utf8_String (line);
292         g_free (line);
293 
294         gtk_list_store_insert_with_values (priv->file_content_model, NULL,
295                                            G_MAXINT, LOAD_FILE_CONTENT_TEXT,
296                                            valid, -1);
297         g_free (valid);
298     }
299 
300     if (error)
301     {
302         Log_Print (LOG_ERROR, _("Error reading file ‘%s’"), error->message);
303         g_error_free (error);
304     }
305 
306     g_object_unref (data);
307     g_object_unref (istream);
308 }
309 
310 /*
311  * Delete the selected line in the treeview passed as parameter
312  */
313 static void
Load_Filename_List_Delete_Line(GtkWidget * treeview)314 Load_Filename_List_Delete_Line (GtkWidget *treeview)
315 {
316     GtkTreeSelection *selection;
317     GtkTreeIter selectedIter, itercopy;
318     GtkTreeModel *model;
319     gboolean rowafter;
320 
321     g_return_if_fail (treeview != NULL);
322 
323     selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
324 
325     if (gtk_tree_selection_get_selected(selection, &model, &selectedIter) != TRUE)
326     {
327         return;
328     }
329 
330     // If there is a line following the one about to be removed, select it for convenience
331     itercopy = selectedIter;
332     rowafter = gtk_tree_model_iter_next(model, &itercopy);
333 
334     // Remove the line to be deleted
335     gtk_list_store_remove(GTK_LIST_STORE(model), &selectedIter);
336 
337     if (rowafter)
338         gtk_tree_selection_select_iter(selection, &itercopy);
339 }
340 
341 static void
on_name_remove_clicked(EtLoadFilesDialog * self,GtkToolButton * button)342 on_name_remove_clicked (EtLoadFilesDialog *self,
343                         GtkToolButton *button)
344 {
345     EtLoadFilesDialogPrivate *priv;
346 
347     priv = et_load_files_dialog_get_instance_private (self);
348     Load_Filename_List_Delete_Line (priv->file_name_view);
349 }
350 
351 static void
on_content_remove_clicked(EtLoadFilesDialog * self,GtkToolButton * button)352 on_content_remove_clicked (EtLoadFilesDialog *self,
353                            GtkToolButton *button)
354 {
355     EtLoadFilesDialogPrivate *priv;
356 
357     priv = et_load_files_dialog_get_instance_private (self);
358     Load_Filename_List_Delete_Line (priv->file_content_view);
359 }
360 
361 /*
362  * Insert a blank line before the selected line in the treeview passed as parameter
363  */
364 static void
Load_Filename_List_Insert_Blank_Line(GtkWidget * treeview)365 Load_Filename_List_Insert_Blank_Line (GtkWidget *treeview)
366 {
367     GtkTreeSelection *selection;
368     GtkTreeIter selectedIter;
369     GtkTreeIter *temp;
370     GtkTreeModel *model;
371 
372     g_return_if_fail (treeview != NULL);
373 
374     selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
375 
376     if (gtk_tree_selection_get_selected(selection, &model, &selectedIter) != TRUE)
377         return;
378 
379     temp = &selectedIter; /* Not used here, but it must be non-NULL to keep GTK+ happy! */
380     gtk_list_store_insert_before(GTK_LIST_STORE(model), temp, &selectedIter);
381 }
382 
383 static void
on_name_insert_blank_clicked(EtLoadFilesDialog * self,GtkToolButton * button)384 on_name_insert_blank_clicked (EtLoadFilesDialog *self,
385                               GtkToolButton *button)
386 {
387     EtLoadFilesDialogPrivate *priv;
388 
389     priv = et_load_files_dialog_get_instance_private (self);
390     Load_Filename_List_Insert_Blank_Line (priv->file_name_view);
391 }
392 
393 static void
on_content_insert_blank_clicked(EtLoadFilesDialog * self,GtkToolButton * button)394 on_content_insert_blank_clicked (EtLoadFilesDialog *self,
395                                  GtkToolButton *button)
396 {
397     EtLoadFilesDialogPrivate *priv;
398 
399     priv = et_load_files_dialog_get_instance_private (self);
400     Load_Filename_List_Insert_Blank_Line (priv->file_content_view);
401 }
402 
403 static gboolean
Load_Filename_List_Key_Press(GtkWidget * treeview,GdkEvent * event)404 Load_Filename_List_Key_Press (GtkWidget *treeview, GdkEvent *event)
405 {
406     if (event && event->type == GDK_KEY_PRESS)
407     {
408         GdkEventKey *kevent = (GdkEventKey *)event;
409 
410         switch(kevent->keyval)
411         {
412             case GDK_KEY_Delete:
413                 Load_Filename_List_Delete_Line(treeview);
414                 return GDK_EVENT_STOP;
415                 break;
416             case GDK_KEY_I:
417             case GDK_KEY_i:
418                 Load_Filename_List_Insert_Blank_Line(treeview);
419                 return GDK_EVENT_STOP;
420                 break;
421             default:
422                 /* Ignore all other keypresses. */
423                 break;
424         }
425     }
426 
427     return GDK_EVENT_PROPAGATE;
428 }
429 
430 /*
431  * Delete all blank lines in the treeview passed as parameter
432  */
433 static void
Load_Filename_List_Delete_All_Blank_Lines(GtkWidget * treeview)434 Load_Filename_List_Delete_All_Blank_Lines (GtkWidget *treeview)
435 {
436     gchar *text = NULL;
437     GtkTreeIter iter;
438     GtkTreeModel *model;
439 
440     model = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
441 
442     if (!gtk_tree_model_get_iter_first(model, &iter))
443         return;
444 
445     while (TRUE)
446     {
447         gtk_tree_model_get(model, &iter, LOAD_FILE_NAME_TEXT, &text, -1);
448 
449         /* Check for blank entry */
450         if (et_str_empty (text))
451         {
452             g_free(text);
453 
454             if (!gtk_list_store_remove(GTK_LIST_STORE(model), &iter))
455                 break;
456             else
457                 continue;
458         }
459         g_free(text);
460 
461         if (!gtk_tree_model_iter_next(model, &iter))
462             break;
463     }
464 }
465 
466 static void
on_content_delete_all_blank_clicked(EtLoadFilesDialog * self,GtkToolButton * button)467 on_content_delete_all_blank_clicked (EtLoadFilesDialog *self,
468                                      GtkToolButton *button)
469 {
470     EtLoadFilesDialogPrivate *priv;
471 
472     priv = et_load_files_dialog_get_instance_private (self);
473     Load_Filename_List_Delete_All_Blank_Lines (priv->file_content_view);
474 }
475 
476 static void
on_name_delete_all_blank_clicked(EtLoadFilesDialog * self,GtkToolButton * button)477 on_name_delete_all_blank_clicked (EtLoadFilesDialog *self,
478                                   GtkToolButton *button)
479 {
480     EtLoadFilesDialogPrivate *priv;
481 
482     priv = et_load_files_dialog_get_instance_private (self);
483     Load_Filename_List_Delete_All_Blank_Lines (priv->file_name_view);
484 }
485 
486 /*
487  * Move up the selected line in the treeview passed as parameter
488  */
489 static void
Load_Filename_List_Move_Up(GtkWidget * treeview)490 Load_Filename_List_Move_Up (GtkWidget *treeview)
491 {
492     GtkTreeSelection *selection;
493     GList *selectedRows;
494     GList *l;
495     GtkTreeIter currentFile;
496     GtkTreeIter nextFile;
497     GtkTreePath *currentPath;
498     GtkTreeModel *treemodel;
499 
500     g_return_if_fail (treeview != NULL);
501 
502     selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
503     treemodel = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
504     selectedRows = gtk_tree_selection_get_selected_rows(selection, NULL);
505 
506     if (!selectedRows)
507     {
508         return;
509     }
510 
511     for (l = selectedRows; l != NULL; l = g_list_next (l))
512     {
513         currentPath = (GtkTreePath *)l->data;
514 
515         if (gtk_tree_model_get_iter (treemodel, &currentFile, currentPath))
516         {
517             // Find the entry above the node...
518             if (gtk_tree_path_prev(currentPath))
519             {
520                 // ...and if it exists, swap the two rows by iter
521                 gtk_tree_model_get_iter(treemodel, &nextFile, currentPath);
522                 gtk_list_store_swap(GTK_LIST_STORE(treemodel), &currentFile, &nextFile);
523             }
524         }
525     }
526 
527     g_list_free_full (selectedRows, (GDestroyNotify)gtk_tree_path_free);
528 }
529 
530 static void
on_content_move_up_clicked(EtLoadFilesDialog * self,GtkToolButton * button)531 on_content_move_up_clicked (EtLoadFilesDialog *self,
532                             GtkToolButton *button)
533 {
534     EtLoadFilesDialogPrivate *priv;
535 
536     priv = et_load_files_dialog_get_instance_private (self);
537     Load_Filename_List_Move_Up (priv->file_content_view);
538 }
539 
540 static void
on_name_move_up_clicked(EtLoadFilesDialog * self,GtkToolButton * button)541 on_name_move_up_clicked (EtLoadFilesDialog *self,
542                          GtkToolButton *button)
543 {
544     EtLoadFilesDialogPrivate *priv;
545 
546     priv = et_load_files_dialog_get_instance_private (self);
547     Load_Filename_List_Move_Up (priv->file_name_view);
548 }
549 
550 /*
551  * Move down the selected line in the treeview passed as parameter
552  */
553 static void
Load_Filename_List_Move_Down(GtkWidget * treeview)554 Load_Filename_List_Move_Down (GtkWidget *treeview)
555 {
556     GtkTreeSelection *selection;
557     GList *selectedRows;
558     GList *l;
559     GtkTreeIter currentFile;
560     GtkTreeIter nextFile;
561     GtkTreePath *currentPath;
562     GtkTreeModel *treemodel;
563 
564     g_return_if_fail (treeview != NULL);
565 
566     selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview));
567     treemodel = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview));
568     selectedRows = gtk_tree_selection_get_selected_rows(selection, NULL);
569 
570     if (!selectedRows)
571     {
572         return;
573     }
574 
575     for (l = selectedRows; l != NULL; l = g_list_next (l))
576     {
577         currentPath = (GtkTreePath *)l->data;
578 
579         if (gtk_tree_model_get_iter (treemodel, &currentFile, currentPath))
580         {
581             // Find the entry below the node and swap the two nodes by iter
582             gtk_tree_path_next(currentPath);
583             if (gtk_tree_model_get_iter(treemodel, &nextFile, currentPath))
584                 gtk_list_store_swap(GTK_LIST_STORE(treemodel), &currentFile, &nextFile);
585         }
586     }
587 
588     g_list_free_full (selectedRows, (GDestroyNotify)gtk_tree_path_free);
589 }
590 
591 static void
on_content_move_down_clicked(EtLoadFilesDialog * self,GtkToolButton * button)592 on_content_move_down_clicked (EtLoadFilesDialog *self,
593                               GtkToolButton *button)
594 {
595     EtLoadFilesDialogPrivate *priv;
596 
597     priv = et_load_files_dialog_get_instance_private (self);
598     Load_Filename_List_Move_Down (priv->file_content_view);
599 }
600 
601 static void
on_name_move_down_clicked(EtLoadFilesDialog * self,GtkToolButton * button)602 on_name_move_down_clicked (EtLoadFilesDialog *self,
603                            GtkToolButton *button)
604 {
605     EtLoadFilesDialogPrivate *priv;
606 
607     priv = et_load_files_dialog_get_instance_private (self);
608     Load_Filename_List_Move_Down (priv->file_name_view);
609 }
610 
611 /*
612  * Load the names of the current list of files
613  */
614 static void
Load_File_List(EtLoadFilesDialog * self)615 Load_File_List (EtLoadFilesDialog *self)
616 {
617     EtLoadFilesDialogPrivate *priv;
618     GList *l;
619     ET_File *etfile;
620     gchar *filename_utf8;
621     gchar *pos;
622 
623     priv = et_load_files_dialog_get_instance_private (self);
624 
625     gtk_list_store_clear(priv->file_name_model);
626 
627     for (l = ETCore->ETFileList; l != NULL; l = g_list_next (l))
628     {
629         etfile = (ET_File *)l->data;
630         filename_utf8 = g_path_get_basename(((File_Name *)etfile->FileNameNew->data)->value_utf8);
631         // Remove the extension ('filename' must be allocated to don't affect the initial value)
632         if ((pos=strrchr(filename_utf8,'.'))!=NULL)
633             *pos = 0;
634         gtk_list_store_insert_with_values (priv->file_name_model, NULL,
635                                            G_MAXINT, LOAD_FILE_NAME_TEXT,
636                                            filename_utf8,
637                                            LOAD_FILE_NAME_POINTER, l->data,
638                                            -1);
639         g_free(filename_utf8);
640     }
641 }
642 
643 static void
on_load_file_name_view_reload_clicked(EtLoadFilesDialog * self,G_GNUC_UNUSED GtkButton * button)644 on_load_file_name_view_reload_clicked (EtLoadFilesDialog *self,
645                                        G_GNUC_UNUSED GtkButton *button)
646 {
647     Load_File_List (self);
648 }
649 
650 static void
on_load_file_content_view_reload_clicked(EtLoadFilesDialog * self,G_GNUC_UNUSED GtkButton * button)651 on_load_file_content_view_reload_clicked (EtLoadFilesDialog *self,
652                                           G_GNUC_UNUSED GtkButton *button)
653 {
654     Load_File_Content (NULL, self);
655 }
656 
657 /*
658  * To select the corresponding row in the other list
659  */
660 static void
Load_Filename_Select_Row_In_Other_List(GtkWidget * treeview_target,GtkTreeSelection * selection_orig)661 Load_Filename_Select_Row_In_Other_List (GtkWidget* treeview_target,
662                                         GtkTreeSelection *selection_orig)
663 {
664     GtkAdjustment *ct_adj, *ce_adj;
665     GtkTreeSelection *selection_target;
666     GtkTreeView *treeview_orig;
667     GtkTreeModel *treemodel_orig;
668     GtkTreeModel *treemodel_target;
669     GtkTreeIter iter_orig;
670     GtkTreeIter iter_target;
671     GtkTreePath *path_orig;
672     gint *indices_orig;
673     gchar *stringiter;
674 
675     g_return_if_fail (treeview_target != NULL && selection_orig != NULL);
676 
677     selection_target = gtk_tree_view_get_selection(GTK_TREE_VIEW(treeview_target));
678     treemodel_target = gtk_tree_view_get_model(GTK_TREE_VIEW(treeview_target));
679 
680     if (!gtk_tree_selection_get_selected(selection_orig, &treemodel_orig, &iter_orig))
681         return; /* Might be called with no selection */
682 
683     treeview_orig = gtk_tree_selection_get_tree_view(selection_orig);
684     path_orig = gtk_tree_model_get_path(treemodel_orig, &iter_orig);
685     gtk_tree_selection_unselect_all(selection_target);
686 
687     /* Synchronize the two lists. */
688     ce_adj = gtk_scrollable_get_vadjustment (GTK_SCROLLABLE (treeview_orig));
689     ct_adj = gtk_scrollable_get_vadjustment(GTK_SCROLLABLE(treeview_target));
690 
691     if (gtk_adjustment_get_upper(GTK_ADJUSTMENT(ct_adj)) >= gtk_adjustment_get_page_size(GTK_ADJUSTMENT(ct_adj))
692     &&  gtk_adjustment_get_upper(GTK_ADJUSTMENT(ce_adj)) >= gtk_adjustment_get_page_size(GTK_ADJUSTMENT(ce_adj)))
693     {
694         // Rules are displayed in the both clist
695         if (gtk_adjustment_get_value(GTK_ADJUSTMENT(ce_adj)) <= gtk_adjustment_get_upper(GTK_ADJUSTMENT(ct_adj)) - gtk_adjustment_get_page_size(GTK_ADJUSTMENT(ct_adj)))
696         {
697             gtk_adjustment_set_value(GTK_ADJUSTMENT(ct_adj),gtk_adjustment_get_value(GTK_ADJUSTMENT(ce_adj)));
698         } else
699         {
700             gtk_adjustment_set_value(GTK_ADJUSTMENT(ct_adj),gtk_adjustment_get_upper(GTK_ADJUSTMENT(ct_adj)) - gtk_adjustment_get_page_size(GTK_ADJUSTMENT(ct_adj)));
701             indices_orig = gtk_tree_path_get_indices(path_orig);
702 
703             if (indices_orig[0] <= (gtk_tree_model_iter_n_children(treemodel_target, NULL) - 1))
704                 gtk_adjustment_set_value(GTK_ADJUSTMENT(ce_adj),gtk_adjustment_get_value(GTK_ADJUSTMENT(ct_adj)));
705 
706         }
707     }else if (gtk_adjustment_get_upper(GTK_ADJUSTMENT(ct_adj)) < gtk_adjustment_get_page_size(GTK_ADJUSTMENT(ct_adj))) // Target Clist rule not visible
708     {
709         indices_orig = gtk_tree_path_get_indices(path_orig);
710 
711         if (indices_orig[0] <= (gtk_tree_model_iter_n_children(treemodel_target, NULL) - 1))
712             gtk_adjustment_set_value(GTK_ADJUSTMENT(ce_adj),gtk_adjustment_get_value(GTK_ADJUSTMENT(ct_adj)));
713     }
714 
715     // Must block the select signal of the target to avoid looping
716     g_signal_handlers_block_by_func(G_OBJECT(selection_target), G_CALLBACK(Load_Filename_Select_Row_In_Other_List), G_OBJECT(treeview_orig));
717 
718     stringiter = gtk_tree_model_get_string_from_iter(treemodel_orig, &iter_orig);
719     if (gtk_tree_model_get_iter_from_string(treemodel_target, &iter_target, stringiter))
720     {
721         gtk_tree_selection_select_iter(selection_target, &iter_target);
722     }
723 
724     g_free(stringiter);
725     g_signal_handlers_unblock_by_func(G_OBJECT(selection_target), G_CALLBACK(Load_Filename_Select_Row_In_Other_List), G_OBJECT(treeview_orig));
726 }
727 
728 static void
create_load_file_content_view_popup(EtLoadFilesDialog * self)729 create_load_file_content_view_popup (EtLoadFilesDialog *self)
730 {
731     EtLoadFilesDialogPrivate *priv;
732     GtkWidget *list;
733     GtkWidget *BrowserPopupMenu;
734     GtkWidget *MenuItem;
735 
736     priv = et_load_files_dialog_get_instance_private (self);
737     list = priv->file_name_view;
738 
739     BrowserPopupMenu = gtk_menu_new();
740     gtk_menu_attach_to_widget (GTK_MENU (BrowserPopupMenu), list, NULL);
741 
742     MenuItem = gtk_menu_item_new_with_label(_("Insert a blank line"));
743     gtk_menu_shell_append(GTK_MENU_SHELL(BrowserPopupMenu), MenuItem);
744     g_signal_connect_swapped(G_OBJECT(MenuItem),"activate", G_CALLBACK(Load_Filename_List_Insert_Blank_Line), G_OBJECT(list));
745 
746     MenuItem = gtk_menu_item_new_with_label(_("Delete this line"));
747     gtk_menu_shell_append(GTK_MENU_SHELL(BrowserPopupMenu),MenuItem);
748     g_signal_connect_swapped(G_OBJECT(MenuItem),"activate", G_CALLBACK(Load_Filename_List_Delete_Line), G_OBJECT(list));
749 
750     MenuItem = gtk_menu_item_new_with_label(_("Delete all blank lines"));
751     gtk_menu_shell_append(GTK_MENU_SHELL(BrowserPopupMenu),MenuItem);
752     g_signal_connect_swapped(G_OBJECT(MenuItem),"activate", G_CALLBACK(Load_Filename_List_Delete_All_Blank_Lines),G_OBJECT(list));
753 
754     MenuItem = gtk_menu_item_new();
755     gtk_menu_shell_append(GTK_MENU_SHELL(BrowserPopupMenu),MenuItem);
756 
757     MenuItem = gtk_menu_item_new_with_label (_("Move this line up"));
758     gtk_menu_shell_append(GTK_MENU_SHELL(BrowserPopupMenu),MenuItem);
759     g_signal_connect_swapped(G_OBJECT(MenuItem),"activate", G_CALLBACK(Load_Filename_List_Move_Up),G_OBJECT(list));
760 
761     MenuItem = gtk_menu_item_new_with_label (_("Move this line down"));
762     gtk_menu_shell_append(GTK_MENU_SHELL(BrowserPopupMenu),MenuItem);
763     g_signal_connect_swapped(G_OBJECT(MenuItem),"activate", G_CALLBACK(Load_Filename_List_Move_Down),G_OBJECT(list));
764 
765     MenuItem = gtk_menu_item_new();
766     gtk_menu_shell_append(GTK_MENU_SHELL(BrowserPopupMenu),MenuItem);
767 
768     MenuItem = gtk_menu_item_new_with_label(_("Reload"));
769     gtk_menu_shell_append(GTK_MENU_SHELL(BrowserPopupMenu),MenuItem);
770     g_signal_connect_swapped (MenuItem, "activate",
771                               G_CALLBACK (on_load_file_name_view_reload_clicked),
772                               self);
773 
774     gtk_widget_show_all(BrowserPopupMenu);
775 
776     priv->load_file_content_menu = BrowserPopupMenu;
777 }
778 
779 static void
do_name_view_popup_menu(EtLoadFilesDialog * self,GdkEventButton * event)780 do_name_view_popup_menu (EtLoadFilesDialog *self,
781                          GdkEventButton *event)
782 {
783     EtLoadFilesDialogPrivate *priv;
784     gint button;
785     gint event_time;
786 
787     priv = et_load_files_dialog_get_instance_private (self);
788 
789     if (event)
790     {
791         button = event->button;
792         event_time = event->time;
793     }
794     else
795     {
796         button = 0;
797         event_time = gtk_get_current_event_time ();
798     }
799 
800     /* TODO: Add popup positioning function. */
801     gtk_menu_popup (GTK_MENU (priv->load_file_content_menu), NULL, NULL, NULL,
802                     NULL, button, event_time);
803 }
804 
805 static gboolean
on_name_view_popup_menu(GtkWidget * treeview,EtLoadFilesDialog * self)806 on_name_view_popup_menu (GtkWidget *treeview,
807                          EtLoadFilesDialog *self)
808 {
809     do_name_view_popup_menu (self, NULL);
810 
811     return GDK_EVENT_STOP;
812 }
813 
814 /*
815  * Handle button press events from the file name view. */
816 static gboolean
on_name_view_button_press_event(GtkWidget * treeview,GdkEventButton * event,EtLoadFilesDialog * self)817 on_name_view_button_press_event (GtkWidget *treeview,
818                                  GdkEventButton *event,
819                                  EtLoadFilesDialog *self)
820 {
821     if (gdk_event_triggers_context_menu ((GdkEvent *)event))
822     {
823         do_name_view_popup_menu (self, event);
824 
825         return GDK_EVENT_STOP;
826     }
827 
828     return GDK_EVENT_PROPAGATE;
829 }
830 
831 static void
create_load_file_name_view_popup(EtLoadFilesDialog * self)832 create_load_file_name_view_popup (EtLoadFilesDialog *self)
833 {
834     EtLoadFilesDialogPrivate *priv;
835     GtkWidget *list;
836     GtkWidget *BrowserPopupMenu;
837     GtkWidget *MenuItem;
838 
839     priv = et_load_files_dialog_get_instance_private (self);
840     list = priv->file_content_view;
841 
842     BrowserPopupMenu = gtk_menu_new();
843     gtk_menu_attach_to_widget (GTK_MENU (BrowserPopupMenu), list, NULL);
844 
845     MenuItem = gtk_menu_item_new_with_label (_("Insert a blank line"));
846     gtk_menu_shell_append(GTK_MENU_SHELL(BrowserPopupMenu), MenuItem);
847     g_signal_connect_swapped(G_OBJECT(MenuItem),"activate", G_CALLBACK(Load_Filename_List_Insert_Blank_Line), G_OBJECT(list));
848 
849     MenuItem = gtk_menu_item_new_with_label (_("Delete this line"));
850     gtk_menu_shell_append(GTK_MENU_SHELL(BrowserPopupMenu),MenuItem);
851     g_signal_connect_swapped(G_OBJECT(MenuItem),"activate", G_CALLBACK(Load_Filename_List_Delete_Line), G_OBJECT(list));
852 
853     MenuItem = gtk_menu_item_new_with_label (_("Delete all blank lines"));
854     gtk_menu_shell_append(GTK_MENU_SHELL(BrowserPopupMenu),MenuItem);
855     g_signal_connect_swapped(G_OBJECT(MenuItem),"activate", G_CALLBACK(Load_Filename_List_Delete_All_Blank_Lines),G_OBJECT(list));
856 
857     MenuItem = gtk_menu_item_new();
858     gtk_menu_shell_append(GTK_MENU_SHELL(BrowserPopupMenu),MenuItem);
859 
860     MenuItem = gtk_menu_item_new_with_label (_("Move this line up"));
861     gtk_menu_shell_append(GTK_MENU_SHELL(BrowserPopupMenu),MenuItem);
862     g_signal_connect_swapped(G_OBJECT(MenuItem),"activate", G_CALLBACK(Load_Filename_List_Move_Up),G_OBJECT(list));
863 
864     MenuItem = gtk_menu_item_new_with_label (_("Move this line down"));
865     gtk_menu_shell_append(GTK_MENU_SHELL(BrowserPopupMenu),MenuItem);
866     g_signal_connect_swapped(G_OBJECT(MenuItem),"activate", G_CALLBACK(Load_Filename_List_Move_Down),G_OBJECT(list));
867 
868     MenuItem = gtk_menu_item_new();
869     gtk_menu_shell_append(GTK_MENU_SHELL(BrowserPopupMenu),MenuItem);
870 
871     MenuItem = gtk_menu_item_new_with_label (_("Reload"));
872     gtk_menu_shell_append(GTK_MENU_SHELL(BrowserPopupMenu),MenuItem);
873     g_signal_connect_swapped (MenuItem, "activate",
874                               G_CALLBACK (on_load_file_content_view_reload_clicked),
875                               self);
876 
877     gtk_widget_show_all(BrowserPopupMenu);
878 
879     priv->content_reload_menuitem = MenuItem;
880     priv->load_file_name_menu = BrowserPopupMenu;
881 }
882 
883 static void
do_content_view_popup_menu(EtLoadFilesDialog * self,GdkEventButton * event)884 do_content_view_popup_menu (EtLoadFilesDialog *self,
885                             GdkEventButton *event)
886 {
887     EtLoadFilesDialogPrivate *priv;
888     gint button;
889     gint event_time;
890 
891     priv = et_load_files_dialog_get_instance_private (self);
892 
893     if (event)
894     {
895         button = event->button;
896         event_time = event->time;
897     }
898     else
899     {
900         button = 0;
901         event_time = gtk_get_current_event_time ();
902     }
903 
904     /* TODO: Add popup positioning function. */
905     gtk_menu_popup (GTK_MENU (priv->load_file_name_menu), NULL, NULL, NULL,
906                     NULL, button, event_time);
907 }
908 
909 static gboolean
on_content_view_popup_menu(GtkWidget * treeview,EtLoadFilesDialog * self)910 on_content_view_popup_menu (GtkWidget *treeview,
911                             EtLoadFilesDialog *self)
912 {
913     do_content_view_popup_menu (self, NULL);
914 
915     return GDK_EVENT_STOP;
916 }
917 
918 /*
919  * Handle button press events from the file content view. */
920 static gboolean
on_content_view_button_press_event(GtkWidget * treeview,GdkEventButton * event,EtLoadFilesDialog * self)921 on_content_view_button_press_event (GtkWidget *treeview,
922                                     GdkEventButton *event,
923                                     EtLoadFilesDialog *self)
924 {
925     if (gdk_event_triggers_context_menu ((GdkEvent *)event))
926     {
927         do_content_view_popup_menu (self, event);
928 
929         return GDK_EVENT_STOP;
930     }
931 
932     return GDK_EVENT_PROPAGATE;
933 }
934 
935 /*
936  * Update the text of the selected line into the list, with the text entered into the entry
937  */
938 static void
Load_Filename_Update_Text_Line(EtLoadFilesDialog * self,GtkWidget * entry)939 Load_Filename_Update_Text_Line (EtLoadFilesDialog *self,
940                                 GtkWidget *entry)
941 {
942     EtLoadFilesDialogPrivate *priv;
943     GtkTreeIter SelectedRow;
944     GtkTreeSelection *selection;
945     GtkTreeModel *model;
946     gboolean hasSelectedRows;
947 
948     g_return_if_fail (self != NULL && entry != NULL);
949 
950     priv = et_load_files_dialog_get_instance_private (self);
951 
952     selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->file_content_view));
953     hasSelectedRows = gtk_tree_selection_get_selected(selection, &model, &SelectedRow);
954     if (hasSelectedRows)
955     {
956         const gchar *text = gtk_entry_get_text(GTK_ENTRY(entry));
957         gtk_list_store_set(GTK_LIST_STORE(model), &SelectedRow, LOAD_FILE_CONTENT_TEXT, text, -1);
958     }
959 }
960 
961 /*
962  * Set the text of the selected line of the list into the entry
963  */
964 static void
Load_Filename_Edit_Text_Line(EtLoadFilesDialog * self,GtkTreeSelection * selection)965 Load_Filename_Edit_Text_Line (EtLoadFilesDialog *self,
966                               GtkTreeSelection *selection)
967 {
968     EtLoadFilesDialogPrivate *priv;
969     gchar *text;
970     GtkTreeIter selectedIter;
971     GtkEntry *entry;
972     gulong handler;
973 
974     priv = et_load_files_dialog_get_instance_private (self);
975 
976     entry = GTK_ENTRY (priv->file_entry);
977 
978     if (gtk_tree_selection_get_selected(selection, NULL, &selectedIter) != TRUE)
979         return;
980 
981     gtk_tree_model_get(GTK_TREE_MODEL(priv->file_content_model), &selectedIter, LOAD_FILE_NAME_TEXT, &text, -1);
982 
983     handler = g_signal_handler_find(entry, G_SIGNAL_MATCH_FUNC, 0, 0, NULL, Load_Filename_Update_Text_Line, NULL);
984     g_signal_handler_block(entry, handler);
985     if (text)
986     {
987         gtk_entry_set_text(entry, text);
988         g_free(text);
989     } else
990         gtk_entry_set_text(entry, "");
991 
992     g_signal_handler_unblock(entry, handler);
993 }
994 
995 /*
996  * The window to load the filenames from a txt.
997  */
998 static void
create_load_files_dialog(EtLoadFilesDialog * self)999 create_load_files_dialog (EtLoadFilesDialog *self)
1000 {
1001     EtLoadFilesDialogPrivate *priv;
1002 
1003     priv = et_load_files_dialog_get_instance_private (self);
1004 
1005     gtk_dialog_add_buttons (GTK_DIALOG (self), _("_Close"),
1006                             GTK_RESPONSE_CANCEL, _("_Apply"),
1007                             GTK_RESPONSE_APPLY, NULL);
1008     gtk_dialog_set_default_response (GTK_DIALOG (self), GTK_RESPONSE_APPLY);
1009 
1010     /* Initial value. */
1011     gtk_file_chooser_set_current_folder_file (GTK_FILE_CHOOSER (priv->file_chooser),
1012                                               et_application_window_get_current_path (ET_APPLICATION_WINDOW (MainWindow)),
1013                                               NULL);
1014 
1015     /* Signals to 'select' the same row into the other list (to show the
1016      * corresponding filenames). */
1017     g_signal_connect_swapped (gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->file_content_view)),
1018                               "changed",
1019                               G_CALLBACK (Load_Filename_Select_Row_In_Other_List),
1020                               priv->file_name_view);
1021     g_signal_connect_swapped (gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->file_name_view)),
1022                               "changed",
1023                               G_CALLBACK (Load_Filename_Select_Row_In_Other_List),
1024                               priv->file_content_view);
1025 
1026     /* Load the list of files in the list widget. */
1027     Load_File_List (self);
1028 
1029     /* Create popup menus. */
1030     create_load_file_content_view_popup (self);
1031     create_load_file_name_view_popup (self);
1032 
1033     g_settings_bind (MainSettings, "load-filenames-run-scanner",
1034                      priv->file_scanner_check, "active",
1035                      G_SETTINGS_BIND_DEFAULT);
1036 
1037     /* To initialize load button sensitivity. */
1038     g_signal_emit_by_name (G_OBJECT (priv->file_chooser), "file-set");
1039 }
1040 
1041 static void
et_load_files_dialog_init(EtLoadFilesDialog * self)1042 et_load_files_dialog_init (EtLoadFilesDialog *self)
1043 {
1044     gtk_widget_init_template (GTK_WIDGET (self));
1045     create_load_files_dialog (self);
1046 }
1047 
1048 static void
et_load_files_dialog_class_init(EtLoadFilesDialogClass * klass)1049 et_load_files_dialog_class_init (EtLoadFilesDialogClass *klass)
1050 {
1051     GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
1052 
1053     gtk_widget_class_set_template_from_resource (widget_class,
1054                                                  "/org/gnome/EasyTAG/load_files_dialog.ui");
1055     gtk_widget_class_bind_template_child_private (widget_class,
1056                                                   EtLoadFilesDialog,
1057                                                   file_chooser);
1058     gtk_widget_class_bind_template_child_private (widget_class,
1059                                                   EtLoadFilesDialog,
1060                                                   file_load_button);
1061     gtk_widget_class_bind_template_child_private (widget_class,
1062                                                   EtLoadFilesDialog,
1063                                                   file_content_view);
1064     gtk_widget_class_bind_template_child_private (widget_class,
1065                                                   EtLoadFilesDialog,
1066                                                   content_reload);
1067     gtk_widget_class_bind_template_child_private (widget_class,
1068                                                   EtLoadFilesDialog,
1069                                                   file_content_model);
1070     gtk_widget_class_bind_template_child_private (widget_class,
1071                                                   EtLoadFilesDialog,
1072                                                   file_name_view);
1073     gtk_widget_class_bind_template_child_private (widget_class,
1074                                                   EtLoadFilesDialog,
1075                                                   file_name_model);
1076     gtk_widget_class_bind_template_child_private (widget_class,
1077                                                   EtLoadFilesDialog,
1078                                                   file_scanner_check);
1079     gtk_widget_class_bind_template_child_private (widget_class,
1080                                                   EtLoadFilesDialog,
1081                                                   file_entry);
1082     gtk_widget_class_bind_template_callback (widget_class,
1083                                              on_content_view_button_press_event);
1084     gtk_widget_class_bind_template_callback (widget_class,
1085                                              on_content_view_popup_menu);
1086     gtk_widget_class_bind_template_callback (widget_class,
1087                                              on_load_file_content_view_reload_clicked);
1088     gtk_widget_class_bind_template_callback (widget_class,
1089                                              on_load_file_name_view_reload_clicked);
1090     gtk_widget_class_bind_template_callback (widget_class,
1091                                              on_name_view_button_press_event);
1092     gtk_widget_class_bind_template_callback (widget_class,
1093                                              on_name_view_popup_menu);
1094     gtk_widget_class_bind_template_callback (widget_class, on_response);
1095     gtk_widget_class_bind_template_callback (widget_class,
1096                                              set_load_button_sensitivity);
1097     gtk_widget_class_bind_template_callback (widget_class, Load_File_Content);
1098     gtk_widget_class_bind_template_callback (widget_class,
1099                                              Load_Filename_Edit_Text_Line);
1100     gtk_widget_class_bind_template_callback (widget_class,
1101                                              on_content_insert_blank_clicked);
1102     gtk_widget_class_bind_template_callback (widget_class,
1103                                              on_name_insert_blank_clicked);
1104     gtk_widget_class_bind_template_callback (widget_class,
1105                                              on_content_delete_all_blank_clicked);
1106     gtk_widget_class_bind_template_callback (widget_class,
1107                                              on_name_delete_all_blank_clicked);
1108     gtk_widget_class_bind_template_callback (widget_class,
1109                                              on_content_remove_clicked);
1110     gtk_widget_class_bind_template_callback (widget_class,
1111                                              on_name_remove_clicked);
1112     gtk_widget_class_bind_template_callback (widget_class,
1113                                              on_content_move_up_clicked);
1114     gtk_widget_class_bind_template_callback (widget_class,
1115                                              on_name_move_up_clicked);
1116     gtk_widget_class_bind_template_callback (widget_class,
1117                                              on_content_move_down_clicked);
1118     gtk_widget_class_bind_template_callback (widget_class,
1119                                              on_name_move_down_clicked);
1120     gtk_widget_class_bind_template_callback (widget_class,
1121                                              Load_Filename_List_Move_Down);
1122     gtk_widget_class_bind_template_callback (widget_class,
1123                                              Load_Filename_List_Key_Press);
1124     gtk_widget_class_bind_template_callback (widget_class,
1125                                              Load_Filename_Update_Text_Line);
1126 }
1127 
1128 /*
1129  * et_load_files_dialog_new:
1130  *
1131  * Create a new EtLoadFilesDialog instance.
1132  *
1133  * Returns: a new #EtLoadFilesDialog
1134  */
1135 EtLoadFilesDialog *
et_load_files_dialog_new(GtkWindow * parent)1136 et_load_files_dialog_new (GtkWindow *parent)
1137 {
1138     g_return_val_if_fail (GTK_WINDOW (parent), NULL);
1139 
1140     return g_object_new (ET_TYPE_LOAD_FILES_DIALOG, "transient-for", parent,
1141                          NULL);
1142 }
1143