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, ¤tFile, 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), ¤tFile, &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, ¤tFile, 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), ¤tFile, &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