1 /*
2  * Copyright (C) 2018-2021 Alexandros Theodotou <alex at zrythm dot org>
3  *
4  * This file is part of Zrythm
5  *
6  * Zrythm is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU Affero General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * Zrythm is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU Affero General Public License for more details.
15  *
16  * You should have received a copy of the GNU Affero General Public License
17  * along with Zrythm.  If not, see <https://www.gnu.org/licenses/>.
18  */
19 
20 #include "audio/engine.h"
21 #include "audio/exporter.h"
22 #include "audio/master_track.h"
23 #include "gui/widgets/dialogs/export_dialog.h"
24 #include "gui/widgets/dialogs/export_progress_dialog.h"
25 #include "gui/widgets/main_window.h"
26 #include "project.h"
27 #include "utils/arrays.h"
28 #include "utils/color.h"
29 #include "utils/datetime.h"
30 #include "utils/flags.h"
31 #include "utils/gtk.h"
32 #include "utils/io.h"
33 #include "utils/mem.h"
34 #include "utils/objects.h"
35 #include "utils/resources.h"
36 #include "utils/ui.h"
37 #include "settings/settings.h"
38 #include "zrythm_app.h"
39 
40 #include <glib/gi18n.h>
41 #include <gtk/gtk.h>
42 
43 G_DEFINE_TYPE (
44   ExportDialogWidget,
45   export_dialog_widget,
46   GTK_TYPE_DIALOG)
47 
48 enum
49 {
50   COLUMN_AUDIO_FORMAT_LABEL,
51   COLUMN_AUDIO_FORMAT,
52   NUM_AUDIO_FORMAT_COLUMNS
53 };
54 
55 enum
56 {
57   FILENAME_PATTERN_MIXDOWN_FORMAT,
58   FILENAME_PATTERN_DATE_MIXDOWN_FORMAT,
59   NUM_FILENAME_PATTERNS,
60 };
61 
62 enum
63 {
64   TRACK_COLUMN_CHECKBOX,
65   TRACK_COLUMN_ICON,
66   TRACK_COLUMN_BG_RGBA,
67   TRACK_COLUMN_DUMMY_TEXT,
68   TRACK_COLUMN_NAME,
69   TRACK_COLUMN_TRACK,
70   NUM_TRACK_COLUMNS,
71 };
72 
73 static const char * dummy_text = "";
74 
75 static void
add_enabled_recursively(ExportDialogWidget * self,GtkTreeIter * iter,Track *** tracks,size_t * size,int * count)76 add_enabled_recursively (
77   ExportDialogWidget * self,
78   GtkTreeIter *        iter,
79   Track ***            tracks,
80   size_t *             size,
81   int *                count)
82 {
83   GtkTreeModel * model =
84     GTK_TREE_MODEL (self->tracks_store);
85 
86   Track * track;
87   gboolean checked;
88   gtk_tree_model_get (
89     model, iter,
90     TRACK_COLUMN_CHECKBOX, &checked,
91     TRACK_COLUMN_TRACK, &track,
92     -1);
93   if (checked)
94     {
95       array_double_size_if_full (
96         *tracks, *count, *size, Track *);
97       array_append (*tracks, *count, track);
98       g_debug ("added %s", track->name);
99     }
100 
101   /* if group track, also check children
102    * recursively */
103   bool has_children =
104     gtk_tree_model_iter_has_child (model, iter);
105   if (has_children)
106     {
107       int num_children =
108         gtk_tree_model_iter_n_children (
109           model, iter);
110 
111       for (int i = 0; i < num_children; i++)
112         {
113           GtkTreeIter child_iter;
114           gtk_tree_model_iter_nth_child (
115             model, &child_iter, iter, i);
116 
117           /* recurse */
118           add_enabled_recursively (
119             self, &child_iter, tracks, size, count);
120         }
121     }
122 }
123 
124 /**
125  * Returns the currently checked tracks.
126  *
127  * Must be free'd with free().
128  *
129  * @param[out] num_tracks Number of tracks returned.
130  *
131  * @return Newly allocated track array, or NULL if
132  *   no tracks selected.
133  */
134 static Track **
get_enabled_tracks(ExportDialogWidget * self,int * num_tracks)135 get_enabled_tracks (
136   ExportDialogWidget * self,
137   int *                num_tracks)
138 {
139   size_t size = 1;
140   int count = 0;
141   Track ** tracks = object_new_n (size, Track *);
142 
143   GtkTreeModel * model =
144     GTK_TREE_MODEL (self->tracks_store);
145   GtkTreeIter iter;
146   gtk_tree_model_get_iter_first (
147     model, &iter);
148 
149   add_enabled_recursively (
150     self, &iter, &tracks, &size, &count);
151 
152   if (count == 0)
153     {
154       *num_tracks = 0;
155       free (tracks);
156       return NULL;
157     }
158   else
159     {
160       *num_tracks = count;
161       return tracks;
162     }
163 }
164 
165 /**
166  * Returns the currently selected tracks.
167  *
168  * Must be free'd with free().
169  *
170  * @param[out] num_tracks Number of tracks returned.
171  *
172  * @return Newly allocated track array, or NULL if
173  *   no tracks selected.
174  */
175 static Track **
get_selected_tracks(ExportDialogWidget * self,int * num_tracks)176 get_selected_tracks (
177   ExportDialogWidget * self,
178   int *                num_tracks)
179 {
180   GtkTreeSelection * selection =
181     gtk_tree_view_get_selection (
182       (self->tracks_treeview));
183 
184   size_t size = 1;
185   int count = 0;
186   Track ** tracks =
187     calloc (size, sizeof (Track *));
188 
189   GList * selected_rows =
190     gtk_tree_selection_get_selected_rows (
191       selection, NULL);
192   GList * list_iter =
193     g_list_first (selected_rows);
194   while (list_iter)
195     {
196       GtkTreePath * tp = list_iter->data;
197       gtk_tree_selection_select_path (
198         selection, tp);
199       GtkTreeIter iter;
200       gtk_tree_model_get_iter (
201         GTK_TREE_MODEL (self->tracks_store),
202         &iter, tp);
203       Track * track;
204       gtk_tree_model_get (
205         GTK_TREE_MODEL (self->tracks_store),
206         &iter, TRACK_COLUMN_TRACK, &track, -1);
207 
208       array_double_size_if_full (
209         tracks, count, size, Track *);
210       array_append (tracks, size, track);
211       g_debug ("track %s selected", track->name);
212 
213       list_iter = g_list_next (list_iter);
214     }
215 
216   g_list_free_full (
217     selected_rows,
218     (GDestroyNotify) gtk_tree_path_free);
219 
220   if (count == 0)
221     {
222       *num_tracks = 0;
223       free (tracks);
224       return NULL;
225     }
226   else
227     {
228       *num_tracks = count;
229       return tracks;
230     }
231 }
232 
233 static char *
get_mixdown_export_filename(ExportDialogWidget * self)234 get_mixdown_export_filename (
235   ExportDialogWidget * self)
236 {
237   const char * mixdown_str = "mixdown";
238   const char * format =
239     exporter_stringize_audio_format (
240       gtk_combo_box_get_active (self->format),
241       true);
242   char * datetime_str =
243     datetime_get_for_filename ();
244   char * base = NULL;
245   switch (g_settings_get_enum (
246             S_EXPORT, "filename-pattern"))
247     {
248     case EFP_APPEND_FORMAT:
249       base =
250         g_strdup_printf (
251           "%s.%s", mixdown_str, format);
252       break;
253     case EFP_PREPEND_DATE_APPEND_FORMAT:
254       base =
255         g_strdup_printf (
256           "%s_%s.%s", datetime_str,
257           mixdown_str, format);
258       break;
259     default:
260       g_return_val_if_reached (NULL);
261     }
262   g_return_val_if_fail (base, NULL);
263 
264   char * exports_dir =
265     project_get_path (
266       PROJECT, PROJECT_PATH_EXPORTS, false);
267   char * tmp =
268     g_build_filename (
269       exports_dir, base, NULL);
270   char * full_path =
271     io_get_next_available_filepath (tmp);
272   g_free (base);
273   g_free (tmp);
274   g_free (exports_dir);
275   g_free (datetime_str);
276 
277   /* we now have the full path, get only the
278    * basename */
279   base = g_path_get_basename (full_path);
280   g_free (full_path);
281 
282   return base;
283 }
284 
285 /**
286  * Gets the filename string for stems.
287  *
288  * @param max_files Max files to show, then append
289  *   "..." at the end.
290  * @param track If non-NULL, assumed to be the
291  *   stem for this track.
292  */
293 static char *
get_stem_export_filenames(ExportDialogWidget * self,int max_files,Track * in_track)294 get_stem_export_filenames (
295   ExportDialogWidget * self,
296   int                  max_files,
297   Track *              in_track)
298 {
299   int num_tracks;
300   Track ** tracks =
301     get_enabled_tracks (self, &num_tracks);
302 
303   if (!tracks)
304     {
305       return g_strdup (_("none"));
306     }
307 
308   const char * format =
309     exporter_stringize_audio_format (
310       gtk_combo_box_get_active (self->format),
311       true);
312   char * datetime_str =
313     datetime_get_for_filename ();
314 
315   GString * gstr = g_string_new (NULL);
316 
317   int new_max_files = MIN (num_tracks, max_files);
318 
319   for (int i = 0; i < new_max_files; i++)
320     {
321       Track * track = tracks[i];
322 
323       if (in_track)
324         {
325           track = in_track;
326         }
327 
328       char * base = NULL;
329       switch (g_settings_get_enum (
330                 S_EXPORT, "filename-pattern"))
331         {
332         case EFP_APPEND_FORMAT:
333           base =
334             g_strdup_printf (
335               "%s.%s", track->name, format);
336           break;
337         case EFP_PREPEND_DATE_APPEND_FORMAT:
338           base =
339             g_strdup_printf (
340               "%s_%s.%s", datetime_str,
341               track->name, format);
342           break;
343         default:
344           g_return_val_if_reached (NULL);
345         }
346       g_return_val_if_fail (base, NULL);
347 
348       char * exports_dir =
349         project_get_path (
350           PROJECT, PROJECT_PATH_EXPORTS, false);
351       char * tmp =
352         g_build_filename (
353           exports_dir, base, NULL);
354       char * full_path =
355         io_get_next_available_filepath (tmp);
356       g_free (base);
357       g_free (tmp);
358       g_free (exports_dir);
359 
360       /* we now have the full path, get only the
361        * basename */
362       base = g_path_get_basename (full_path);
363       g_free (full_path);
364 
365       g_string_append (gstr, base);
366 
367       if (in_track)
368         {
369           g_free (base);
370           goto return_result;
371         }
372 
373       if (i < (new_max_files - 1))
374         {
375           g_string_append (gstr, "\n");
376         }
377       else if (i == (new_max_files - 1) &&
378                new_max_files < num_tracks)
379         {
380           if (num_tracks - new_max_files == 1)
381             {
382               g_string_append (
383                 gstr, _("\n1 more file..."));
384             }
385           else
386             {
387               g_string_append_printf (
388                 gstr, _("\n%d more files..."),
389                 num_tracks - new_max_files);
390             }
391         }
392       g_free (base);
393     }
394 return_result:
395   g_free (datetime_str);
396 
397   return g_string_free (gstr, false);
398 }
399 
400 static char *
get_exports_dir(void)401 get_exports_dir (void)
402 {
403   bool export_stems =
404     g_settings_get_boolean (
405       S_EXPORT, "export-stems");
406   return
407     project_get_path (
408       PROJECT,
409       export_stems ?
410         PROJECT_PATH_EXPORTS_STEMS :
411         PROJECT_PATH_EXPORTS,
412       false);
413 }
414 
415 /**
416  * Gets the export filename only, or absolute path
417  * if @ref absolute is true.
418  *
419  * @param track If non-NULL, assumed to be the
420  *   stem for this track.
421  */
422 static char *
get_export_filename(ExportDialogWidget * self,bool absolute,Track * track)423 get_export_filename (
424   ExportDialogWidget * self,
425   bool                 absolute,
426   Track *              track)
427 {
428   bool export_stems =
429     g_settings_get_boolean (
430       S_EXPORT, "export-stems");
431   char * filename = NULL;
432   if (export_stems)
433     {
434       filename =
435         get_stem_export_filenames (self, 4, track);
436       if (absolute)
437         {
438           char * exports_dir = get_exports_dir ();
439           char * abs_path =
440             g_build_filename (
441               exports_dir, filename, NULL);
442           g_free (exports_dir);
443           g_free (filename);
444           return abs_path;
445         }
446       else
447         {
448           return filename;
449         }
450     }
451   else
452     {
453       filename =
454         get_mixdown_export_filename (self);
455 
456       if (absolute)
457         {
458           char * exports_dir = get_exports_dir ();
459           char * abs_path =
460             g_build_filename (
461               exports_dir, filename, NULL);
462           g_free (exports_dir);
463           g_free (filename);
464           return abs_path;
465         }
466       else
467         {
468           return filename;
469         }
470     }
471 }
472 
473 static void
update_text(ExportDialogWidget * self)474 update_text (ExportDialogWidget * self)
475 {
476   char * filename =
477     get_export_filename (self, false, NULL);
478   g_return_if_fail (filename);
479 
480   char matcha[10];
481   ui_gdk_rgba_to_hex (&UI_COLORS->matcha, matcha);
482 
483 #define ORANGIZE(x) \
484   "<span " \
485   "foreground=\"" matcha "\">" x "</span>"
486 
487   char * exports_dir = get_exports_dir ();
488   char * str =
489     g_strdup_printf (
490       "%s\n"
491       "<span foreground=\"%s\">%s</span>"
492       "\n\n"
493       "%s\n"
494       "<a href=\"%s\">%s</a>",
495       _("The following files will be created:"),
496       matcha,
497       filename,
498       _("in the directory:"),
499       exports_dir,
500       exports_dir);
501   gtk_label_set_markup (
502     self->output_label, str);
503   g_free (filename);
504   g_free (str);
505   g_free (exports_dir);
506 
507   g_signal_connect (
508     G_OBJECT (self->output_label), "activate-link",
509     G_CALLBACK (z_gtk_activate_dir_link_func), self);
510 
511 #undef ORANGIZE
512 }
513 
514 static void
on_song_toggled(GtkToggleButton * toggle,ExportDialogWidget * self)515 on_song_toggled (GtkToggleButton * toggle,
516                  ExportDialogWidget * self)
517 {
518   if (gtk_toggle_button_get_active (toggle))
519     {
520       gtk_toggle_button_set_active (
521         self->time_range_loop, 0);
522       gtk_toggle_button_set_active (
523         self->time_range_custom, 0);
524     }
525 }
526 
527 static void
on_loop_toggled(GtkToggleButton * toggle,ExportDialogWidget * self)528 on_loop_toggled (GtkToggleButton * toggle,
529                  ExportDialogWidget * self)
530 {
531   if (gtk_toggle_button_get_active (toggle))
532     {
533       gtk_toggle_button_set_active (
534         self->time_range_song, 0);
535       gtk_toggle_button_set_active (
536         self->time_range_custom, 0);
537     }
538 }
539 
540 static void
on_custom_toggled(GtkToggleButton * toggle,ExportDialogWidget * self)541 on_custom_toggled (GtkToggleButton * toggle,
542                    ExportDialogWidget * self)
543 {
544   if (gtk_toggle_button_get_active (toggle))
545     {
546       gtk_toggle_button_set_active (
547         self->time_range_song, 0);
548       gtk_toggle_button_set_active (
549         self->time_range_loop, 0);
550     }
551 }
552 
553 static void
on_mixdown_toggled(GtkToggleButton * toggle,ExportDialogWidget * self)554 on_mixdown_toggled (
555   GtkToggleButton * toggle,
556   ExportDialogWidget * self)
557 {
558   bool export_stems =
559     !gtk_toggle_button_get_active (toggle);
560   g_settings_set_boolean (
561     S_EXPORT, "export-stems", export_stems);
562   gtk_toggle_button_set_active (
563     self->stems_toggle, export_stems);
564   update_text (self);
565 }
566 
567 static void
on_stems_toggled(GtkToggleButton * toggle,ExportDialogWidget * self)568 on_stems_toggled (
569   GtkToggleButton * toggle,
570   ExportDialogWidget * self)
571 {
572   bool export_stems =
573     gtk_toggle_button_get_active (toggle);
574   g_settings_set_boolean (
575     S_EXPORT, "export-stems", export_stems);
576   gtk_toggle_button_set_active (
577     self->mixdown_toggle, !export_stems);
578   update_text (self);
579 }
580 
581 /**
582  * Creates the combo box model for bit depth.
583  */
584 static GtkTreeModel *
create_bit_depth_store()585 create_bit_depth_store ()
586 {
587   GtkTreeIter iter;
588   GtkTreeStore *store;
589 
590   store =
591     gtk_tree_store_new (1,
592                         G_TYPE_STRING);
593 
594   gtk_tree_store_append (store, &iter, NULL);
595   gtk_tree_store_set (
596     store, &iter,
597     0, "16 bit",
598     -1);
599   gtk_tree_store_append (store, &iter, NULL);
600   gtk_tree_store_set (
601     store, &iter,
602     0, "24 bit",
603     -1);
604   gtk_tree_store_append (store, &iter, NULL);
605   gtk_tree_store_set (
606     store, &iter,
607     0, "32 bit",
608     -1);
609 
610   return GTK_TREE_MODEL (store);
611 }
612 
613 static void
setup_bit_depth_combo_box(ExportDialogWidget * self)614 setup_bit_depth_combo_box (
615   ExportDialogWidget * self)
616 {
617   GtkTreeModel * model =
618     create_bit_depth_store ();
619   gtk_combo_box_set_model (
620     self->bit_depth,
621     model);
622   gtk_cell_layout_clear (
623     GTK_CELL_LAYOUT (self->bit_depth));
624   GtkCellRenderer* renderer =
625     gtk_cell_renderer_text_new ();
626   gtk_cell_layout_pack_start (
627     GTK_CELL_LAYOUT (self->bit_depth),
628     renderer,
629     TRUE);
630   gtk_cell_layout_set_attributes (
631     GTK_CELL_LAYOUT (self->bit_depth),
632     renderer,
633     "text", 0,
634     NULL);
635 
636   gtk_combo_box_set_active (
637     self->bit_depth,
638     0);
639 }
640 
641 /**
642  * Creates the combo box model for the pattern.
643  */
644 static GtkTreeModel *
create_filename_pattern_store()645 create_filename_pattern_store ()
646 {
647   GtkTreeIter iter;
648   GtkTreeStore *store;
649 
650   store = gtk_tree_store_new (1, G_TYPE_STRING);
651 
652   gtk_tree_store_append (store, &iter, NULL);
653   gtk_tree_store_set (
654     store, &iter,
655     0, _("<name>.<format>"),
656     -1);
657   gtk_tree_store_append (store, &iter, NULL);
658   gtk_tree_store_set (
659     store, &iter,
660     0, _("<date>_<name>.<format>"),
661     -1);
662 
663   return GTK_TREE_MODEL (store);
664 }
665 
666 static void
on_filename_pattern_changed(GtkComboBox * widget,ExportDialogWidget * self)667 on_filename_pattern_changed (
668   GtkComboBox *        widget,
669   ExportDialogWidget * self)
670 {
671   g_settings_set_enum (
672     S_EXPORT, "filename-pattern",
673     gtk_combo_box_get_active (widget));
674 
675   update_text (self);
676 }
677 
678 static void
setup_filename_pattern_combo_box(ExportDialogWidget * self)679 setup_filename_pattern_combo_box (
680   ExportDialogWidget * self)
681 {
682   GtkTreeModel * model =
683     create_filename_pattern_store ();
684   gtk_combo_box_set_model (
685     self->filename_pattern, model);
686   gtk_cell_layout_clear (
687     GTK_CELL_LAYOUT (self->filename_pattern));
688   GtkCellRenderer* renderer =
689     gtk_cell_renderer_text_new ();
690   gtk_cell_layout_pack_start (
691     GTK_CELL_LAYOUT (self->filename_pattern),
692     renderer, TRUE);
693   gtk_cell_layout_set_attributes (
694     GTK_CELL_LAYOUT (self->filename_pattern),
695     renderer, "text", 0, NULL);
696 
697   gtk_combo_box_set_active (
698     self->filename_pattern,
699     g_settings_get_enum (
700       S_EXPORT, "filename-pattern"));
701 
702   g_signal_connect (
703     G_OBJECT (self->filename_pattern), "changed",
704     G_CALLBACK (on_filename_pattern_changed), self);
705 }
706 
707 /**
708  * Creates the combo box model for the audio
709  * formats.
710  */
711 static GtkTreeModel *
create_formats_store()712 create_formats_store ()
713 {
714   GtkTreeIter iter;
715   GtkTreeStore *store;
716 
717   store =
718     gtk_tree_store_new (NUM_AUDIO_FORMAT_COLUMNS,
719                         G_TYPE_STRING,
720                         G_TYPE_INT);
721 
722   for (int i = 0; i < NUM_AUDIO_FORMATS; i++)
723     {
724       gtk_tree_store_append (store, &iter, NULL);
725       const char * str =
726         exporter_stringize_audio_format (i, false);
727       gtk_tree_store_set (
728         store, &iter,
729         COLUMN_AUDIO_FORMAT_LABEL, str,
730         COLUMN_AUDIO_FORMAT, i,
731         -1);
732     }
733 
734   return GTK_TREE_MODEL (store);
735 }
736 
737 static void
on_format_changed(GtkComboBox * widget,ExportDialogWidget * self)738 on_format_changed (
739   GtkComboBox *        widget,
740   ExportDialogWidget * self)
741 {
742   update_text (self);
743   AudioFormat format =
744     gtk_combo_box_get_active (widget);
745 
746   g_settings_set_enum (
747     S_EXPORT, "format", format);
748 
749 #define SET_SENSITIVE(x) \
750   gtk_widget_set_sensitive ( \
751     GTK_WIDGET (self->x), 1)
752 
753 #define SET_UNSENSITIVE(x) \
754   gtk_widget_set_sensitive ( \
755     GTK_WIDGET (self->x), 0)
756 
757   SET_UNSENSITIVE (export_genre);
758   SET_UNSENSITIVE (export_artist);
759   SET_UNSENSITIVE (export_title);
760   SET_UNSENSITIVE (bit_depth);
761   SET_UNSENSITIVE (dither);
762 
763   switch (format)
764     {
765     case AUDIO_FORMAT_MIDI:
766       break;
767     case AUDIO_FORMAT_OGG_VORBIS:
768     case AUDIO_FORMAT_OGG_OPUS:
769       SET_SENSITIVE (export_genre);
770       SET_SENSITIVE (export_artist);
771       SET_SENSITIVE (export_title);
772       SET_SENSITIVE (dither);
773       break;
774     default:
775       SET_SENSITIVE (export_genre);
776       SET_SENSITIVE (export_artist);
777       SET_SENSITIVE (export_title);
778       SET_SENSITIVE (bit_depth);
779       SET_SENSITIVE (dither);
780       break;
781     }
782 
783 #undef SET_SENSITIVE
784 #undef SET_UNSENSITIVE
785 }
786 
787 static void
setup_formats_combo_box(ExportDialogWidget * self)788 setup_formats_combo_box (
789   ExportDialogWidget * self)
790 {
791   GtkTreeModel * model =
792     create_formats_store ();
793   gtk_combo_box_set_model (
794     self->format,
795     model);
796   gtk_cell_layout_clear (
797     GTK_CELL_LAYOUT (self->format));
798   GtkCellRenderer* renderer =
799     gtk_cell_renderer_text_new ();
800   gtk_cell_layout_pack_start (
801     GTK_CELL_LAYOUT (self->format),
802     renderer,
803     TRUE);
804   gtk_cell_layout_set_attributes (
805     GTK_CELL_LAYOUT (self->format),
806     renderer,
807     "text", COLUMN_AUDIO_FORMAT_LABEL,
808     NULL);
809 
810   gtk_combo_box_set_active (
811     self->format,
812     g_settings_get_enum (S_EXPORT, "format"));
813 }
814 
815 static void
on_cancel_clicked(GtkButton * btn,ExportDialogWidget * self)816 on_cancel_clicked (GtkButton * btn,
817                    ExportDialogWidget * self)
818 {
819   gtk_window_close (GTK_WINDOW (self));
820   /*gtk_widget_destroy (GTK_WIDGET (self));*/
821 }
822 
823 static void
on_progress_dialog_closed(GtkDialog * dialog,int response_id,ExportDialogWidget * self)824 on_progress_dialog_closed (
825   GtkDialog * dialog,
826   int         response_id,
827   ExportDialogWidget * self)
828 {
829   update_text (self);
830 }
831 
832 /**
833  * @param track If non-NULL, assumed to be a stem
834  *   for this track.
835  */
836 static void
init_export_info(ExportDialogWidget * self,ExportSettings * info,Track * track)837 init_export_info (
838   ExportDialogWidget * self,
839   ExportSettings *     info,
840   Track *              track)
841 {
842   memset (info, 0, sizeof (ExportSettings));
843   info->format =
844     gtk_combo_box_get_active (self->format);
845   g_settings_set_enum (
846     S_EXPORT, "format", info->format);
847   info->depth =
848     gtk_combo_box_get_active (self->bit_depth);
849   g_settings_set_enum (
850     S_EXPORT, "bit-depth", info->depth);
851   info->dither =
852     gtk_toggle_button_get_active (self->dither);
853   g_settings_set_boolean (
854     S_EXPORT, "dither", info->dither);
855   info->artist =
856     g_strdup (
857       gtk_entry_get_text (self->export_artist));
858   info->title =
859     g_strdup (
860       gtk_entry_get_text (self->export_title));
861   info->genre =
862     g_strdup (
863       gtk_entry_get_text (self->export_genre));
864   g_settings_set_string (
865     S_EXPORT, "artist", info->artist);
866   g_settings_set_string (
867     S_EXPORT, "title", info->title);
868   g_settings_set_string (
869     S_EXPORT, "genre", info->genre);
870 
871 #define SET_TIME_RANGE(x) \
872 g_settings_set_enum ( \
873 S_EXPORT, "time-range", TIME_RANGE_##x); \
874 info->time_range = TIME_RANGE_##x
875 
876   if (gtk_toggle_button_get_active (
877         self->time_range_song))
878     {
879       SET_TIME_RANGE (SONG);
880     }
881   else if (gtk_toggle_button_get_active (
882              self->time_range_loop))
883     {
884       SET_TIME_RANGE (LOOP);
885     }
886   else if (gtk_toggle_button_get_active (
887              self->time_range_custom))
888     {
889       SET_TIME_RANGE (CUSTOM);
890     }
891 
892   info->file_uri =
893     get_export_filename (self, true, track);
894 
895   info->bounce_with_parents = true;
896 
897   info->mode = EXPORT_MODE_TRACKS;
898   info->progress_info.has_error = false;
899   info->progress_info.cancelled = false;
900   strcpy (info->progress_info.error_str, "");
901 }
902 
903 static void
on_export_clicked(GtkButton * btn,ExportDialogWidget * self)904 on_export_clicked (
905   GtkButton * btn,
906   ExportDialogWidget * self)
907 {
908   bool export_stems =
909     g_settings_get_boolean (
910       S_EXPORT, "export-stems");
911 
912   int num_tracks;
913   Track ** tracks =
914     get_enabled_tracks (self, &num_tracks);
915   if (!tracks)
916     {
917       ui_show_error_message (
918         MAIN_WINDOW, _("No tracks to export"));
919       return;
920     }
921 
922   /* make exports dir if not there yet */
923   char * exports_dir = get_exports_dir ();
924   io_mkdir (exports_dir);
925   g_free (exports_dir);
926 
927   if (export_stems)
928     {
929       /* export each track individually */
930       for (int i = 0; i < num_tracks; i++)
931         {
932           /* unmark all tracks for bounce */
933           tracklist_mark_all_tracks_for_bounce (
934             TRACKLIST, false);
935 
936           Track * track = tracks[i];
937           track_mark_for_bounce (
938             track, F_BOUNCE, F_MARK_REGIONS,
939             F_MARK_CHILDREN, F_MARK_PARENTS);
940 
941           ExportSettings info;
942           init_export_info (self, &info, track);
943 
944           g_message ("exporting %s", info.file_uri);
945 
946           /* start exporting in a new thread */
947           GThread * thread =
948             g_thread_new (
949               "export_thread",
950               (GThreadFunc)
951               exporter_generic_export_thread,
952               &info);
953 
954           /* create a progress dialog and block */
955           ExportProgressDialogWidget * progress_dialog =
956             export_progress_dialog_widget_new (
957               &info, true, true, F_CANCELABLE);
958           gtk_window_set_transient_for (
959             GTK_WINDOW (progress_dialog),
960             GTK_WINDOW (self));
961           g_signal_connect (
962             G_OBJECT (progress_dialog), "response",
963             G_CALLBACK (on_progress_dialog_closed),
964             self);
965           gtk_dialog_run (
966             GTK_DIALOG (progress_dialog));
967           gtk_widget_destroy (
968             GTK_WIDGET (progress_dialog));
969 
970           g_thread_join (thread);
971 
972           g_free (info.file_uri);
973 
974           track->bounce = false;
975         }
976     }
977   else /* if exporting mixdown */
978     {
979       ExportSettings info;
980       init_export_info (self, &info, NULL);
981 
982       /* unmark all tracks for bounce */
983       tracklist_mark_all_tracks_for_bounce (
984         TRACKLIST, false);
985 
986       /* mark all checked tracks for bounce */
987       for (int i = 0; i < num_tracks; i++)
988         {
989           Track * track = tracks[i];
990           track_mark_for_bounce (
991             track, F_BOUNCE, F_MARK_REGIONS,
992             F_NO_MARK_CHILDREN, F_MARK_PARENTS);
993         }
994 
995       g_message ("exporting %s", info.file_uri);
996 
997       /* start exporting in a new thread */
998       GThread * thread =
999         g_thread_new (
1000           "export_thread",
1001           (GThreadFunc) exporter_generic_export_thread,
1002           &info);
1003 
1004       /* create a progress dialog and block */
1005       ExportProgressDialogWidget * progress_dialog =
1006         export_progress_dialog_widget_new (
1007           &info, true, true, F_CANCELABLE);
1008       gtk_window_set_transient_for (
1009         GTK_WINDOW (progress_dialog),
1010         GTK_WINDOW (self));
1011       g_signal_connect (
1012         G_OBJECT (progress_dialog), "response",
1013         G_CALLBACK (on_progress_dialog_closed), self);
1014       gtk_dialog_run (GTK_DIALOG (progress_dialog));
1015       gtk_widget_destroy (GTK_WIDGET (progress_dialog));
1016 
1017       g_thread_join (thread);
1018 
1019       g_free (info.file_uri);
1020     }
1021 }
1022 
1023 /**
1024  * Visible function for tracks tree model.
1025  *
1026  * Used for filtering based on selected options.
1027  */
1028 static gboolean
tracks_tree_model_visible_func(GtkTreeModel * model,GtkTreeIter * iter,ExportDialogWidget * self)1029 tracks_tree_model_visible_func (
1030   GtkTreeModel *       model,
1031   GtkTreeIter  *       iter,
1032   ExportDialogWidget * self)
1033 {
1034   bool visible = true;
1035 
1036   return visible;
1037 }
1038 
1039 static void
add_group_track_children(ExportDialogWidget * self,GtkTreeStore * tree_store,GtkTreeIter * iter,Track * track)1040 add_group_track_children (
1041   ExportDialogWidget * self,
1042   GtkTreeStore *       tree_store,
1043   GtkTreeIter *        iter,
1044   Track *              track)
1045 {
1046   /* add the group */
1047   GtkTreeIter group_iter;
1048   gtk_tree_store_append (
1049     tree_store, &group_iter, iter);
1050   gtk_tree_store_set (
1051     tree_store, &group_iter,
1052     TRACK_COLUMN_CHECKBOX, true,
1053     TRACK_COLUMN_ICON, track->icon_name,
1054     TRACK_COLUMN_BG_RGBA, &track->color,
1055     TRACK_COLUMN_DUMMY_TEXT, dummy_text,
1056     TRACK_COLUMN_NAME, track->name,
1057     TRACK_COLUMN_TRACK, track,
1058     -1);
1059 
1060   g_debug (
1061     "%s: track '%s'", __func__, track->name);
1062 
1063   /* add the children */
1064   for (int i = 0; i < track->num_children; i++)
1065     {
1066       Track * child =
1067         tracklist_find_track_by_name_hash (
1068           TRACKLIST, track->children[i]);
1069       g_return_if_fail (
1070         IS_TRACK_AND_NONNULL (child));
1071 
1072       g_debug ("child: '%s'", child->name);
1073 
1074       if (child->num_children > 0)
1075         {
1076           add_group_track_children (
1077             self, tree_store, &group_iter, child);
1078         }
1079       else
1080         {
1081           GtkTreeIter child_iter;
1082           gtk_tree_store_append (
1083             tree_store, &child_iter, &group_iter);
1084           gtk_tree_store_set (
1085             tree_store, &child_iter,
1086             TRACK_COLUMN_CHECKBOX, true,
1087             TRACK_COLUMN_ICON, child->icon_name,
1088             TRACK_COLUMN_BG_RGBA, &child->color,
1089             TRACK_COLUMN_DUMMY_TEXT, dummy_text,
1090             TRACK_COLUMN_NAME, child->name,
1091             TRACK_COLUMN_TRACK, child,
1092             -1);
1093         }
1094     }
1095 }
1096 
1097 static GtkTreeModel *
create_model_for_tracks(ExportDialogWidget * self)1098 create_model_for_tracks (
1099   ExportDialogWidget * self)
1100 {
1101   /* checkbox, icon, foreground rgba,
1102    * background rgba, name, track */
1103   GtkTreeStore * tree_store =
1104     gtk_tree_store_new (
1105       NUM_TRACK_COLUMNS,
1106       G_TYPE_BOOLEAN,
1107       G_TYPE_STRING,
1108       GDK_TYPE_RGBA,
1109       G_TYPE_STRING,
1110       G_TYPE_STRING,
1111       G_TYPE_POINTER);
1112 
1113   /*GtkTreeIter iter;*/
1114   /*gtk_tree_store_append (tree_store, &iter, NULL);*/
1115   add_group_track_children (
1116     self, tree_store, NULL, P_MASTER_TRACK);
1117 
1118   self->tracks_store = tree_store;
1119 
1120   GtkTreeModel * model =
1121     gtk_tree_model_filter_new (
1122       GTK_TREE_MODEL (tree_store), NULL);
1123   gtk_tree_model_filter_set_visible_func (
1124     GTK_TREE_MODEL_FILTER (model),
1125     (GtkTreeModelFilterVisibleFunc)
1126     tracks_tree_model_visible_func,
1127     self, NULL);
1128 
1129   return model;
1130 }
1131 
1132 /**
1133  * This toggles on all parents recursively if
1134  * something is toggled.
1135  */
1136 static void
set_track_toggle_on_parent_recursively(ExportDialogWidget * self,GtkTreeIter * iter,bool toggled)1137 set_track_toggle_on_parent_recursively (
1138   ExportDialogWidget * self,
1139   GtkTreeIter *        iter,
1140   bool                 toggled)
1141 {
1142   if (!toggled)
1143     {
1144       return;
1145     }
1146 
1147   GtkTreeModel * model =
1148     GTK_TREE_MODEL (self->tracks_store);
1149 
1150   /* enable the parent if toggled */
1151   GtkTreeIter parent_iter;
1152   bool has_parent =
1153     gtk_tree_model_iter_parent (
1154       model, &parent_iter, iter);
1155   if (has_parent)
1156     {
1157       /* set new value on widget */
1158       gtk_tree_store_set (
1159         GTK_TREE_STORE (model), &parent_iter,
1160         TRACK_COLUMN_CHECKBOX, toggled, -1);
1161 
1162       set_track_toggle_on_parent_recursively (
1163         self, &parent_iter, toggled);
1164     }
1165 }
1166 
1167 static void
set_track_toggle_recursively(ExportDialogWidget * self,GtkTreeIter * iter,bool toggled)1168 set_track_toggle_recursively (
1169   ExportDialogWidget * self,
1170   GtkTreeIter *        iter,
1171   bool                 toggled)
1172 {
1173   GtkTreeModel * model =
1174     GTK_TREE_MODEL (self->tracks_store);
1175 
1176   /* if group track, also enable/disable children
1177    * recursively */
1178   bool has_children =
1179     gtk_tree_model_iter_has_child (model, iter);
1180   if (has_children)
1181     {
1182       int num_children =
1183         gtk_tree_model_iter_n_children (
1184           model, iter);
1185 
1186       for (int i = 0; i < num_children; i++)
1187         {
1188           GtkTreeIter child_iter;
1189           gtk_tree_model_iter_nth_child (
1190             model, &child_iter, iter, i);
1191 
1192           /* set new value on widget */
1193           gtk_tree_store_set (
1194             GTK_TREE_STORE (model), &child_iter,
1195             TRACK_COLUMN_CHECKBOX, toggled, -1);
1196 
1197           /* recurse */
1198           set_track_toggle_recursively (
1199             self, &child_iter, toggled);
1200         }
1201     }
1202 }
1203 
1204 static void
on_track_toggled(GtkCellRendererToggle * cell,gchar * path_str,ExportDialogWidget * self)1205 on_track_toggled (
1206   GtkCellRendererToggle * cell,
1207   gchar *                 path_str,
1208   ExportDialogWidget *    self)
1209 {
1210   GtkTreeModel * model =
1211     GTK_TREE_MODEL (self->tracks_store);
1212   g_debug ("path str: %s", path_str);
1213 
1214   /* get tree path and iter */
1215   GtkTreePath * path =
1216     gtk_tree_path_new_from_string (path_str);
1217   GtkTreeIter  iter;
1218   bool ret =
1219     gtk_tree_model_get_iter (model, &iter, path);
1220   g_return_if_fail (ret);
1221   g_return_if_fail (
1222     gtk_tree_store_iter_is_valid (
1223       GTK_TREE_STORE (model), &iter));
1224 
1225   /* get toggled */
1226   gboolean toggled;
1227   gtk_tree_model_get (
1228     model, &iter,
1229     TRACK_COLUMN_CHECKBOX, &toggled, -1);
1230   g_return_if_fail (
1231     gtk_tree_store_iter_is_valid (
1232       GTK_TREE_STORE (model), &iter));
1233 
1234   /* get new value */
1235   toggled ^= 1;
1236 
1237   /* set new value on widget */
1238   gtk_tree_store_set (
1239     GTK_TREE_STORE (model), &iter,
1240     TRACK_COLUMN_CHECKBOX, toggled, -1);
1241 
1242   /* if exporting mixdown (single file) */
1243   if (!g_settings_get_boolean (
1244         S_EXPORT, "export-stems"))
1245     {
1246       /* toggle parents if toggled */
1247       set_track_toggle_on_parent_recursively (
1248         self, &iter, toggled);
1249 
1250       /* propagate value to children recursively */
1251       set_track_toggle_recursively (
1252         self, &iter, toggled);
1253     }
1254 
1255   update_text (self);
1256 
1257   /* clean up */
1258   gtk_tree_path_free (path);
1259 }
1260 
1261 static void
enable_or_disable_tracks(ExportDialogWidget * self,bool enable)1262 enable_or_disable_tracks (
1263   ExportDialogWidget * self,
1264   bool                 enable)
1265 {
1266   int num_tracks;
1267   Track ** tracks =
1268     get_selected_tracks (self, &num_tracks);
1269 
1270   if (tracks)
1271     {
1272       /* TODO enable/disable */
1273     }
1274 }
1275 
1276 static void
on_tracks_disable(GtkMenuItem * menuitem,ExportDialogWidget * self)1277 on_tracks_disable (
1278   GtkMenuItem *        menuitem,
1279   ExportDialogWidget * self)
1280 {
1281   g_message ("tracks disable");
1282   enable_or_disable_tracks (self, false);
1283 }
1284 
1285 static void
on_tracks_enable(GtkMenuItem * menuitem,ExportDialogWidget * self)1286 on_tracks_enable (
1287   GtkMenuItem *        menuitem,
1288   ExportDialogWidget * self)
1289 {
1290   g_message ("tracks enable");
1291   enable_or_disable_tracks (self, true);
1292 }
1293 
1294 static void
show_tracks_context_menu(ExportDialogWidget * self)1295 show_tracks_context_menu (
1296   ExportDialogWidget * self)
1297 {
1298   GtkWidget *menuitem;
1299   GtkWidget * menu = gtk_menu_new();
1300 
1301   /* FIXME this is allocating memory every time */
1302 
1303   menuitem =
1304     gtk_menu_item_new_with_label (_("Enable"));
1305   gtk_widget_set_visible (menuitem, true);
1306   gtk_menu_shell_append (
1307     GTK_MENU_SHELL (menu),
1308     GTK_WIDGET (menuitem));
1309   g_signal_connect (
1310     G_OBJECT (menuitem), "activate",
1311     G_CALLBACK (on_tracks_enable), self);
1312 
1313   menuitem =
1314     gtk_menu_item_new_with_label (_("Disable"));
1315   gtk_widget_set_visible (menuitem, true);
1316   gtk_menu_shell_append (
1317     GTK_MENU_SHELL (menu),
1318     GTK_WIDGET (menuitem));
1319   g_signal_connect (
1320     G_OBJECT (menuitem), "activate",
1321     G_CALLBACK (on_tracks_disable), self);
1322 
1323   gtk_menu_attach_to_widget (
1324     GTK_MENU (menu),
1325     GTK_WIDGET (self), NULL);
1326   gtk_menu_popup_at_pointer (GTK_MENU (menu), NULL);
1327 }
1328 
1329 static void
on_track_right_click(GtkGestureMultiPress * gesture,gint n_press,gdouble x_dbl,gdouble y_dbl,ExportDialogWidget * self)1330 on_track_right_click (
1331   GtkGestureMultiPress * gesture,
1332   gint                   n_press,
1333   gdouble                x_dbl,
1334   gdouble                y_dbl,
1335   ExportDialogWidget *   self)
1336 {
1337   if (n_press != 1)
1338     return;
1339 
1340   show_tracks_context_menu (self);
1341 }
1342 
1343 static void
setup_treeview(ExportDialogWidget * self)1344 setup_treeview (
1345   ExportDialogWidget * self)
1346 {
1347   self->tracks_model =
1348     create_model_for_tracks (self);
1349   gtk_tree_view_set_model (
1350     self->tracks_treeview, self->tracks_model);
1351 
1352   GtkTreeView * tree_view = self->tracks_treeview;
1353 
1354   /* init tree view */
1355   GtkCellRenderer * renderer;
1356   GtkTreeViewColumn * column;
1357 
1358   /* column for checkbox */
1359   renderer = gtk_cell_renderer_toggle_new ();
1360   column =
1361     gtk_tree_view_column_new_with_attributes (
1362       "icon", renderer,
1363       "active", TRACK_COLUMN_CHECKBOX,
1364       NULL);
1365   gtk_tree_view_append_column (tree_view, column);
1366   g_signal_connect (
1367     renderer, "toggled",
1368     G_CALLBACK (on_track_toggled), self);
1369 
1370   /* column for color */
1371   renderer = gtk_cell_renderer_text_new ();
1372   column =
1373     gtk_tree_view_column_new_with_attributes (
1374       "name", renderer,
1375       "text", TRACK_COLUMN_DUMMY_TEXT,
1376       "background-rgba", TRACK_COLUMN_BG_RGBA,
1377       NULL);
1378   gtk_tree_view_append_column (tree_view, column);
1379 
1380   /* column for icon */
1381   renderer = gtk_cell_renderer_pixbuf_new ();
1382   column =
1383     gtk_tree_view_column_new_with_attributes (
1384       "icon", renderer,
1385       "icon-name", TRACK_COLUMN_ICON,
1386       NULL);
1387   gtk_tree_view_append_column (tree_view, column);
1388 
1389   /* column for name */
1390   renderer = gtk_cell_renderer_text_new ();
1391   column =
1392     gtk_tree_view_column_new_with_attributes (
1393       "name", renderer,
1394       "text", TRACK_COLUMN_NAME,
1395       NULL);
1396   gtk_tree_view_append_column (tree_view, column);
1397 
1398   /* set search column */
1399   gtk_tree_view_set_search_column (
1400     tree_view, TRACK_COLUMN_NAME);
1401 
1402   /* hide headers */
1403   gtk_tree_view_set_headers_visible (
1404     GTK_TREE_VIEW (tree_view), false);
1405 
1406   gtk_tree_selection_set_mode (
1407     gtk_tree_view_get_selection (tree_view),
1408     GTK_SELECTION_MULTIPLE);
1409 
1410   /* connect right click handler */
1411   GtkGestureMultiPress * mp =
1412     GTK_GESTURE_MULTI_PRESS (
1413       gtk_gesture_multi_press_new (
1414         GTK_WIDGET (tree_view)));
1415   gtk_gesture_single_set_button (
1416     GTK_GESTURE_SINGLE (mp),
1417     GDK_BUTTON_SECONDARY);
1418   g_signal_connect (
1419     G_OBJECT (mp), "pressed",
1420     G_CALLBACK (on_track_right_click), self);
1421 
1422 #if 0
1423   /* set search func */
1424   gtk_tree_view_set_search_equal_func (
1425     GTK_TREE_VIEW (tree_view),
1426     (GtkTreeViewSearchEqualFunc)
1427       plugin_search_equal_func,
1428     self, NULL);
1429 
1430   GtkTreeSelection * sel =
1431     gtk_tree_view_get_selection (
1432       GTK_TREE_VIEW (tree_view));
1433   g_signal_connect (
1434     G_OBJECT (sel), "changed",
1435     G_CALLBACK (on_selection_changed), self);
1436 #endif
1437 }
1438 
1439 /**
1440  * Creates a new export dialog.
1441  */
1442 ExportDialogWidget *
export_dialog_widget_new()1443 export_dialog_widget_new ()
1444 {
1445   ExportDialogWidget * self =
1446     g_object_new (EXPORT_DIALOG_WIDGET_TYPE, NULL);
1447 
1448   update_text (self);
1449 
1450   g_signal_connect (
1451     G_OBJECT (self->export_button), "clicked",
1452     G_CALLBACK (on_export_clicked), self);
1453   g_signal_connect (
1454     G_OBJECT (self->format), "changed",
1455     G_CALLBACK (on_format_changed), self);
1456   g_signal_connect (
1457     G_OBJECT (self->time_range_song), "toggled",
1458     G_CALLBACK (on_song_toggled), self);
1459   g_signal_connect (
1460     G_OBJECT (self->time_range_loop), "toggled",
1461     G_CALLBACK (on_loop_toggled), self);
1462   g_signal_connect (
1463     G_OBJECT (self->time_range_custom), "toggled",
1464     G_CALLBACK (on_custom_toggled), self);
1465   g_signal_connect (
1466     G_OBJECT (self->mixdown_toggle), "toggled",
1467     G_CALLBACK (on_mixdown_toggled), self);
1468   g_signal_connect (
1469     G_OBJECT (self->stems_toggle), "toggled",
1470     G_CALLBACK (on_stems_toggled), self);
1471 
1472   return self;
1473 }
1474 
1475 static void
export_dialog_widget_class_init(ExportDialogWidgetClass * _klass)1476 export_dialog_widget_class_init (
1477   ExportDialogWidgetClass * _klass)
1478 {
1479   GtkWidgetClass * klass = GTK_WIDGET_CLASS (_klass);
1480   resources_set_class_template (
1481     klass, "export_dialog.ui");
1482 
1483 #define BIND_CHILD(x) \
1484   gtk_widget_class_bind_template_child ( \
1485     klass, ExportDialogWidget, x)
1486 
1487   BIND_CHILD (cancel_button);
1488   BIND_CHILD (export_button);
1489   BIND_CHILD (export_artist);
1490   BIND_CHILD (export_title);
1491   BIND_CHILD (export_genre);
1492   BIND_CHILD (filename_pattern);
1493   BIND_CHILD (bit_depth);
1494   BIND_CHILD (time_range_song);
1495   BIND_CHILD (time_range_loop);
1496   BIND_CHILD (time_range_custom);
1497   BIND_CHILD (format);
1498   BIND_CHILD (dither);
1499   BIND_CHILD (output_label);
1500   BIND_CHILD (tracks_treeview);
1501   BIND_CHILD (mixdown_toggle);
1502   BIND_CHILD (stems_toggle);
1503 
1504 #undef BIND_CHILD
1505 
1506   gtk_widget_class_bind_template_callback (
1507     klass, on_cancel_clicked);
1508 }
1509 
1510 static void
export_dialog_widget_init(ExportDialogWidget * self)1511 export_dialog_widget_init (
1512   ExportDialogWidget * self)
1513 {
1514   gtk_widget_init_template (GTK_WIDGET (self));
1515 
1516   gtk_entry_set_text (
1517     GTK_ENTRY (self->export_artist),
1518     g_settings_get_string (S_EXPORT, "artist"));
1519   gtk_entry_set_text (
1520     GTK_ENTRY (self->export_title),
1521     g_settings_get_string (S_EXPORT, "title"));
1522   gtk_entry_set_text (
1523     GTK_ENTRY (self->export_genre),
1524     g_settings_get_string (S_EXPORT, "genre"));
1525 
1526   gtk_toggle_button_set_active (
1527     self->time_range_song, false);
1528   gtk_toggle_button_set_active (
1529     self->time_range_loop, false);
1530   gtk_toggle_button_set_active (
1531     self->time_range_custom, false);
1532   switch (g_settings_get_enum (S_EXPORT, "time-range"))
1533     {
1534     case 0: // loop
1535       gtk_toggle_button_set_active (
1536         self->time_range_loop, true);
1537       break;
1538     case 1: // song
1539       gtk_toggle_button_set_active (
1540         self->time_range_song, true);
1541       break;
1542     case 2: // custom
1543       gtk_toggle_button_set_active (
1544         self->time_range_custom, true);
1545       break;
1546     }
1547   bool export_stems =
1548     g_settings_get_boolean (
1549       S_EXPORT, "export-stems");
1550   gtk_toggle_button_set_active (
1551     self->mixdown_toggle, !export_stems);
1552   gtk_toggle_button_set_active (
1553     self->stems_toggle, export_stems);
1554 
1555   gtk_toggle_button_set_active (
1556     GTK_TOGGLE_BUTTON (self->dither),
1557     g_settings_get_boolean (
1558       S_EXPORT, "dither"));
1559 
1560   setup_bit_depth_combo_box (self);
1561   setup_formats_combo_box (self);
1562   setup_filename_pattern_combo_box (self);
1563 
1564   setup_treeview (self);
1565 
1566   on_format_changed (self->format, self);
1567 }
1568