1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
2 /*
3  * presets.c
4  * Copyright (C) John Stebbins 2008-2021 <stebbins@stebbins>
5  *
6  * presets.c is free software.
7  *
8  * You may redistribute it and/or modify it under the terms of the
9  * GNU General Public License version 2, as published by the Free Software
10  * Foundation.
11  *
12  * presets.c is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15  * See the GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with main.c.  If not, write to:
19  *  The Free Software Foundation, Inc.,
20  *  51 Franklin Street, Fifth Floor
21  *  Boston, MA  02110-1301, USA.
22  */
23 #include <sys/types.h>
24 #include <sys/stat.h>
25 #include <fcntl.h>
26 #include <unistd.h>
27 #include <glib.h>
28 #include <glib-object.h>
29 #include <glib/gstdio.h>
30 #include <glib/gi18n.h>
31 #include <string.h>
32 #include "ghbcompat.h"
33 #include "handbrake/handbrake.h"
34 #include "settings.h"
35 #include "callbacks.h"
36 #include "audiohandler.h"
37 #include "subtitlehandler.h"
38 #include "hb-backend.h"
39 #include "resources.h"
40 #include "presets.h"
41 #include "values.h"
42 #include "handbrake/lang.h"
43 #include "videohandler.h"
44 
45 #define MAX_NESTED_PRESET 3
46 
47 static GhbValue *prefsDict = NULL;
48 static gboolean prefs_modified = FALSE;
49 static gchar *override_user_config_dir = NULL;
50 
51 static void store_prefs(void);
52 static void store_presets(void);
53 
54 hb_preset_index_t*
ghb_tree_get_index(GtkTreeModel * store,GtkTreeIter * iter)55 ghb_tree_get_index(GtkTreeModel *store, GtkTreeIter *iter)
56 {
57     GtkTreePath       *treepath;
58     int               *indices, len;
59     hb_preset_index_t *path;
60 
61     treepath = gtk_tree_model_get_path(store, iter);
62     indices  = gtk_tree_path_get_indices(treepath);
63     len      = gtk_tree_path_get_depth(treepath);
64     path     = hb_preset_index_init(indices, len);
65     gtk_tree_path_free(treepath);
66 
67     return path;
68 }
69 
70 hb_preset_index_t*
ghb_tree_path_get_index(GtkTreePath * treepath)71 ghb_tree_path_get_index(GtkTreePath *treepath)
72 {
73     int *indices, len;
74 
75     indices = gtk_tree_path_get_indices(treepath);
76     len     = gtk_tree_path_get_depth(treepath);
77 
78     return hb_preset_index_init(indices, len);
79 }
80 
81 // This only handle limited depth
82 GtkTreePath*
ghb_tree_path_new_from_index(const hb_preset_index_t * path)83 ghb_tree_path_new_from_index(const hb_preset_index_t *path)
84 {
85     if (path == NULL || path->depth == 0)
86         return NULL;
87 
88 #if GTK_CHECK_VERSION(3, 12, 0)
89     return gtk_tree_path_new_from_indicesv((int*)path->index, path->depth);
90 #else
91     switch (path->depth)
92     {
93         case 1:
94             return gtk_tree_path_new_from_indices(
95                 path->index[0], -1);
96         case 2:
97             return gtk_tree_path_new_from_indices(
98                 path->index[0], path->index[1], -1);
99         case 3:
100             return gtk_tree_path_new_from_indices(
101                 path->index[0], path->index[1], path->index[2], -1);
102         case 4:
103             return gtk_tree_path_new_from_indices(
104                 path->index[0], path->index[1], path->index[2],
105                 path->index[3], -1);
106         case 5:
107             return gtk_tree_path_new_from_indices(
108                 path->index[0], path->index[1], path->index[2],
109                 path->index[3], path->index[4], -1);
110         case 6:
111             return gtk_tree_path_new_from_indices(
112                 path->index[0], path->index[1], path->index[2],
113                 path->index[3], path->index[4], path->index[5], -1);
114         case 7:
115             return gtk_tree_path_new_from_indices(
116                 path->index[0], path->index[1], path->index[2],
117                 path->index[3], path->index[4], path->index[5],
118                 path->index[6], -1);
119         case 8:
120             return gtk_tree_path_new_from_indices(
121                 path->index[0], path->index[1], path->index[2],
122                 path->index[3], path->index[4], path->index[5],
123                 path->index[6], path->index[7], -1);
124         default:
125             g_warning("Preset path depth too deep");
126             return NULL;
127     }
128 #endif
129 }
130 
131 #if 0
132 void
133 dump_preset_indices(const gchar *msg, hb_preset_index_t *path)
134 {
135     gint ii;
136 
137     g_message("%s indices: len %d", msg, path->depth);
138     for (ii = 0; ii < path->depth; ii++)
139     {
140         printf("%d ", path->index[ii]);
141     }
142     printf("\n");
143 }
144 #endif
145 
146 void
ghb_presets_list_show_default(signal_user_data_t * ud)147 ghb_presets_list_show_default(signal_user_data_t *ud)
148 {
149     hb_preset_index_t *path;
150 
151     path = hb_presets_get_default_index();
152     if (path == NULL || path->depth == 0)
153         return;
154 
155     GtkTreeView  *treeview;
156     GtkTreeStore *store;
157     GtkTreePath  *treepath;
158     GtkTreeIter   iter;
159 
160     treeview = GTK_TREE_VIEW(GHB_WIDGET(ud->builder, "presets_list"));
161     store    = GTK_TREE_STORE(gtk_tree_view_get_model(treeview));
162     treepath = ghb_tree_path_new_from_index(path);
163     if (treepath)
164     {
165         if (gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, treepath))
166         {
167             gtk_tree_store_set(store, &iter,
168                         1, 800,
169                         2, 2 ,
170                         -1);
171         }
172         gtk_tree_path_free(treepath);
173     }
174     free(path);
175 }
176 
177 void
ghb_presets_list_clear_default(signal_user_data_t * ud)178 ghb_presets_list_clear_default(signal_user_data_t *ud)
179 {
180     hb_preset_index_t *path;
181 
182     path = hb_presets_get_default_index();
183     if (path == NULL || path->depth == 0)
184         return;
185 
186     GtkTreeView  *treeview;
187     GtkTreeStore *store;
188     GtkTreePath  *treepath;
189     GtkTreeIter   iter;
190 
191     treeview = GTK_TREE_VIEW(GHB_WIDGET(ud->builder, "presets_list"));
192     store    = GTK_TREE_STORE(gtk_tree_view_get_model(treeview));
193     treepath = ghb_tree_path_new_from_index(path);
194     if (treepath)
195     {
196         if (gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, treepath))
197         {
198             gtk_tree_store_set(store, &iter,
199                         1, 400,
200                         2, 0 ,
201                         -1);
202         }
203         gtk_tree_path_free(treepath);
204     }
205     free(path);
206 }
207 
208 static gint
preset_get_type(hb_preset_index_t * path)209 preset_get_type(hb_preset_index_t *path)
210 {
211     GhbValue *dict;
212     dict = hb_preset_get(path);
213     if (dict)
214     {
215         return ghb_dict_get_int(dict, "Type");
216     }
217     else
218     {
219         g_warning("ghb_preset_get_type (): internal preset lookup error");
220         return 0;
221     }
222     return 0;
223 }
224 
225 static gboolean
preset_is_folder(hb_preset_index_t * path)226 preset_is_folder(hb_preset_index_t *path)
227 {
228     GhbValue *dict;
229     gboolean folder = FALSE;
230 
231     dict = hb_preset_get(path);
232     if (dict)
233     {
234         folder = ghb_dict_get_bool(dict, "Folder");
235     }
236     return folder;
237 }
238 
239 void
ghb_preset_to_settings(GhbValue * settings,GhbValue * preset)240 ghb_preset_to_settings(GhbValue *settings, GhbValue *preset)
241 {
242     // Remove legacy x264Option
243     ghb_dict_remove(settings, "x264Option");
244 
245     // Initialize defaults
246     ghb_settings_init(settings, "Initialization");
247 
248     // Initialize the ui settings from a preset
249     ghb_dict_copy(settings, preset);
250 
251     // Fix up all the internal settings that are derived from preset values.
252 
253     ghb_dict_set(settings, "scale_height", ghb_value_dup(
254         ghb_dict_get_value(settings, "PictureHeight")));
255 
256     ghb_dict_set(settings, "scale_width", ghb_value_dup(
257         ghb_dict_get_value(settings, "PictureWidth")));
258 
259     if (ghb_dict_get_bool(settings, "PictureAutoCrop"))
260     {
261         ghb_dict_set_string(settings, "crop_mode", "auto");
262     }
263     else
264     {
265         int ct = ghb_dict_get_int(settings, "PictureTopCrop");
266         int cb = ghb_dict_get_int(settings, "PictureBottomCrop");
267         int cl = ghb_dict_get_int(settings, "PictureLeftCrop");
268         int cr = ghb_dict_get_int(settings, "PictureRightCrop");
269         if (ct == 0 && cb == 0 && cl == 0 && cr == 0)
270         {
271             ghb_dict_set_string(settings, "crop_mode", "none");
272         }
273         else
274         {
275             ghb_dict_set_string(settings, "crop_mode", "custom");
276         }
277     }
278 
279     int width, height;
280     const gchar * resolution_limit;
281 
282     width    = ghb_dict_get_int(settings, "PictureWidth");
283     height   = ghb_dict_get_int(settings, "PictureHeight");
284     resolution_limit = ghb_lookup_resolution_limit(width, height);
285     ghb_dict_set_string(settings, "resolution_limit", resolution_limit);
286 
287     const char * rotate = ghb_dict_get_string(settings, "PictureRotate");
288     if (rotate != NULL)
289     {
290         int         angle, hflip;
291         char      * angle_opt;
292         hb_dict_t * filter_settings;
293 
294         filter_settings = hb_generate_filter_settings(HB_FILTER_ROTATE,
295                                                       NULL, NULL, rotate);
296         angle = hb_dict_get_int(filter_settings, "angle");
297         hflip = hb_dict_get_bool(filter_settings, "hflip");
298         angle_opt = g_strdup_printf("%d", angle);
299         ghb_dict_set_string(settings, "rotate", angle_opt);
300         ghb_dict_set_int(settings, "hflip", hflip);
301         g_free(angle_opt);
302         hb_value_free(&filter_settings);
303     }
304     gint vqtype;
305 
306     vqtype = ghb_dict_get_int(settings, "VideoQualityType");
307 
308     // VideoQualityType/0/1/2 - vquality_type_/target/bitrate/constant
309     // *note: target is no longer used
310     switch (vqtype)
311     {
312     case 0:
313     {
314         ghb_dict_set_bool(settings, "vquality_type_bitrate", TRUE);
315         ghb_dict_set_bool(settings, "vquality_type_constant", FALSE);
316     } break;
317     case 1:
318     {
319         ghb_dict_set_bool(settings, "vquality_type_bitrate", TRUE);
320         ghb_dict_set_bool(settings, "vquality_type_constant", FALSE);
321     } break;
322     case 2:
323     {
324         ghb_dict_set_bool(settings, "vquality_type_bitrate", FALSE);
325         ghb_dict_set_bool(settings, "vquality_type_constant", TRUE);
326     } break;
327     default:
328     {
329         ghb_dict_set_bool(settings, "vquality_type_bitrate", FALSE);
330         ghb_dict_set_bool(settings, "vquality_type_constant", TRUE);
331     } break;
332     }
333 
334     const gchar *mode = ghb_dict_get_string(settings, "VideoFramerateMode");
335     if (mode != NULL && strcmp(mode, "cfr") == 0)
336     {
337         ghb_dict_set_bool(settings, "VideoFramerateCFR", TRUE);
338         ghb_dict_set_bool(settings, "VideoFrameratePFR", FALSE);
339         ghb_dict_set_bool(settings, "VideoFramerateVFR", FALSE);
340     }
341     else if (mode != NULL && strcmp(mode, "pfr") == 0)
342     {
343         ghb_dict_set_bool(settings, "VideoFramerateCFR", FALSE);
344         ghb_dict_set_bool(settings, "VideoFrameratePFR", TRUE);
345         ghb_dict_set_bool(settings, "VideoFramerateVFR", FALSE);
346     }
347     else
348     {
349         ghb_dict_set_bool(settings, "VideoFramerateCFR", FALSE);
350         ghb_dict_set_bool(settings, "VideoFrameratePFR", FALSE);
351         ghb_dict_set_bool(settings, "VideoFramerateVFR", TRUE);
352     }
353 
354     int                 encoder;
355     const char         *videoPreset;
356 
357     encoder      = ghb_get_video_encoder(settings);
358     videoPreset  = ghb_dict_get_string(settings, "VideoPreset");
359     ghb_set_video_preset(settings, encoder, videoPreset);
360 
361     char *videoTune;
362     char *tune = NULL;
363     char *saveptr;
364     char *tok;
365 
366     videoTune = g_strdup(ghb_dict_get_string(settings, "VideoTune"));
367     if (videoTune != NULL)
368     {
369         tok = strtok_r(videoTune, ",./-+", &saveptr);
370         ghb_dict_set_bool(settings, "x264FastDecode", FALSE);
371         ghb_dict_set_bool(settings, "x264ZeroLatency", FALSE);
372         while (tok != NULL)
373         {
374             if (!strcasecmp(tok, "fastdecode"))
375             {
376                 ghb_dict_set_bool(settings, "x264FastDecode", TRUE);
377             }
378             else if (!strcasecmp(tok, "zerolatency"))
379             {
380                 ghb_dict_set_bool(settings, "x264ZeroLatency", TRUE);
381             }
382             else if (tune == NULL)
383             {
384                 tune = g_strdup(tok);
385             }
386             else
387             {
388                 ghb_log("Superfluous tunes! %s", tok);
389             }
390             tok = strtok_r(NULL, ",./-+", &saveptr);
391         }
392         g_free(videoTune);
393     }
394     if (tune != NULL)
395     {
396         ghb_dict_set_string(settings, "VideoTune", tune);
397         g_free(tune);
398     }
399 
400     const char *videoProfile;
401     videoProfile = ghb_dict_get_string(settings, "VideoProfile");
402     if (videoProfile != NULL)
403         ghb_dict_set_string(settings, "VideoProfile", videoProfile);
404 
405     const char *videoLevel;
406     videoLevel = ghb_dict_get_string(settings, "VideoLevel");
407     if (videoLevel != NULL)
408         ghb_dict_set_string(settings, "VideoLevel", videoLevel);
409 
410     if (ghb_dict_get(settings, "x264OptionExtra") != NULL)
411     {
412         const char *optionExtra;
413         optionExtra = ghb_dict_get_string(settings, "x264OptionExtra");
414         ghb_dict_set_string(settings, "VideoOptionExtra", optionExtra);
415     }
416 
417     // Extract copy mask to check box booleans
418     GhbValue *copy_mask;
419     copy_mask = ghb_dict_get(preset, "AudioCopyMask");
420     if (copy_mask != NULL)
421     {
422         int count = ghb_array_len(copy_mask);
423         int ii;
424         for (ii = 0; ii < count; ii++)
425         {
426             GhbValue *val = ghb_array_get(copy_mask, ii);
427             const char *s = ghb_value_get_string(val);
428             int acodec = hb_audio_encoder_get_from_name(s);
429             switch (acodec)
430             {
431                 default:
432                     break;
433                 case HB_ACODEC_MP2_PASS:
434                     ghb_dict_set_bool(settings, "AudioAllowMP2Pass", 1);
435                     break;
436                 case HB_ACODEC_LAME:
437                 case HB_ACODEC_MP3_PASS:
438                     ghb_dict_set_bool(settings, "AudioAllowMP3Pass", 1);
439                     break;
440                 case HB_ACODEC_CA_AAC:
441                 case HB_ACODEC_FDK_AAC:
442                 case HB_ACODEC_FFAAC:
443                 case HB_ACODEC_AAC_PASS:
444                     ghb_dict_set_bool(settings, "AudioAllowAACPass", 1);
445                     break;
446                 case HB_ACODEC_AC3:
447                 case HB_ACODEC_AC3_PASS:
448                     ghb_dict_set_bool(settings, "AudioAllowAC3Pass", 1);
449                     break;
450                 case HB_ACODEC_DCA:
451                 case HB_ACODEC_DCA_PASS:
452                     ghb_dict_set_bool(settings, "AudioAllowDTSPass", 1);
453                     break;
454                 case HB_ACODEC_DCA_HD:
455                 case HB_ACODEC_DCA_HD_PASS:
456                     ghb_dict_set_bool(settings, "AudioAllowDTSHDPass", 1);
457                     break;
458                 case HB_ACODEC_FFEAC3:
459                 case HB_ACODEC_EAC3_PASS:
460                     ghb_dict_set_bool(settings, "AudioAllowEAC3Pass", 1);
461                     break;
462                 case HB_ACODEC_FFFLAC:
463                 case HB_ACODEC_FFFLAC24:
464                 case HB_ACODEC_FLAC_PASS:
465                     ghb_dict_set_bool(settings, "AudioAllowFLACPass", 1);
466                     break;
467                 case HB_ACODEC_FFTRUEHD:
468                 case HB_ACODEC_TRUEHD_PASS:
469                     ghb_dict_set_bool(settings, "AudioAllowTRUEHDPass", 1);
470                     break;
471             }
472         }
473     }
474 }
475 
476 // Initialization order of some widgets matter because the value of
477 // these widgets are used to establish limits on the values that
478 // other widgets are allowed to take.
479 //
480 // So make sure these get initialized first.
481 static const char *widget_priority_list[] =
482 {
483     "preview_count",
484     "PtoPType",
485     "VideoEncoder",
486     "VideoQualityGranularity",
487     "AudioEncoder",
488     "PictureDeinterlaceFilter",
489     "PictureDeinterlacePreset",
490     NULL
491 };
492 
493 void
ghb_settings_to_ui(signal_user_data_t * ud,GhbValue * dict)494 ghb_settings_to_ui(signal_user_data_t *ud, GhbValue *dict)
495 {
496     GhbDictIter  iter;
497     const gchar *key;
498     GhbValue    *gval;
499     int          ii;
500     GhbValue    *tmp = ghb_value_dup(dict);
501 
502     if (dict == NULL)
503         return;
504 
505     for (ii = 0; widget_priority_list[ii] != NULL; ii++)
506     {
507         key = widget_priority_list[ii];
508         gval = ghb_dict_get_value(tmp, key);
509         if (gval != NULL)
510             ghb_ui_settings_update(ud, dict, key, gval);
511     }
512 
513     iter = ghb_dict_iter_init(tmp);
514     // middle (void*) cast prevents gcc warning "dereferencing type-punned
515     // pointer will break strict-aliasing rules"
516     while (ghb_dict_iter_next(tmp, &iter, &key, &gval))
517     {
518         ghb_ui_settings_update(ud, dict, key, gval);
519     }
520     ghb_value_free(&tmp);
521 }
522 
523 char*
preset_get_fullname(hb_preset_index_t * path,const char * sep,gboolean escape)524 preset_get_fullname(hb_preset_index_t *path, const char * sep, gboolean escape)
525 {
526     int                ii;
527     GString           *gstr;
528     hb_preset_index_t *tmp;
529     GhbValue          *dict;
530 
531     gstr = g_string_new("");
532     tmp  = hb_preset_index_dup(path);
533     for (ii = 1; ii <= path->depth; ii++)
534     {
535         const char *name;
536         tmp->depth = ii;
537         dict = hb_preset_get(tmp);
538         if (dict == NULL)
539         {
540             break;
541         }
542         name = ghb_dict_get_string(dict, "PresetName");
543         if (name != NULL)
544         {
545             g_string_append(gstr, sep);
546             if (escape)
547             {
548                 char * esc = g_markup_escape_text(name, -1);
549                 g_string_append(gstr, esc);
550                 g_free(esc);
551             }
552             else
553             {
554                 g_string_append(gstr, name);
555             }
556         }
557     }
558     free(tmp);
559     char *str = g_string_free(gstr, FALSE);
560     return str;
561 }
562 
563 static void
set_preset_menu_button_label(signal_user_data_t * ud,hb_preset_index_t * path)564 set_preset_menu_button_label(signal_user_data_t *ud, hb_preset_index_t *path)
565 {
566     char              * fullname, * text;
567     const char        * description;
568     GtkLabel          * label;
569     GtkWidget         * widget;
570     GhbValue          * dict;
571     int                 type;
572 
573     dict = hb_preset_get(path);
574     type = ghb_dict_get_int(dict, "Type");
575     fullname = preset_get_fullname(path, " <span alpha=\"70%\">></span> ", TRUE);
576     label = GTK_LABEL(GHB_WIDGET(ud->builder, "presets_menu_button_label"));
577     text = g_strdup_printf("%s%s", type == HB_PRESET_TYPE_CUSTOM ?
578                                    "Custom" : "Official", fullname);
579     gtk_label_set_markup(label, text);
580     free(fullname);
581     free(text);
582 
583     description = ghb_dict_get_string(dict, "PresetDescription");
584     widget = GHB_WIDGET(ud->builder, "presets_menu_button");
585     gtk_widget_set_tooltip_text(widget, description);
586 }
587 
588 void
ghb_preset_menu_button_refresh(signal_user_data_t * ud,const char * fullname,int type)589 ghb_preset_menu_button_refresh(signal_user_data_t *ud,
590                                const char *fullname, int type)
591 {
592     hb_preset_index_t * path;
593 
594     path = hb_preset_search_index(fullname, 0, type);
595     set_preset_menu_button_label(ud, path);
596 }
597 
598 static void
select_preset2(signal_user_data_t * ud,hb_preset_index_t * path)599 select_preset2(signal_user_data_t *ud, hb_preset_index_t *path)
600 {
601     GtkTreeView      *treeview;
602     GtkTreeSelection *selection;
603     GtkTreeModel     *store;
604     GtkTreeIter       iter;
605     GtkTreePath      *treepath;
606 
607     if (path == NULL || path->depth == 0)
608         return;
609 
610     treeview  = GTK_TREE_VIEW(GHB_WIDGET(ud->builder, "presets_list"));
611     selection = gtk_tree_view_get_selection (treeview);
612     store     = gtk_tree_view_get_model (treeview);
613     treepath  = ghb_tree_path_new_from_index(path);
614     if (treepath != NULL)
615     {
616         gtk_tree_view_expand_to_path(treeview, treepath);
617         if (gtk_tree_model_get_iter(store, &iter, treepath))
618         {
619             gtk_tree_selection_select_iter(selection, &iter);
620         }
621         else
622         {
623             if (gtk_tree_model_get_iter_first(store, &iter))
624                 gtk_tree_selection_select_iter(selection, &iter);
625         }
626         // Make the selection visible in scroll window if it is not.
627         gtk_tree_view_scroll_to_cell(treeview, treepath, NULL, FALSE, 0, 0);
628         gtk_tree_path_free(treepath);
629     }
630     set_preset_menu_button_label(ud, path);
631 
632     int type = preset_get_type(path);
633     GSimpleAction * action;
634 
635     action = G_SIMPLE_ACTION(g_action_map_lookup_action(
636                              G_ACTION_MAP(ud->app), "preset-rename"));
637     g_simple_action_set_enabled(action, type == HB_PRESET_TYPE_CUSTOM);
638     action = G_SIMPLE_ACTION(g_action_map_lookup_action(
639                              G_ACTION_MAP(ud->app), "preset-save"));
640     g_simple_action_set_enabled(action, type == HB_PRESET_TYPE_CUSTOM);
641 }
642 
643 void
ghb_select_preset(signal_user_data_t * ud,const char * name,int type)644 ghb_select_preset(signal_user_data_t *ud, const char *name, int type)
645 {
646     hb_preset_index_t *path;
647 
648     path = hb_preset_search_index(name, 1, type);
649     if (path != NULL)
650     {
651         select_preset2(ud, path);
652         free(path);
653     }
654 }
655 
656 void
ghb_select_default_preset(signal_user_data_t * ud)657 ghb_select_default_preset(signal_user_data_t *ud)
658 {
659     hb_preset_index_t *path;
660 
661     path = hb_presets_get_default_index();
662     if (path == NULL || path->depth == 0)
663     {
664         // No default set, find original "default" preset
665         g_free(path);
666         path = hb_preset_search_index("Fast 1080p30", 1, HB_PRESET_TYPE_ALL);
667     }
668     if (path == NULL || path->depth == 0)
669     {
670         int index[2] = {0, 0};
671 
672         // Could not find original default, try first available preset
673         g_free(path);
674         path = hb_preset_index_init(index, 2);
675     }
676     if (path != NULL)
677     {
678         select_preset2(ud, path);
679         g_free(path);
680     }
681 }
682 
683 gchar *
ghb_get_user_config_dir(gchar * subdir)684 ghb_get_user_config_dir(gchar *subdir)
685 {
686     const gchar * dir, * ghb = "ghb";
687     gchar       * config;
688 
689     if (override_user_config_dir != NULL)
690     {
691         dir = override_user_config_dir;
692     }
693     else
694     {
695         dir = g_get_user_config_dir();
696     }
697     if (dir == NULL || !g_file_test(dir, G_FILE_TEST_IS_DIR))
698     {
699         dir = g_get_home_dir();
700         ghb = ".ghb";
701     }
702     if (dir == NULL || !g_file_test(dir, G_FILE_TEST_IS_DIR))
703     {
704         // Last ditch, use CWD
705         dir = "./";
706         ghb = ".ghb";
707     }
708     config = g_strdup_printf("%s/%s", dir, ghb);
709     if (!g_file_test(config, G_FILE_TEST_IS_DIR))
710         g_mkdir (config, 0755);
711     if (subdir)
712     {
713         gchar **split;
714         gint    ii;
715 
716         split = g_strsplit(subdir, G_DIR_SEPARATOR_S, -1);
717         for (ii = 0; split[ii] != NULL; ii++)
718         {
719             gchar *tmp;
720 
721             tmp = g_strdup_printf ("%s/%s", config, split[ii]);
722             g_free(config);
723             config = tmp;
724             if (!g_file_test(config, G_FILE_TEST_IS_DIR))
725                 g_mkdir (config, 0755);
726         }
727         g_strfreev(split);
728     }
729     return config;
730 }
731 
732 void
ghb_override_user_config_dir(char * dir)733 ghb_override_user_config_dir(char *dir)
734 {
735     override_user_config_dir = dir;
736 }
737 
738 static void
write_config_file(const gchar * name,GhbValue * dict)739 write_config_file(const gchar *name, GhbValue *dict)
740 {
741     gchar *config, *path;
742 
743     config = ghb_get_user_config_dir(NULL);
744     path   = g_strdup_printf ("%s/%s", config, name);
745     g_free(config);
746     ghb_json_write_file(path, dict);
747     g_free(path);
748 }
749 
750 void
ghb_write_settings_file(const gchar * path,GhbValue * dict)751 ghb_write_settings_file(const gchar *path, GhbValue *dict)
752 {
753     ghb_json_write_file(path, dict);
754 }
755 
756 static int
presets_add_config_file(const gchar * name)757 presets_add_config_file(const gchar *name)
758 {
759     gchar      *config, *path;
760     hb_value_t *preset;
761 
762     config = ghb_get_user_config_dir(NULL);
763     path   = g_strdup_printf ("%s/%s", config, name);
764     g_free(config);
765     if (!g_file_test(path, G_FILE_TEST_IS_REGULAR))
766         return -1;
767     preset = hb_presets_read_file(path);
768     g_free(path);
769     if (preset != NULL)
770     {
771         int hb_major, hb_minor, hb_micro;
772         int major, minor, micro;
773         hb_presets_version(preset, &major, &minor, &micro);
774         hb_presets_current_version(&hb_major, &hb_minor, &hb_micro);
775         if (major != hb_major)
776         {
777             // Make a backup whenever the preset version changes
778             config  = ghb_get_user_config_dir(NULL);
779             path    = g_strdup_printf ("%s/presets.%d.%d.%d.json",
780                             config, major, minor, micro);
781             hb_value_write_json(preset, path);
782             g_free(config);
783             g_free(path);
784         }
785 
786         if (major > hb_major)
787         {
788             // Change in major indicates non-backward compatible preset changes.
789             // We can't successfully load presets that were generated by
790             // a newer version of handbrake than is currently running.
791             hb_value_free(&preset);
792             return -2;
793         }
794 
795         hb_value_t *imported;
796         hb_presets_import(preset, &imported);
797         hb_presets_add(imported);
798         if (major != hb_major || minor != hb_minor || micro != hb_micro)
799         {
800             // Reload hb builtin presets
801             hb_presets_builtin_update();
802             store_presets();
803         }
804         hb_value_free(&imported);
805         hb_value_free(&preset);
806         return 0;
807     }
808     return -1;
809 }
810 
811 static GhbValue*
read_config_file(const gchar * name)812 read_config_file(const gchar *name)
813 {
814     gchar    *config, *path;
815     GhbValue *gval = NULL;
816 
817     config = ghb_get_user_config_dir(NULL);
818     path   = g_strdup_printf ("%s/%s", config, name);
819     g_free(config);
820     if (g_file_test(path, G_FILE_TEST_IS_REGULAR))
821     {
822         gval = ghb_json_parse_file(path);
823         if (gval == NULL)
824             gval = hb_plist_parse_file(path);
825     }
826     g_free(path);
827     return gval;
828 }
829 
830 GhbValue*
ghb_read_settings_file(const gchar * path)831 ghb_read_settings_file(const gchar *path)
832 {
833     GhbValue *gval = NULL;
834 
835     if (g_file_test(path, G_FILE_TEST_IS_REGULAR))
836     {
837         gval = ghb_json_parse_file(path);
838         if (gval == NULL)
839             gval = hb_plist_parse_file(path);
840     }
841     return gval;
842 }
843 
844 #if 0
845 // Currently unused, but keeping around just in case...
846 gboolean
847 ghb_lock_file(const gchar *name)
848 {
849 #if !defined(_WIN32)
850     gchar *config, *path;
851     int fd, lock = 0;
852 
853     config = ghb_get_user_config_dir(NULL);
854     path = g_strdup_printf ("%s/%s", config, name);
855     fd = open(path, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
856     if (fd >= 0)
857         lock = lockf(fd, F_TLOCK, 0);
858     if (lock)
859         close(fd);
860     g_free(config);
861     g_free(path);
862     return !lock;
863 #else
864     return 1;
865 #endif
866 }
867 #endif
868 
869 void
ghb_write_pid_file()870 ghb_write_pid_file()
871 {
872 #if !defined(_WIN32)
873     gchar *config, *path;
874     pid_t  pid;
875     FILE  *fp;
876     int    fd;
877 
878     pid    = getpid();
879     config = ghb_get_user_config_dir(NULL);
880     path   = g_strdup_printf ("%s/ghb.pid.%d", config, pid);
881     fp     = g_fopen(path, "w");
882 
883     if (fp != NULL)
884     {
885         fprintf(fp, "%d\n", pid);
886         fclose(fp);
887     }
888 
889     fd = open(path, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
890     if (fd >= 0)
891     {
892         lockf(fd, F_TLOCK, 0);
893     }
894 
895     g_free(config);
896     g_free(path);
897 #endif
898 }
899 
900 int
ghb_find_pid_file()901 ghb_find_pid_file()
902 {
903     const gchar *file;
904     gchar       *config;
905 
906     config = ghb_get_user_config_dir(NULL);
907 
908     if (g_file_test(config, G_FILE_TEST_IS_DIR))
909     {
910         GDir *gdir;
911         gdir = g_dir_open(config, 0, NULL);
912         file = g_dir_read_name(gdir);
913         while (file)
914         {
915             if (strncmp(file, "ghb.pid.", 8) == 0)
916             {
917                 gchar *path;
918                 int    pid;
919 
920                 sscanf(file, "ghb.pid.%d", &pid);
921                 path = g_strdup_printf("%s/%s", config, file);
922 
923 #if !defined(_WIN32)
924                 int fd, lock = 1;
925 
926                 fd = open(path, O_RDWR);
927                 if (fd >= 0)
928                 {
929                     lock = lockf(fd, F_TLOCK, 0);
930                 }
931                 if (lock == 0)
932                 {
933                     close(fd);
934                     g_dir_close(gdir);
935                     g_unlink(path);
936                     g_free(path);
937                     g_free(config);
938                     return pid;
939                 }
940                 g_free(path);
941                 close(fd);
942 #else
943                 g_dir_close(gdir);
944                 g_unlink(path);
945                 g_free(path);
946                 g_free(config);
947                 return pid;
948 #endif
949             }
950             file = g_dir_read_name(gdir);
951         }
952         g_dir_close(gdir);
953     }
954     g_free(config);
955     return -1;
956 }
957 
958 static void
remove_config_file(const gchar * name)959 remove_config_file(const gchar *name)
960 {
961     gchar *config, *path;
962 
963     config = ghb_get_user_config_dir(NULL);
964     path   = g_strdup_printf ("%s/%s", config, name);
965     if (g_file_test(path, G_FILE_TEST_IS_REGULAR))
966     {
967         g_unlink(path);
968     }
969     g_free(path);
970     g_free(config);
971 }
972 
973 void
ghb_pref_save(GhbValue * settings,const gchar * key)974 ghb_pref_save(GhbValue *settings, const gchar *key)
975 {
976     const GhbValue *value, *value2;
977 
978     value = ghb_dict_get_value(settings, key);
979     if (value != NULL)
980     {
981         GhbValue *dict;
982         dict = ghb_dict_get(prefsDict, "Preferences");
983         if (dict == NULL) return;
984         value2 = ghb_dict_get(dict, key);
985         if (ghb_value_cmp(value, value2) != 0)
986         {
987             ghb_dict_set(dict, key, ghb_value_dup(value));
988             store_prefs();
989             prefs_modified = FALSE;
990         }
991     }
992 }
993 
994 void
ghb_pref_set(GhbValue * settings,const gchar * key)995 ghb_pref_set(GhbValue *settings, const gchar *key)
996 {
997     const GhbValue *value, *value2;
998 
999     value = ghb_dict_get_value(settings, key);
1000     if (value != NULL)
1001     {
1002         GhbValue *dict;
1003         dict = ghb_dict_get(prefsDict, "Preferences");
1004         if (dict == NULL) return;
1005         value2 = ghb_dict_get(dict, key);
1006         if (ghb_value_cmp(value, value2) != 0)
1007         {
1008             ghb_dict_set(dict, key, ghb_value_dup(value));
1009             prefs_modified = TRUE;
1010         }
1011     }
1012 }
1013 
1014 void
ghb_prefs_store(void)1015 ghb_prefs_store(void)
1016 {
1017     if (prefs_modified)
1018     {
1019         store_prefs();
1020         prefs_modified = FALSE;
1021     }
1022 }
1023 
1024 void
ghb_settings_init(GhbValue * settings,const char * name)1025 ghb_settings_init(GhbValue *settings, const char *name)
1026 {
1027     GhbValue    *internal;
1028 
1029     GhbValue *internalDict = ghb_resource_get("internal-defaults");
1030     // Setting a ui widget will cause the corresponding setting
1031     // to be set, but it also triggers a callback that can
1032     // have the side effect of using other settings values
1033     // that have not yet been set.  So set *all* settings first
1034     // then update the ui.
1035     internal = ghb_dict_get(internalDict, name);
1036     ghb_dict_copy(settings, internal);
1037 }
1038 
1039 void
ghb_settings_close()1040 ghb_settings_close()
1041 {
1042     if (prefsDict)
1043         ghb_value_free(&prefsDict);
1044 }
1045 
1046 #if defined(_WIN32)
1047 gchar*
FindFirstCDROM(void)1048 FindFirstCDROM(void)
1049 {
1050     gint ii, drives;
1051     gchar drive[5];
1052 
1053     strcpy(drive, "A:" G_DIR_SEPARATOR_S);
1054     drives = GetLogicalDrives();
1055     for (ii = 0; ii < 26; ii++)
1056     {
1057         if (drives & 0x01)
1058         {
1059             guint dtype;
1060 
1061             drive[0] = 'A' + ii;
1062             dtype = GetDriveType(drive);
1063             if (dtype == DRIVE_CDROM)
1064             {
1065                 return g_strdup(drive);
1066             }
1067         }
1068         drives >>= 1;
1069     }
1070     return NULL;
1071 }
1072 #endif
1073 
1074 void
ghb_prefs_load(signal_user_data_t * ud)1075 ghb_prefs_load(signal_user_data_t *ud)
1076 {
1077     GhbValue    *dict, *internal;
1078     GhbValue *internalDict;
1079 
1080     internalDict = ghb_resource_get("internal-defaults");
1081     prefsDict    = read_config_file("preferences.json");
1082     if (prefsDict == NULL)
1083         prefsDict    = read_config_file("preferences");
1084     if (prefsDict == NULL)
1085         prefsDict = ghb_dict_new();
1086     dict     = ghb_dict_get(prefsDict, "Preferences");
1087     internal = ghb_dict_get(internalDict, "Preferences");
1088     if (dict == NULL && internal != NULL)
1089     {
1090         dict = ghb_value_dup(internal);
1091         ghb_dict_set(prefsDict, "Preferences", dict);
1092 
1093         const gchar *dir = g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP);
1094         if (dir == NULL)
1095         {
1096             dir = ".";
1097         }
1098         ghb_dict_set_string(dict, "ExportDirectory", dir);
1099 
1100         dir = g_get_user_special_dir (G_USER_DIRECTORY_VIDEOS);
1101         if (dir == NULL)
1102         {
1103             dir = ".";
1104         }
1105         ghb_dict_set_string(dict, "destination_dir", dir);
1106 
1107         ghb_dict_set_string(dict, "SrtDir", dir);
1108 #if defined(_WIN32)
1109         gchar *source;
1110 
1111         source = FindFirstCDROM();
1112         if (source == NULL)
1113         {
1114             source = g_strdup("C:" G_DIR_SEPARATOR_S);
1115         }
1116         ghb_dict_set_string(dict, "default_source", source);
1117         g_free(source);
1118 #endif
1119         store_prefs();
1120     }
1121     ghb_dict_remove(dict, "show_presets");
1122 }
1123 
1124 void
ghb_prefs_to_settings(GhbValue * settings)1125 ghb_prefs_to_settings(GhbValue *settings)
1126 {
1127     // Initialize the ui from presets file.
1128     GhbValue *dict;
1129 
1130     if (prefsDict == NULL)
1131         return;
1132 
1133     // Setting a ui widget will cause the corresponding setting
1134     // to be set, but it also triggers a callback that can
1135     // have the side effect of using other settings values
1136     // that have not yet been set.  So set *all* settings first
1137     // then update the ui.
1138     dict     = ghb_dict_get(prefsDict, "Preferences");
1139     ghb_dict_copy(settings, dict);
1140 }
1141 
1142 static const gchar*
get_preset_color(gint type,gboolean is_folder)1143 get_preset_color(gint type, gboolean is_folder)
1144 {
1145     const gchar *color;
1146 
1147     if (type == HB_PRESET_TYPE_CUSTOM)
1148     {
1149         color = "DimGray";
1150         if (is_folder)
1151         {
1152             color = "black";
1153         }
1154     }
1155     else
1156     {
1157         color = "blue";
1158         if (is_folder)
1159         {
1160             color = "Navy";
1161         }
1162     }
1163     return color;
1164 }
1165 
1166 hb_preset_index_t *
get_selected_path(signal_user_data_t * ud)1167 get_selected_path(signal_user_data_t *ud)
1168 {
1169     GtkTreeView      *treeview;
1170     GtkTreeSelection *selection;
1171     GtkTreeModel     *store;
1172     GtkTreeIter       iter;
1173 
1174     treeview  = GTK_TREE_VIEW(GHB_WIDGET(ud->builder, "presets_list"));
1175     selection = gtk_tree_view_get_selection(treeview);
1176     if (gtk_tree_selection_get_selected(selection, &store, &iter))
1177     {
1178         return ghb_tree_get_index(store, &iter);
1179     }
1180     return NULL;
1181 }
1182 
1183 G_MODULE_EXPORT gboolean
presets_window_delete_cb(GtkWidget * xwidget,GdkEvent * event,signal_user_data_t * ud)1184 presets_window_delete_cb(
1185     GtkWidget *xwidget,
1186 #if !GTK_CHECK_VERSION(3, 90, 0)
1187     GdkEvent *event,
1188 #endif
1189     signal_user_data_t *ud)
1190 {
1191     GSimpleAction * action;
1192     GVariant      * state = g_variant_new_boolean(FALSE);
1193 
1194     action = G_SIMPLE_ACTION(g_action_map_lookup_action(
1195                              G_ACTION_MAP(ud->app), "show-presets"));
1196     g_action_change_state(G_ACTION(action), state);
1197     return TRUE;
1198 }
1199 
1200 G_MODULE_EXPORT void
presets_sz_alloc_cb(GtkWidget * widget,int width,int height,int baseline,signal_user_data_t * ud)1201 presets_sz_alloc_cb(
1202     GtkWidget *widget,
1203 #if GTK_CHECK_VERSION(3, 90, 0)
1204     int width,
1205     int height,
1206     int baseline,
1207 #else
1208     GdkRectangle *rect,
1209 #endif
1210     signal_user_data_t *ud)
1211 {
1212     if (gtk_widget_get_visible(widget))
1213     {
1214         gint w, h, ww, wh;
1215         w = ghb_dict_get_int(ud->prefs, "presets_window_width");
1216         h = ghb_dict_get_int(ud->prefs, "presets_window_height");
1217 
1218         gtk_window_get_size(GTK_WINDOW(widget), &ww, &wh);
1219         if ( w != ww || h != wh )
1220         {
1221             ghb_dict_set_int(ud->prefs, "presets_window_width", ww);
1222             ghb_dict_set_int(ud->prefs, "presets_window_height", wh);
1223             ghb_pref_set(ud->prefs, "presets_window_width");
1224             ghb_pref_set(ud->prefs, "presets_window_height");
1225             ghb_prefs_store();
1226         }
1227     }
1228 }
1229 
1230 G_MODULE_EXPORT void
preset_select_action_cb(GSimpleAction * action,GVariant * param,signal_user_data_t * ud)1231 preset_select_action_cb(GSimpleAction *action, GVariant *param,
1232                         signal_user_data_t *ud)
1233 {
1234     const char * preset_path = g_variant_get_string(param, NULL);
1235     int          type        = preset_path[0] - '0';
1236 
1237     ghb_select_preset(ud, &preset_path[1], type);
1238 }
1239 
1240 G_MODULE_EXPORT void
preset_reload_action_cb(GSimpleAction * action,GVariant * param,signal_user_data_t * ud)1241 preset_reload_action_cb(GSimpleAction *action, GVariant *param,
1242                         signal_user_data_t *ud)
1243 {
1244     const char * preset_path;
1245     int          type;
1246 
1247     type         = ghb_dict_get_int(ud->settings, "Type");
1248     preset_path  = ghb_dict_get_string(ud->settings, "PresetFullName");
1249     if (preset_path != NULL)
1250     {
1251         ghb_select_preset(ud, preset_path, type);
1252     }
1253 }
1254 
1255 void
ghb_presets_menu_init(signal_user_data_t * ud)1256 ghb_presets_menu_init(signal_user_data_t *ud)
1257 {
1258     GMenu              * menu = g_menu_new();
1259     hb_preset_index_t  * path;
1260     GhbValue           * presets;
1261     int                  menu_count, submenu_count, type, ii, jj, kk;
1262     char              ** official_names;
1263 
1264     // Add official presets
1265     path   = hb_preset_index_init(NULL, 0);
1266     presets = hb_presets_get_folder_children(path);
1267     if (presets == NULL)
1268     {
1269         g_warning(_("ghb_presets_menu_init: Failed to find presets folder."));
1270         g_free(path);
1271         return;
1272     }
1273 
1274     menu_count = ghb_array_len(presets);
1275     // Menus can't contain the same name twice.  Since our preset list
1276     // allows official and custom preset categories with the same name
1277     // I must modify one of them when duplicates exist :(
1278     official_names = calloc(menu_count + 1, sizeof(char*));
1279     kk = 0;
1280     path->depth++;
1281     // Process Official Presets in first pass, then Custom Presets
1282     for (type = 0; type < 2; type++)
1283     {
1284         GMenu * section = g_menu_new();
1285         for (ii = 0; ii < menu_count; ii++)
1286         {
1287             GhbValue    * dict;
1288             const gchar * folder_name;
1289             gint          folder_type;
1290             gboolean      is_folder;
1291             GhbValue    * folder;
1292             GString     * folder_str;
1293             char        * menu_item_name;
1294 
1295             path->index[path->depth-1] = ii;
1296 
1297             dict        = ghb_array_get(presets, ii);
1298             folder_name = ghb_dict_get_string(dict, "PresetName");
1299             folder_type = ghb_dict_get_int(dict, "Type");
1300             is_folder   = ghb_dict_get_bool(dict, "Folder");
1301 
1302             if (folder_type != type)
1303             {
1304                 continue;
1305             }
1306 
1307             if (type == HB_PRESET_TYPE_OFFICIAL)
1308             {
1309                 // Add folder name to list of official names
1310                 official_names[kk++] = g_strdup(folder_name);
1311             }
1312 
1313             folder_str = g_string_new("");
1314             g_string_append_printf(folder_str, "%d/%s",
1315                                    folder_type, folder_name);
1316             if (is_folder)
1317             {
1318                 GMenu * submenu = g_menu_new();
1319 
1320                 folder = hb_presets_get_folder_children(path);
1321                 submenu_count = ghb_array_len(folder);
1322                 for (jj = 0; jj < submenu_count; jj++)
1323                 {
1324                     const gchar * name;
1325                     GString     * preset_str = g_string_new(folder_str->str);
1326 
1327                     dict        = ghb_array_get(folder, jj);
1328                     name        = ghb_dict_get_string(dict, "PresetName");
1329                     type        = ghb_dict_get_int(dict, "Type");
1330                     is_folder   = ghb_dict_get_bool(dict, "Folder");
1331 
1332                     // Sanity check, Preset types must match their folder
1333                     if (type != folder_type)
1334                     {
1335                         continue;
1336                     }
1337                     // Enforce 2 level limit
1338                     if (is_folder)
1339                     {
1340                         continue;
1341                     }
1342                     g_string_append_printf(preset_str, "/%s", name);
1343 
1344                     char * preset_path;
1345                     char * detail_action;
1346 
1347                     preset_path = g_string_free(preset_str, FALSE);
1348                     detail_action = g_strdup_printf("app.preset-select(\"%s\")",
1349                                                     preset_path);
1350                     g_menu_append(submenu, name, detail_action);
1351                     free(preset_path);
1352                     free(detail_action);
1353                 }
1354                 if (type == HB_PRESET_TYPE_CUSTOM &&
1355                     ghb_strv_contains((const char**)official_names, folder_name))
1356                 {
1357                     menu_item_name = g_strdup_printf("My %s", folder_name);
1358                 }
1359                 else
1360                 {
1361                     menu_item_name = g_strdup(folder_name);
1362                 }
1363                 g_menu_append_submenu(section, menu_item_name,
1364                                       G_MENU_MODEL(submenu));
1365                 g_free(menu_item_name);
1366             }
1367             g_string_free(folder_str, TRUE);
1368         }
1369         g_menu_append_section(menu,
1370                               type == HB_PRESET_TYPE_CUSTOM ?
1371                               "Custom" : "Official",
1372                               G_MENU_MODEL(section));
1373     }
1374     g_free(path);
1375     g_strfreev(official_names);
1376 
1377     GtkMenuButton * mb;
1378 
1379     mb = GTK_MENU_BUTTON(GHB_WIDGET(ud->builder, "presets_menu_button"));
1380     gtk_menu_button_set_menu_model(mb, G_MENU_MODEL(menu));
1381 }
1382 
1383 
1384 void
ghb_presets_menu_clear(signal_user_data_t * ud)1385 ghb_presets_menu_clear(signal_user_data_t *ud)
1386 {
1387     GtkMenuButton * mb;
1388     GMenuModel    * mm;
1389 
1390     mb = GTK_MENU_BUTTON(GHB_WIDGET(ud->builder, "presets_menu_button"));
1391     mm = gtk_menu_button_get_menu_model(mb);
1392     gtk_menu_button_set_menu_model(mb, NULL);
1393     g_object_unref(G_OBJECT(mm));
1394 }
1395 
1396 void
ghb_presets_menu_reinit(signal_user_data_t * ud)1397 ghb_presets_menu_reinit(signal_user_data_t *ud)
1398 {
1399     ghb_presets_menu_clear(ud);
1400     ghb_presets_menu_init(ud);
1401 }
1402 
1403 void
ghb_presets_list_init(signal_user_data_t * ud,const hb_preset_index_t * path)1404 ghb_presets_list_init(signal_user_data_t *ud, const hb_preset_index_t *path)
1405 {
1406     hb_preset_index_t *next_path;
1407     GhbValue          *folder;
1408     GtkTreeView       *treeview;
1409     GtkTreeStore      *store;
1410     GtkTreePath       *parent_path;
1411     GtkTreeIter        iter, parent_iter, *piter;
1412     gint               count, ii;
1413 
1414     if (path == NULL)
1415     {
1416         hb_preset_index_t *p = hb_preset_index_init(NULL, 0);
1417         ghb_presets_list_init(ud, p);
1418         free(p);
1419         return;
1420     }
1421     next_path = hb_preset_index_dup(path);
1422     folder    = hb_presets_get_folder_children(path);
1423     if (folder == NULL)
1424     {
1425         g_warning(_("Failed to find parent folder when adding child."));
1426         g_free(next_path);
1427         return;
1428     }
1429     treeview    = GTK_TREE_VIEW(GHB_WIDGET(ud->builder, "presets_list"));
1430     store       = GTK_TREE_STORE(gtk_tree_view_get_model(treeview));
1431     parent_path = ghb_tree_path_new_from_index(path);
1432     if (parent_path != NULL)
1433     {
1434         gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &parent_iter,
1435                                 parent_path);
1436         piter = &parent_iter;
1437         gtk_tree_path_free(parent_path);
1438     }
1439     else
1440     {
1441         piter = NULL;
1442     }
1443     count = ghb_array_len(folder);
1444     next_path->depth++;
1445     for (ii = 0; ii < count; ii++)
1446     {
1447         GhbValue    *dict;
1448         const gchar *name;
1449         const gchar *color;
1450         gint         type;
1451         const gchar *description;
1452         gboolean     is_folder;
1453         gboolean     def;
1454 
1455         next_path->index[next_path->depth-1] = ii;
1456 
1457         // Additional settings, add row
1458         dict        = ghb_array_get(folder, ii);
1459         name        = ghb_dict_get_string(dict, "PresetName");
1460         description = ghb_dict_get_string(dict, "PresetDescription");
1461         type        = ghb_dict_get_int(dict, "Type");
1462         is_folder   = ghb_dict_get_bool(dict, "Folder");
1463         def         = ghb_dict_get_bool(dict, "Default");
1464         color       = get_preset_color(type, is_folder);
1465 
1466         gtk_tree_store_append(store, &iter, piter);
1467         gtk_tree_store_set(store, &iter,
1468                             0, name,
1469                             1, def ? 800 : 400,
1470                             2, def ? 2   : 0,
1471                             3, color,
1472                             4, description,
1473                             5, type == HB_PRESET_TYPE_OFFICIAL ? 0 : 1,
1474                             -1);
1475         if (is_folder)
1476         {
1477             ghb_presets_list_init(ud, next_path);
1478             if (ghb_dict_get_bool(dict, "FolderOpen"))
1479             {
1480                 GtkTreePath *path;
1481                 path = gtk_tree_model_get_path(GTK_TREE_MODEL(store), &iter);
1482                 gtk_tree_view_expand_to_path(treeview, path);
1483                 gtk_tree_path_free(path);
1484             }
1485         }
1486     }
1487     g_free(next_path);
1488     if (path == NULL)
1489     {
1490         ghb_presets_list_show_default(ud);
1491     }
1492 }
1493 
1494 static void
presets_list_clear(signal_user_data_t * ud)1495 presets_list_clear(signal_user_data_t *ud)
1496 {
1497     GtkTreeView  *treeview;
1498     GtkTreeModel *store;
1499     GtkTreeIter   iter;
1500     gboolean      valid;
1501 
1502     treeview = GTK_TREE_VIEW(GHB_WIDGET(ud->builder, "presets_list"));
1503     store    = gtk_tree_view_get_model(treeview);
1504     valid    = gtk_tree_model_get_iter_first(store, &iter);
1505     while (valid)
1506     {
1507         gtk_tree_store_remove(GTK_TREE_STORE(store), &iter);
1508         valid = gtk_tree_model_get_iter_first(store, &iter);
1509     }
1510 }
1511 
1512 void
ghb_presets_list_reinit(signal_user_data_t * ud)1513 ghb_presets_list_reinit(signal_user_data_t *ud)
1514 {
1515     presets_list_clear(ud);
1516     ghb_presets_list_init(ud, NULL);
1517 }
1518 
1519 static void
presets_list_update_item(signal_user_data_t * ud,const hb_preset_index_t * path,gboolean recurse)1520 presets_list_update_item(
1521     signal_user_data_t       *ud,
1522     const hb_preset_index_t  *path,
1523     gboolean                  recurse)
1524 {
1525     GhbValue     *dict;
1526     GtkTreeView  *treeview;
1527     GtkTreeStore *store;
1528     GtkTreePath  *treepath;
1529     GtkTreeIter   iter;
1530     const gchar  *name;
1531     const gchar  *description;
1532     gint          type;
1533     gboolean      is_folder;
1534     gboolean      def;
1535     const gchar  *color;
1536 
1537     dict = hb_preset_get(path);
1538     if (dict == NULL)
1539         return;
1540 
1541     treeview = GTK_TREE_VIEW(GHB_WIDGET(ud->builder, "presets_list"));
1542     store    = GTK_TREE_STORE(gtk_tree_view_get_model(treeview));
1543     treepath = ghb_tree_path_new_from_index(path);
1544     gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, treepath);
1545 
1546     // Additional settings, add row
1547     name        = ghb_dict_get_string(dict, "PresetName");
1548     description = ghb_dict_get_string(dict, "PresetDescription");
1549     type        = ghb_dict_get_int(dict, "Type");
1550     is_folder   = ghb_dict_get_bool(dict, "Folder");
1551     def         = ghb_dict_get_bool(dict, "Default");
1552     color       = get_preset_color(type, is_folder);
1553 
1554     gtk_tree_store_set(store, &iter,
1555                         0, name,
1556                         1, def ? 800 : 400,
1557                         2, def ? 2   : 0,
1558                         3, color,
1559                         4, description,
1560                         5, type == HB_PRESET_TYPE_OFFICIAL ? 0 : 1,
1561                         -1);
1562     if (recurse && is_folder)
1563     {
1564         ghb_presets_list_init(ud, path);
1565     }
1566 }
1567 
1568 static void
presets_list_append(signal_user_data_t * ud,const hb_preset_index_t * path)1569 presets_list_append(signal_user_data_t *ud, const hb_preset_index_t *path)
1570 {
1571     hb_preset_index_t *folder_path;
1572     hb_value_t        *dict;
1573     GtkTreeView       *treeview;
1574     GtkTreeStore      *store;
1575     GtkTreePath       *folder_treepath;
1576     GtkTreeIter        iter, parent_iter, *piter;
1577     const gchar       *name;
1578     const gchar       *description;
1579     gint               type;
1580     gboolean           is_folder;
1581     gboolean           def;
1582     const gchar       *color;
1583 
1584     folder_path = hb_preset_index_dup(path);
1585     folder_path->depth--;
1586 
1587     dict = hb_preset_get(path);
1588     if (dict == NULL)
1589     {
1590         g_message("Ack! Desync between presets and preset list");
1591         return;
1592     }
1593 
1594     treeview = GTK_TREE_VIEW(GHB_WIDGET(ud->builder, "presets_list"));
1595     store    = GTK_TREE_STORE(gtk_tree_view_get_model(treeview));
1596 
1597     folder_treepath = ghb_tree_path_new_from_index(folder_path);
1598     if (folder_treepath != NULL)
1599     {
1600         gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &parent_iter,
1601                                 folder_treepath);
1602         piter = &parent_iter;
1603         gtk_tree_path_free(folder_treepath);
1604     }
1605     else
1606     {
1607         piter = NULL;
1608     }
1609 
1610     // Additional settings, add row
1611     name        = ghb_dict_get_string(dict, "PresetName");
1612     description = ghb_dict_get_string(dict, "PresetDescription");
1613     type        = ghb_dict_get_int(dict, "Type");
1614     is_folder   = ghb_dict_get_bool(dict, "Folder");
1615     def         = ghb_dict_get_bool(dict, "Default");
1616     color       = get_preset_color(type, is_folder);
1617 
1618     gtk_tree_store_append(store, &iter, piter);
1619     gtk_tree_store_set(store, &iter,
1620                         0, name,
1621                         1, def ? 800 : 400,
1622                         2, def ? 2   : 0,
1623                         3, color,
1624                         4, description,
1625                         5, type == HB_PRESET_TYPE_OFFICIAL ? 0 : 1,
1626                         -1);
1627     if (is_folder)
1628     {
1629         ghb_presets_list_init(ud, path);
1630     }
1631 }
1632 
1633 static void
presets_list_remove(signal_user_data_t * ud,hb_preset_index_t * path)1634 presets_list_remove(signal_user_data_t *ud, hb_preset_index_t *path)
1635 {
1636     GtkTreeView  *treeview;
1637     GtkTreePath  *treepath;
1638     GtkTreeIter   iter;
1639     GtkTreeStore *store;
1640 
1641     treeview = GTK_TREE_VIEW(GHB_WIDGET(ud->builder, "presets_list"));
1642     store    = GTK_TREE_STORE(gtk_tree_view_get_model(treeview));
1643     treepath = ghb_tree_path_new_from_index(path);
1644     if (treepath)
1645     {
1646         if (gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, treepath))
1647             gtk_tree_store_remove(store, &iter);
1648         gtk_tree_path_free(treepath);
1649     }
1650 }
1651 
1652 void
ghb_save_queue(GhbValue * queue)1653 ghb_save_queue(GhbValue *queue)
1654 {
1655     pid_t  pid;
1656     char  *name;
1657 
1658     pid  = getpid();
1659     name = g_strdup_printf ("queue.%d", pid);
1660     write_config_file(name, queue);
1661     g_free(name);
1662 }
1663 
1664 GhbValue*
ghb_load_old_queue(int pid)1665 ghb_load_old_queue(int pid)
1666 {
1667     GhbValue *queue;
1668     char     *name;
1669 
1670     name  = g_strdup_printf ("queue.%d", pid);
1671     queue = read_config_file(name);
1672     g_free(name);
1673     return queue;
1674 }
1675 
1676 void
ghb_remove_old_queue_file(int pid)1677 ghb_remove_old_queue_file(int pid)
1678 {
1679     char *name;
1680 
1681     name = g_strdup_printf ("queue.%d", pid);
1682     remove_config_file(name);
1683     g_free(name);
1684 }
1685 
ghb_create_copy_mask(GhbValue * settings)1686 GhbValue* ghb_create_copy_mask(GhbValue *settings)
1687 {
1688     GhbValue *copy_mask = ghb_array_new();
1689     if (ghb_dict_get_bool(settings, "AudioAllowMP2Pass"))
1690     {
1691         ghb_array_append(copy_mask, ghb_string_value_new("copy:mp2"));
1692     }
1693     if (ghb_dict_get_bool(settings, "AudioAllowMP3Pass"))
1694     {
1695         ghb_array_append(copy_mask, ghb_string_value_new("copy:mp3"));
1696     }
1697     if (ghb_dict_get_bool(settings, "AudioAllowAACPass"))
1698     {
1699         ghb_array_append(copy_mask, ghb_string_value_new("copy:aac"));
1700     }
1701     if (ghb_dict_get_bool(settings, "AudioAllowAC3Pass"))
1702     {
1703         ghb_array_append(copy_mask, ghb_string_value_new("copy:ac3"));
1704     }
1705     if (ghb_dict_get_bool(settings, "AudioAllowDTSPass"))
1706     {
1707         ghb_array_append(copy_mask, ghb_string_value_new("copy:dts"));
1708     }
1709     if (ghb_dict_get_bool(settings, "AudioAllowDTSHDPass"))
1710     {
1711         ghb_array_append(copy_mask, ghb_string_value_new("copy:dtshd"));
1712     }
1713     if (ghb_dict_get_bool(settings, "AudioAllowEAC3Pass"))
1714     {
1715         ghb_array_append(copy_mask, ghb_string_value_new("copy:eac3"));
1716     }
1717     if (ghb_dict_get_bool(settings, "AudioAllowFLACPass"))
1718     {
1719         ghb_array_append(copy_mask, ghb_string_value_new("copy:flac"));
1720     }
1721     if (ghb_dict_get_bool(settings, "AudioAllowTRUEHDPass"))
1722     {
1723         ghb_array_append(copy_mask, ghb_string_value_new("copy:truehd"));
1724     }
1725     return copy_mask;
1726 }
1727 
1728 // Translate internal values to preset key, value pairs
1729 GhbValue*
ghb_settings_to_preset(GhbValue * settings)1730 ghb_settings_to_preset(GhbValue *settings)
1731 {
1732     GhbValue *preset = ghb_value_dup(settings);
1733 
1734     gboolean br, constant;
1735 
1736     ghb_dict_remove(preset, "title");
1737     ghb_dict_set_bool(preset, "Default", 0);
1738 
1739     br = ghb_dict_get_bool(preset, "vquality_type_bitrate");
1740     constant = ghb_dict_get_bool(preset, "vquality_type_constant");
1741 
1742     const char * angle    = ghb_dict_get_string(preset, "rotate");
1743     int          hflip    = ghb_dict_get_int(preset, "hflip");
1744     char       * rot_flip = g_strdup_printf("angle=%s:hflip=%d", angle, hflip);
1745     ghb_dict_set_string(preset, "PictureRotate", rot_flip);
1746     g_free(rot_flip);
1747 
1748     const gchar * crop_mode;
1749 
1750     crop_mode = ghb_dict_get_string(settings, "crop_mode");
1751     ghb_dict_set_bool(preset, "PictureAutoCrop", !strcmp(crop_mode, "auto"));
1752 
1753     // VideoQualityType/0/1/2 - vquality_type_/target/bitrate/constant
1754     // *note: target is no longer used
1755     if (br)
1756     {
1757         ghb_dict_set_int(preset, "VideoQualityType", 1);
1758     }
1759     else if (constant)
1760     {
1761         ghb_dict_set_int(preset, "VideoQualityType", 2);
1762     }
1763 
1764     if (ghb_dict_get_bool(preset, "VideoFramerateCFR"))
1765     {
1766         ghb_dict_set_string(preset, "VideoFramerateMode", "cfr");
1767     }
1768     else if (ghb_dict_get_bool(preset, "VideoFrameratePFR"))
1769     {
1770         ghb_dict_set_string(preset, "VideoFramerateMode", "pfr");
1771     }
1772     else
1773     {
1774         ghb_dict_set_string(preset, "VideoFramerateMode", "vfr");
1775     }
1776 
1777     GhbValue *alist, *adict;
1778     gint count, ii;
1779 
1780     alist = ghb_dict_get(preset, "AudioList");
1781     count = ghb_array_len(alist);
1782     for (ii = 0; ii < count; ii++)
1783     {
1784         gdouble drc;
1785 
1786         adict = ghb_array_get(alist, ii);
1787         drc = ghb_dict_get_double(adict, "AudioTrackDRCSlider");
1788         if (drc < 1.0)
1789         {
1790             ghb_dict_set_double(adict, "AudioTrackDRCSlider", 0.0);
1791         }
1792     }
1793 
1794     GhbValue *copy_mask = ghb_create_copy_mask(preset);
1795     ghb_dict_set(preset, "AudioCopyMask", copy_mask);
1796 
1797     GString *str = g_string_new("");
1798     const char *sep = "";
1799     const char *tune = ghb_dict_get_string(preset, "VideoTune");
1800     if (tune != NULL && strcasecmp(tune, "none"))
1801     {
1802         g_string_append_printf(str, "%s", tune);
1803         sep = ",";
1804     }
1805     int encoder = ghb_get_video_encoder(settings);
1806     if (encoder & HB_VCODEC_X264_MASK)
1807     {
1808         if (ghb_dict_get_bool(preset, "x264FastDecode"))
1809         {
1810             g_string_append_printf(str, "%s%s", sep, "fastdecode");
1811             sep = ",";
1812         }
1813         if (ghb_dict_get_bool(preset, "x264ZeroLatency"))
1814         {
1815             g_string_append_printf(str, "%s%s", sep, "zerolatency");
1816         }
1817     }
1818     char *tunes;
1819     tunes = g_string_free(str, FALSE);
1820     ghb_dict_set_string(preset, "VideoTune", tunes);
1821     g_free(tunes);
1822 
1823     GhbValue *in_val, *out_val;
1824 
1825     // Convert PictureModulus to correct data type
1826     in_val = ghb_dict_get(preset, "PictureModulus");
1827     out_val = ghb_value_xform(in_val, GHB_INT);
1828     if (out_val != NULL)
1829         ghb_dict_set(preset, "PictureModulus", out_val);
1830 
1831     return preset;
1832 }
1833 
1834 static guint prefs_timeout_id = 0;
1835 
1836 static gboolean
delayed_store_prefs(gpointer data)1837 delayed_store_prefs(gpointer data)
1838 {
1839     write_config_file("preferences.json", prefsDict);
1840     prefs_timeout_id = 0;
1841     return FALSE;
1842 }
1843 
1844 static void
store_presets()1845 store_presets()
1846 {
1847     gchar      *config, *path;
1848     hb_value_t *presets;
1849 
1850     config  = ghb_get_user_config_dir(NULL);
1851     path    = g_strdup_printf ("%s/%s", config, "presets.json");
1852     presets = hb_presets_get();
1853     hb_presets_write_json(presets, path);
1854     g_free(config);
1855     g_free(path);
1856 }
1857 
1858 static void
store_prefs(void)1859 store_prefs(void)
1860 {
1861     if (prefs_timeout_id != 0)
1862     {
1863         GMainContext *mc;
1864         GSource *source;
1865 
1866         mc = g_main_context_default();
1867         source = g_main_context_find_source_by_id(mc, prefs_timeout_id);
1868         if (source != NULL)
1869             g_source_destroy(source);
1870     }
1871     prefs_timeout_id = g_timeout_add_seconds(1, (GSourceFunc)delayed_store_prefs, NULL);
1872 }
1873 
1874 void
ghb_presets_load(signal_user_data_t * ud)1875 ghb_presets_load(signal_user_data_t *ud)
1876 {
1877     int result = presets_add_config_file("presets.json");
1878     if (result == -2)
1879     {
1880         // The above can fail if the presets file was written by a
1881         // more recent version of HandBrake than is currently running.
1882         // Look for a backup version that matches the currently running
1883         // version.
1884         GtkWindow *hb_window = GTK_WINDOW(GHB_WIDGET(ud->builder, "hb_window"));
1885         gchar *message = g_strdup_printf(
1886             _("Presets found are newer than what is supported by this version of HandBrake!\n\n"
1887               "Would you like to continue?"));
1888         if (!ghb_message_dialog(hb_window, GTK_MESSAGE_WARNING, message,
1889             _("Get me out of here!"), _("Load backup presets")))
1890         {
1891             g_free(message);
1892             exit(1);
1893         }
1894         g_free(message);
1895 
1896         gchar *name;
1897         int major, minor, micro;
1898 
1899         hb_presets_current_version(&major, &minor, &micro);
1900         name = g_strdup_printf("presets.%d.%d.%d.json", major, minor, micro);
1901         ghb_log("Failed to read presets file, trying backup (%s)...", name);
1902         if (presets_add_config_file(name) < 0)
1903         {
1904             ghb_log("Failed to read backup presets, using defaults...");
1905             hb_presets_builtin_update();
1906             // Don't store defaults unless the user explicitly saves
1907             // a new preset.  This would overwrite the presets file
1908             // that was generated by a newer version of HandBrake.
1909         }
1910         g_free(name);
1911     }
1912     else if (result < 0)
1913     {
1914         if (presets_add_config_file("presets") < 0)
1915         {
1916             ghb_log("Failed to read presets file, initializing new presets...");
1917             hb_presets_builtin_update();
1918             store_presets();
1919         }
1920     }
1921     ghb_update_ui_combo_box(ud, "PresetCategory", NULL, FALSE);
1922 }
1923 
1924 static void
settings_save(signal_user_data_t * ud,const char * category,const char * name,const char * desc,gboolean set_def)1925 settings_save(signal_user_data_t *ud, const char * category,
1926               const char *name, const char * desc, gboolean set_def)
1927 {
1928     GhbValue          * preset, * new_preset;
1929     gboolean            def = FALSE;
1930     hb_preset_index_t * folder_path, * path;
1931     char              * fullname;
1932 
1933     folder_path = hb_preset_search_index(category, 0, HB_PRESET_TYPE_CUSTOM);
1934     if (folder_path->depth <= 0)
1935     {
1936         GhbValue * new_folder;
1937         new_folder = ghb_dict_new();
1938         ghb_dict_set_string(new_folder, "PresetName", category);
1939         ghb_dict_set(new_folder, "ChildrenArray", ghb_array_new());
1940         ghb_dict_set_int(new_folder, "Type", HB_PRESET_TYPE_CUSTOM);
1941         ghb_dict_set_bool(new_folder, "Folder", TRUE);
1942         int index = hb_preset_append(folder_path, new_folder);
1943         if (index >= 0)
1944         {
1945             folder_path->index[folder_path->depth++] = index;
1946             presets_list_append(ud, folder_path);
1947         }
1948         else
1949         {
1950             ghb_log("Failed to create category (%s)...", category);
1951             return;
1952         }
1953         ghb_value_free(&new_folder);
1954     }
1955 
1956     new_preset = ghb_settings_to_preset(ud->settings);
1957     ghb_dict_set_int(new_preset, "Type", HB_PRESET_TYPE_CUSTOM);
1958     ghb_dict_set_string(new_preset, "PresetName", name);
1959     ghb_dict_set_string(new_preset, "PresetDescription", desc);
1960 
1961     fullname = g_strdup_printf("/%s/%s", category, name);
1962     path = hb_preset_search_index(fullname, 0, HB_PRESET_TYPE_CUSTOM);
1963     preset = hb_preset_get(path);
1964     if (preset != NULL)
1965     {
1966         // Replacing an existing preset
1967         def = ghb_dict_get_bool(preset, "Default");
1968 
1969         // If we are replacing the default preset, re-mark it as default
1970         ghb_dict_set_bool(new_preset, "Default", def);
1971         // Already exists, update its description
1972         if (hb_preset_set(path, new_preset) >= 0)
1973         {
1974             presets_list_update_item(ud, path, FALSE);
1975         }
1976     }
1977     else
1978     {
1979         // Check if the new preset is also the new default preset
1980         if (set_def)
1981         {
1982             ghb_dict_set_bool(new_preset, "Default", set_def);
1983             ghb_presets_list_clear_default(ud);
1984             hb_presets_clear_default();
1985         }
1986 
1987         // Adding a new preset
1988         // Append to the folder
1989         int index = hb_preset_append(folder_path, new_preset);
1990         if (index >= 0)
1991         {
1992             folder_path->index[folder_path->depth++] = index;
1993             presets_list_append(ud, folder_path);
1994         }
1995         *path = *folder_path;
1996     }
1997 
1998 
1999     free(fullname);
2000     ghb_value_free(&new_preset);
2001 
2002     store_presets();
2003     ghb_presets_menu_reinit(ud);
2004 
2005     ud->dont_clear_presets = TRUE;
2006     // Make the new preset the selected item
2007     select_preset2(ud, path);
2008     ud->dont_clear_presets = FALSE;
2009 
2010     free(folder_path);
2011     free(path);
2012 
2013     return;
2014 }
2015 
2016 G_MODULE_EXPORT void
preset_import_action_cb(GSimpleAction * action,GVariant * param,signal_user_data_t * ud)2017 preset_import_action_cb(GSimpleAction *action, GVariant *param,
2018                         signal_user_data_t *ud)
2019 {
2020     GtkWindow       *hb_window;
2021     GtkWidget       *dialog;
2022     GtkResponseType  response;
2023     const gchar     *exportDir;
2024     gchar           *filename;
2025     GtkFileFilter   *filter;
2026 
2027     hb_window = GTK_WINDOW(GHB_WIDGET(ud->builder, "hb_window"));
2028     dialog = gtk_file_chooser_dialog_new("Import Preset", hb_window,
2029                 GTK_FILE_CHOOSER_ACTION_OPEN,
2030                 GHB_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2031                 GHB_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
2032                 NULL);
2033 
2034     filter = gtk_file_filter_new();
2035     gtk_file_filter_set_name(filter, _("All (*)"));
2036     gtk_file_filter_add_pattern(filter, "*");
2037     gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
2038 
2039     filter = gtk_file_filter_new();
2040     gtk_file_filter_set_name(filter, _("Presets (*.json)"));
2041     gtk_file_filter_add_pattern(filter, "*.json");
2042     gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
2043     gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
2044 
2045     filter = gtk_file_filter_new();
2046     gtk_file_filter_set_name(filter, _("Legacy Presets (*.plist)"));
2047     gtk_file_filter_add_pattern(filter, "*.plist");
2048     gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
2049 
2050     exportDir = ghb_dict_get_string(ud->prefs, "ExportDirectory");
2051     if (exportDir == NULL || exportDir[0] == '\0')
2052     {
2053         exportDir = ".";
2054     }
2055     gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), exportDir);
2056 
2057     response = gtk_dialog_run(GTK_DIALOG(dialog));
2058     gtk_widget_hide(dialog);
2059     if (response == GTK_RESPONSE_ACCEPT)
2060     {
2061         gchar    *dir;
2062         int       index;
2063 
2064         filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
2065         if (!g_file_test(filename, G_FILE_TEST_IS_REGULAR))
2066         {
2067             gtk_widget_destroy(dialog);
2068             g_free(filename);
2069             return;
2070         }
2071         // import the preset
2072         index = hb_presets_add_path(filename);
2073 
2074         exportDir = ghb_dict_get_string(ud->prefs, "ExportDirectory");
2075         dir = g_path_get_dirname(filename);
2076         if (strcmp(dir, exportDir) != 0)
2077         {
2078             ghb_dict_set_string(ud->prefs, "ExportDirectory", dir);
2079             ghb_pref_save(ud->prefs, "ExportDirectory");
2080         }
2081         g_free(filename);
2082         g_free(dir);
2083         store_presets();
2084 
2085         // Re-init the UI preset list
2086         ghb_presets_list_reinit(ud);
2087         ghb_presets_menu_reinit(ud);
2088         if (index < 0)
2089         {
2090             ghb_select_default_preset(ud);
2091         }
2092         else
2093         {
2094             hb_preset_index_t path;
2095             path.index[0] = index;
2096             path.depth = 1;
2097             select_preset2(ud, &path);
2098         }
2099     }
2100     gtk_widget_destroy(dialog);
2101 }
2102 
2103 G_MODULE_EXPORT void
preset_export_action_cb(GSimpleAction * action,GVariant * param,signal_user_data_t * ud)2104 preset_export_action_cb(GSimpleAction *action, GVariant *param,
2105                         signal_user_data_t *ud)
2106 {
2107     hb_preset_index_t *path;
2108     GtkWindow         *hb_window;
2109     GtkWidget         *dialog;
2110     GtkResponseType    response;
2111     const gchar       *exportDir;
2112     gchar             *filename;
2113     GhbValue          *dict;
2114     char              *preset_name;
2115 
2116     path = get_selected_path(ud);
2117     if (path == NULL || path->depth <= 0)
2118     {
2119         const gchar       *name;
2120         char * new_name;
2121 
2122         free(path);
2123         dict = ghb_settings_to_preset(ud->settings);
2124         name = ghb_dict_get_string(dict, "PresetName");
2125         new_name = g_strdup_printf("%s (modified)", name);
2126         ghb_dict_set_string(dict, "PresetName", new_name);
2127         free(new_name);
2128     }
2129     else
2130     {
2131         dict = hb_value_dup(hb_preset_get(path));
2132         free(path);
2133     }
2134 
2135     if (dict == NULL)
2136     {
2137         return;
2138     }
2139     preset_name = g_strdup(ghb_dict_get_string(dict, "PresetName"));
2140 
2141     hb_window = GTK_WINDOW(GHB_WIDGET(ud->builder, "hb_window"));
2142     dialog = gtk_file_chooser_dialog_new(_("Export Preset"), hb_window,
2143                 GTK_FILE_CHOOSER_ACTION_SAVE,
2144                 GHB_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
2145                 GHB_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
2146                 NULL);
2147 
2148     exportDir = ghb_dict_get_string(ud->prefs, "ExportDirectory");
2149     if (exportDir == NULL || exportDir[0] == '\0')
2150     {
2151         exportDir = ".";
2152     }
2153 
2154     // Clean up preset name for use as a filename.  Removing leading
2155     // and trailing whitespace and filename illegal characters.
2156     g_strstrip(preset_name);
2157     g_strdelimit(preset_name, GHB_UNSAFE_FILENAME_CHARS, '_');
2158     filename = g_strdup_printf("%s.json", preset_name);
2159     gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), exportDir);
2160     gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), filename);
2161     g_free(filename);
2162     g_free(preset_name);
2163 
2164     response = gtk_dialog_run(GTK_DIALOG(dialog));
2165     gtk_widget_hide(dialog);
2166     if (response == GTK_RESPONSE_ACCEPT)
2167     {
2168         gchar    *dir;
2169 
2170         filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
2171 
2172         // export the preset
2173         hb_presets_write_json(dict, filename);
2174         exportDir = ghb_dict_get_string(ud->prefs, "ExportDirectory");
2175         dir = g_path_get_dirname(filename);
2176         if (strcmp(dir, exportDir) != 0)
2177         {
2178             ghb_dict_set_string(ud->prefs, "ExportDirectory", dir);
2179             ghb_pref_save(ud->prefs, "ExportDirectory");
2180         }
2181         g_free(dir);
2182         g_free(filename);
2183     }
2184     gtk_widget_destroy(dialog);
2185     hb_value_free(&dict);
2186 }
2187 
2188 G_MODULE_EXPORT void
preset_rename_action_cb(GSimpleAction * action,GVariant * param,signal_user_data_t * ud)2189 preset_rename_action_cb(GSimpleAction *action, GVariant *param,
2190                         signal_user_data_t *ud)
2191 {
2192     const gchar       * name;
2193     const gchar       * fullname;
2194     int                 type;
2195     hb_preset_index_t * path;
2196     GtkWidget         * dialog;
2197     GtkEntry          * entry;
2198     GtkTextView       * tv;
2199     GhbValue          * dict;
2200     GtkResponseType     response;
2201 
2202     name      = ghb_dict_get_string(ud->settings, "PresetName");
2203     type      = ghb_dict_get_int(ud->settings, "Type");
2204     fullname  = ghb_dict_get_string(ud->settings, "PresetFullName");
2205 
2206     if (type != HB_PRESET_TYPE_CUSTOM)
2207     {
2208         // Only allow renaming custom presets
2209         return;
2210     }
2211     path = hb_preset_search_index(fullname, 0, type);
2212 
2213     ghb_ui_update(ud, "PresetReDescription",
2214                   ghb_dict_get_value(ud->settings, "PresetDescription"));
2215     tv = GTK_TEXT_VIEW(GHB_WIDGET(ud->builder, "PresetReDescription"));
2216 
2217     dialog   = GHB_WIDGET(ud->builder, "preset_rename_dialog");
2218     entry    = GTK_ENTRY(GHB_WIDGET(ud->builder, "PresetReName"));
2219     ghb_editable_set_text(entry, name);
2220 
2221     response = gtk_dialog_run(GTK_DIALOG(dialog));
2222     gtk_widget_hide(dialog);
2223     if (response == GTK_RESPONSE_OK)
2224     {
2225         GtkTextBuffer * buffer;
2226         GtkTextIter     start, end;
2227         char          * desc;
2228 
2229         // save the new name
2230         name = ghb_editable_get_text(entry);
2231         dict = hb_preset_get(path);
2232         if (dict != NULL)
2233         {
2234             ghb_dict_set_string(dict, "PresetName", name);
2235             store_presets();
2236         }
2237 
2238         buffer = gtk_text_view_get_buffer(tv);
2239         gtk_text_buffer_get_bounds(buffer, &start, &end);
2240         desc = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
2241         ghb_dict_set_string(ud->settings, "PresetDescription", desc);
2242         free(desc);
2243 
2244         char * full = preset_get_fullname(path, "/", FALSE);
2245         ghb_dict_set_string(ud->settings, "PresetFullName", full);
2246         ghb_dict_set_string(ud->settings, "PresetName", name);
2247         free(full);
2248         ghb_presets_menu_reinit(ud);
2249         set_preset_menu_button_label(ud, path);
2250     }
2251 }
2252 
preset_save_action(signal_user_data_t * ud,gboolean as)2253 static void preset_save_action(signal_user_data_t *ud, gboolean as)
2254 {
2255     const char        * category = NULL;
2256     const gchar       * name;
2257     const gchar       * fullname;
2258     int                 type;
2259     hb_preset_index_t * path;
2260     GtkWidget         * dialog;
2261     GtkWidget         * widget;
2262     GtkEntry          * entry;
2263     GtkTextView       * tv;
2264     GhbValue          * dict;
2265     GtkResponseType     response;
2266 
2267     name      = ghb_dict_get_string(ud->settings, "PresetName");
2268     type      = ghb_dict_get_int(ud->settings, "Type");
2269     fullname  = ghb_dict_get_string(ud->settings, "PresetFullName");
2270 
2271     ghb_ui_update(ud, "PresetSetDefault", ghb_boolean_value(FALSE));
2272 
2273     path = hb_preset_search_index(fullname, 0, type);
2274 
2275     // Find an appropriate default category
2276     if (path != NULL)
2277     {
2278         path->depth = 1;
2279         dict        = hb_preset_get(path);
2280         if (ghb_dict_get_bool(dict, "Folder"))
2281         {
2282             category = ghb_dict_get_string(dict, "PresetName");
2283         }
2284         free(path);
2285     }
2286     if (category == NULL)
2287     {
2288         // Find first custom folder
2289         hb_value_t * presets;
2290         int          ii, count;
2291 
2292         presets = hb_presets_get();
2293         count   = hb_value_array_len(presets);
2294         for (ii = 0; ii < count; ii++)
2295         {
2296             dict = hb_value_array_get(presets, ii);
2297             if (!ghb_dict_get_bool(dict, "Folder"))
2298             {
2299                 continue;
2300             }
2301             if (ghb_dict_get_int(dict, "Type") == HB_PRESET_TYPE_CUSTOM)
2302             {
2303                 category = ghb_dict_get_string(dict, "PresetName");
2304                 break;
2305             }
2306         }
2307     }
2308     if (category == NULL)
2309     {
2310         // Force creation of new category
2311         category = "new";
2312     }
2313     ghb_ui_update(ud, "PresetCategory", ghb_string_value(category));
2314     tv = GTK_TEXT_VIEW(GHB_WIDGET(ud->builder, "PresetDescription"));
2315 
2316     dialog   = GHB_WIDGET(ud->builder, "preset_save_dialog");
2317     entry    = GTK_ENTRY(GHB_WIDGET(ud->builder, "PresetName"));
2318     ghb_editable_set_text(entry, name);
2319 
2320     widget = GHB_WIDGET(ud->builder, "PresetName");
2321     gtk_widget_set_sensitive(widget, as);
2322     widget = GHB_WIDGET(ud->builder, "PresetCategory");
2323     gtk_widget_set_sensitive(widget, as);
2324     widget = GHB_WIDGET(ud->builder, "PresetSetDefault");
2325     gtk_widget_set_visible(widget, as);
2326 
2327     response = gtk_dialog_run(GTK_DIALOG(dialog));
2328     gtk_widget_hide(dialog);
2329     if (response == GTK_RESPONSE_OK)
2330     {
2331         GtkTextBuffer * buffer;
2332         GtkTextIter     start, end;
2333         char          * desc;
2334         gboolean        def;
2335 
2336         // save the preset
2337         name = ghb_editable_get_text(entry);
2338         category = ghb_dict_get_string(ud->settings, "PresetCategory");
2339         if (!strcmp(category, "new"))
2340         {
2341             entry = GTK_ENTRY(GHB_WIDGET(ud->builder, "PresetCategoryName"));
2342             category = ghb_editable_get_text(entry);
2343         }
2344         if (category == NULL || category[0] == 0)
2345         {
2346             ghb_log("Invalid empty category.");
2347             return;
2348         }
2349         buffer = gtk_text_view_get_buffer(tv);
2350         gtk_text_buffer_get_bounds(buffer, &start, &end);
2351         desc = gtk_text_buffer_get_text(buffer, &start, &end, FALSE);
2352         def = ghb_dict_get_bool(ud->settings, "PresetSetDefault");
2353         settings_save(ud, category, name, desc, def);
2354         free(desc);
2355     }
2356 }
2357 
2358 G_MODULE_EXPORT void
preset_save_action_cb(GSimpleAction * action,GVariant * param,signal_user_data_t * ud)2359 preset_save_action_cb(GSimpleAction *action, GVariant *param,
2360                       signal_user_data_t *ud)
2361 {
2362     preset_save_action(ud, FALSE);
2363 }
2364 
2365 G_MODULE_EXPORT void
preset_save_as_action_cb(GSimpleAction * action,GVariant * param,signal_user_data_t * ud)2366 preset_save_as_action_cb(GSimpleAction *action, GVariant *param,
2367                          signal_user_data_t *ud)
2368 {
2369     preset_save_action(ud, TRUE);
2370 }
2371 
2372 static void
preset_save_set_ok_sensitive(signal_user_data_t * ud)2373 preset_save_set_ok_sensitive(signal_user_data_t *ud)
2374 {
2375     GtkEntry   * entry;
2376     GtkWidget  * ok_button;
2377     const char * name;
2378     const char * category;
2379     const char * category_name;
2380     gboolean     sensitive;
2381 
2382     ok_button = GHB_WIDGET(ud->builder, "preset_ok");
2383 
2384     category = ghb_dict_get_string(ud->settings, "PresetCategory");
2385     entry = GTK_ENTRY(GHB_WIDGET(ud->builder, "PresetName"));
2386     name = ghb_editable_get_text(entry);
2387     entry = GTK_ENTRY(GHB_WIDGET(ud->builder, "PresetCategoryName"));
2388     category_name = ghb_editable_get_text(entry);
2389 
2390     sensitive = name[0] && (strcmp(category, "new") || category_name[0]);
2391     gtk_widget_set_sensitive(ok_button, sensitive);
2392 }
2393 
2394 G_MODULE_EXPORT void
preset_category_changed_cb(GtkWidget * widget,signal_user_data_t * ud)2395 preset_category_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
2396 {
2397     ghb_widget_to_setting(ud->settings, widget);
2398     preset_save_set_ok_sensitive(ud);
2399     ghb_check_dependency(ud, widget, NULL);
2400 }
2401 
2402 G_MODULE_EXPORT void
preset_name_changed_cb(GtkWidget * widget,signal_user_data_t * ud)2403 preset_name_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
2404 {
2405     preset_save_set_ok_sensitive(ud);
2406 }
2407 
2408 G_MODULE_EXPORT void
presets_reload_action_cb(GSimpleAction * action,GVariant * param,signal_user_data_t * ud)2409 presets_reload_action_cb(GSimpleAction *action, GVariant *param,
2410                          signal_user_data_t *ud)
2411 {
2412     // Reload the builtin presets
2413     hb_presets_builtin_update();
2414     store_presets();
2415 
2416     ghb_presets_list_reinit(ud);
2417     ghb_presets_menu_reinit(ud);
2418     ghb_select_default_preset(ud);
2419 }
2420 
2421 G_MODULE_EXPORT void
preset_remove_action_cb(GSimpleAction * action,GVariant * param,signal_user_data_t * ud)2422 preset_remove_action_cb(GSimpleAction *action, GVariant *param,
2423                         signal_user_data_t *ud)
2424 {
2425     hb_preset_index_t * path;
2426     GhbValue          * preset;
2427 
2428     path = get_selected_path(ud);
2429     if (path != NULL)
2430     {
2431         preset = hb_preset_get(path);
2432     }
2433     if (path == NULL || preset == NULL)
2434     {
2435         const char * fullname;
2436         int          type;
2437 
2438         fullname = ghb_dict_get_string(ud->settings, "PresetFullName");
2439         if (fullname == NULL)
2440         {
2441             return;
2442         }
2443         type     = ghb_dict_get_int(ud->settings, "Type");
2444         path = hb_preset_search_index(fullname, 0, type);
2445         if (path != NULL)
2446         {
2447             preset = hb_preset_get(path);
2448         }
2449     }
2450     if (path == NULL || preset == NULL)
2451     {
2452         return;
2453     }
2454 
2455     GtkWindow       * hb_window;
2456     GtkWidget       * dialog;
2457     gboolean          is_folder;
2458     GtkResponseType   response;
2459     const char      * name;
2460 
2461     name  = ghb_dict_get_string(preset, "PresetName");
2462     is_folder = preset_is_folder(path);
2463     hb_window = GTK_WINDOW(GHB_WIDGET(ud->builder, "hb_window"));
2464     dialog = gtk_message_dialog_new(hb_window, GTK_DIALOG_MODAL,
2465                         GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO,
2466                         _("Confirm deletion of %s:\n\n%s"),
2467                         is_folder ? _("folder") : _("preset"),
2468                         name);
2469     response = gtk_dialog_run(GTK_DIALOG(dialog));
2470     gtk_widget_destroy(dialog);
2471     if (response == GTK_RESPONSE_YES)
2472     {
2473         int depth = path->depth;
2474 
2475         // Determine which preset to highlight after deletion done
2476         hb_preset_index_t new_path = *path;
2477         // Always select a preset, not a folder
2478         if (depth == 1)
2479         {
2480             new_path.depth = 2;
2481         }
2482         // Try next
2483         new_path.index[depth - 1] = path->index[depth - 1] + 1;
2484         preset = hb_preset_get(&new_path);
2485         // After deletion, index of new selected item is one less
2486         new_path.index[depth - 1]--;
2487         if (preset == NULL && path->index[depth - 1] > 0)
2488         {
2489             // Try previous
2490             new_path.index[depth - 1] = path->index[depth - 1] - 1;
2491             preset = hb_preset_get(&new_path);
2492         }
2493         if (preset == NULL)
2494         {
2495             // perhaps we are deleting the last item in a folder
2496             // Try first item in next and previous folders
2497             depth = 1;
2498             new_path.index[1] = 0;
2499             // Try next
2500             new_path.index[depth - 1] = path->index[depth - 1] + 1;
2501             preset = hb_preset_get(&new_path);
2502             // After deletion, index of new selected item is one less
2503             new_path.index[depth - 1]--;
2504             if (preset == NULL && path->index[depth - 1] > 0)
2505             {
2506                 // Try previous
2507                 new_path.index[depth - 1] = path->index[depth - 1] - 1;
2508                 preset = hb_preset_get(&new_path);
2509             }
2510         }
2511 
2512         GtkTreeView      * treeview;
2513         GtkTreeSelection * selection;
2514 
2515         // unselect item to be deleted
2516         treeview  = GTK_TREE_VIEW(GHB_WIDGET(ud->builder, "presets_list"));
2517         selection = gtk_tree_view_get_selection(treeview);
2518         gtk_tree_selection_unselect_all(selection);
2519 
2520         // Remove the item
2521         if (hb_preset_delete(path) >= 0)
2522         {
2523             presets_list_remove(ud, path);
2524             if (path->depth == 2)
2525             {
2526                 // If deleting this item resulted in an empty folder
2527                 // delete the folder
2528                 int count = 0;
2529                 hb_value_t * folder;
2530 
2531                 path->depth = 1;
2532                 folder = hb_presets_get_folder_children(path);
2533                 if (folder != NULL)
2534                 {
2535                     count = ghb_array_len(folder);
2536                 }
2537                 if (count == 0)
2538                 {
2539                     // delete the folder
2540                     hb_preset_delete(path);
2541                     presets_list_remove(ud, path);
2542                 }
2543             }
2544             store_presets();
2545             ghb_presets_menu_reinit(ud);
2546         }
2547         if (preset != NULL)
2548         {
2549             select_preset2(ud, &new_path);
2550         }
2551     }
2552     free(path);
2553     ghb_update_ui_combo_box(ud, "PresetCategory", NULL, FALSE);
2554 }
2555 
2556 // controls where valid drop locations are
2557 G_MODULE_EXPORT gboolean
2558 #if GTK_CHECK_VERSION(3, 90, 0)
presets_drag_motion_cb(GtkTreeView * tv,GdkDrop * ctx,gint x,gint y,signal_user_data_t * ud)2559 presets_drag_motion_cb(
2560     GtkTreeView        *tv,
2561     GdkDrop            *ctx,
2562     gint                x,
2563     gint                y,
2564     signal_user_data_t *ud)
2565 #else
2566 presets_drag_motion_cb(
2567     GtkTreeView        *tv,
2568     GdkDragContext     *ctx,
2569     gint                x,
2570     gint                y,
2571     guint               time,
2572     signal_user_data_t *ud)
2573 #endif
2574 {
2575     GtkTreeViewDropPosition  drop_pos;
2576     GtkTreeIter              iter;
2577     GtkTreeView             *srctv;
2578     GtkTreeModel            *model;
2579     GtkTreeSelection        *select;
2580     GtkTreePath             *treepath;
2581     hb_preset_index_t       *path;
2582     gint                     src_ptype, dst_ptype;
2583     gboolean                 src_folder, dst_folder;
2584     GhbValue                *src_preset, *dst_preset;
2585     GtkWidget               *widget;
2586 #if GTK_CHECK_VERSION(3, 90, 0)
2587     // Dummy time for backwards compatibility
2588     guint                    time = 0;
2589 #endif
2590 
2591     treepath = g_object_get_data(G_OBJECT(tv), "dst-tree-path");
2592     if (treepath != NULL)
2593     {
2594         gtk_tree_path_free(treepath);
2595     }
2596     g_object_set_data(G_OBJECT(tv), "dst-tree-path", NULL);
2597 
2598 #if GTK_CHECK_VERSION(3, 90, 0)
2599     GdkDrag *drag_ctx = gdk_drop_get_drag(ctx);
2600     widget = gtk_drag_get_source_widget(drag_ctx);
2601 #else
2602     widget = gtk_drag_get_source_widget(ctx);
2603 #endif
2604     if (widget == NULL || widget != GTK_WIDGET(tv))
2605         return TRUE;
2606 
2607     // Get the type of the object being dragged
2608     srctv  = GTK_TREE_VIEW(widget);
2609     select = gtk_tree_view_get_selection(srctv);
2610     gtk_tree_selection_get_selected(select, &model, &iter);
2611     path   = ghb_tree_get_index(model, &iter);
2612 
2613     src_preset = hb_preset_get(path);
2614     free(path);
2615     if (src_preset == NULL)
2616     {
2617         ghb_drag_status(ctx, 0, time);
2618         return TRUE;
2619     }
2620 
2621     src_ptype  = ghb_dict_get_int(src_preset, "Type");
2622     src_folder = ghb_dict_get_bool(src_preset, "Folder");
2623 
2624     // The rest checks that the destination is a valid position
2625     // in the list.
2626     gtk_tree_view_get_dest_row_at_pos(tv, x, y, &treepath, &drop_pos);
2627     if (treepath == NULL)
2628     {
2629         ghb_drag_status(ctx, 0, time);
2630         return TRUE;
2631     }
2632     // Don't allow repositioning of builtin presets
2633     if (src_ptype != HB_PRESET_TYPE_CUSTOM)
2634     {
2635         gtk_tree_view_set_drag_dest_row(tv, NULL, drop_pos);
2636         ghb_drag_status(ctx, 0, time);
2637         gtk_tree_path_free(treepath);
2638         return TRUE;
2639     }
2640 
2641     path = ghb_tree_path_get_index(treepath);
2642     dst_preset = hb_preset_get(path);
2643     free(path);
2644     if (dst_preset == NULL)
2645     {
2646         ghb_drag_status(ctx, 0, time);
2647         gtk_tree_path_free(treepath);
2648         return TRUE;
2649     }
2650 
2651     dst_ptype = ghb_dict_get_int(dst_preset, "Type");
2652     dst_folder = ghb_dict_get_bool(dst_preset, "Folder");
2653 
2654     // Don't allow mixing custom presets in the builtins
2655     if (dst_ptype != HB_PRESET_TYPE_CUSTOM)
2656     {
2657         gtk_tree_view_set_drag_dest_row(tv, NULL, drop_pos);
2658         ghb_drag_status(ctx, 0, time);
2659         gtk_tree_path_free(treepath);
2660         return TRUE;
2661     }
2662 
2663     // Don't allow dropping source folder before/after a non-folder
2664     if (src_folder && !dst_folder)
2665     {
2666         gtk_tree_path_up(treepath);
2667         path = ghb_tree_path_get_index(treepath);
2668         dst_preset = hb_preset_get(path);
2669         free(path);
2670         if (dst_preset == NULL)
2671         {
2672             ghb_drag_status(ctx, 0, time);
2673             gtk_tree_path_free(treepath);
2674             return TRUE;
2675         }
2676 
2677         dst_ptype = ghb_dict_get_int(dst_preset, "Type");
2678         dst_folder = ghb_dict_get_bool(dst_preset, "Folder");
2679         drop_pos = GTK_TREE_VIEW_DROP_AFTER;
2680     }
2681 
2682     // Don't allow dropping a source folder into another folder
2683     if ((src_folder || !dst_folder) &&
2684         drop_pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)
2685     {
2686         drop_pos = GTK_TREE_VIEW_DROP_BEFORE;
2687     }
2688     if ((src_folder || !dst_folder) &&
2689         drop_pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER)
2690     {
2691         drop_pos = GTK_TREE_VIEW_DROP_AFTER;
2692     }
2693 
2694     // Don't allow dropping a non-folder before a folder
2695     if (!src_folder && dst_folder && drop_pos == GTK_TREE_VIEW_DROP_BEFORE)
2696     {
2697         gtk_tree_view_set_drag_dest_row(tv, NULL, drop_pos);
2698         ghb_drag_status(ctx, 0, time);
2699         gtk_tree_path_free(treepath);
2700         return TRUE;
2701     }
2702 
2703     if (!src_folder && dst_folder && drop_pos == GTK_TREE_VIEW_DROP_AFTER)
2704     {
2705         drop_pos = GTK_TREE_VIEW_DROP_INTO_OR_AFTER;
2706     }
2707 
2708     gtk_tree_view_set_drag_dest_row(tv, treepath, drop_pos);
2709     ghb_drag_status(ctx, GDK_ACTION_MOVE, time);
2710 
2711     g_object_set_data(G_OBJECT(tv), "dst-tree-path", treepath);
2712     g_object_set_data(G_OBJECT(tv), "dst-drop-pos", (gpointer)drop_pos);
2713 
2714     return TRUE;
2715 }
2716 
2717 G_MODULE_EXPORT void
2718 #if GTK_CHECK_VERSION(3, 90, 0)
presets_drag_data_received_cb(GtkTreeView * tv,GdkDrop * dc,GtkSelectionData * selection_data,signal_user_data_t * ud)2719 presets_drag_data_received_cb(
2720     GtkTreeView        *tv,
2721     GdkDrop            *dc,
2722     GtkSelectionData   *selection_data,
2723     signal_user_data_t *ud)
2724 #else
2725 presets_drag_data_received_cb(
2726     GtkTreeView        *tv,
2727     GdkDragContext     *dc,
2728     gint                x,
2729     gint                y,
2730     GtkSelectionData   *selection_data,
2731     guint               info,
2732     guint               t,
2733     signal_user_data_t *ud)
2734 #endif
2735 {
2736     GtkTreeModel            *dst_model;
2737     GtkTreePath             *dst_treepath = NULL;
2738     GtkTreeViewDropPosition  drop_pos;
2739     GtkTreeIter              dst_iter, src_iter;
2740     gint                     src_ptype;
2741     gboolean                 src_folder, dst_folder;
2742 
2743     dst_model    = gtk_tree_view_get_model(tv);
2744     dst_treepath = g_object_get_data(G_OBJECT(tv), "dst-tree-path");
2745     drop_pos     = (GtkTreeViewDropPosition)g_object_get_data(G_OBJECT(tv),
2746                                                               "dst-drop-pos");
2747     g_object_set_data(G_OBJECT(tv), "dst-tree-path", NULL);
2748     if (dst_treepath == NULL)
2749     {
2750         return;
2751     }
2752 
2753     GtkTreeView       *src_widget;
2754     GtkTreeModel      *src_model;
2755     GtkTreeSelection  *select;
2756     hb_preset_index_t *dst_path, *src_path;
2757 
2758 #if GTK_CHECK_VERSION(3, 90, 0)
2759     GdkDrag *drag_ctx = gdk_drop_get_drag(dc);
2760     src_widget = GTK_TREE_VIEW(gtk_drag_get_source_widget(drag_ctx));
2761 #else
2762     src_widget = GTK_TREE_VIEW(gtk_drag_get_source_widget(dc));
2763 #endif
2764     select     = gtk_tree_view_get_selection (src_widget);
2765     gtk_tree_selection_get_selected(select, &src_model, &src_iter);
2766 
2767     src_path   = ghb_tree_get_index(src_model, &src_iter);
2768     src_ptype  = preset_get_type(src_path);
2769     src_folder = preset_is_folder(src_path);
2770 
2771     // Don't allow repositioning of builtin presets
2772     if (src_ptype != HB_PRESET_TYPE_CUSTOM)
2773     {
2774         free(src_path);
2775         gtk_tree_path_free(dst_treepath);
2776         return;
2777     }
2778 
2779     dst_path = ghb_tree_path_get_index(dst_treepath);
2780     dst_folder = preset_is_folder(dst_path);
2781 
2782     // Don't allow allow dropping source folders after/before non-folders
2783     if (src_folder && !dst_folder)
2784     {
2785         gtk_tree_path_up(dst_treepath);
2786         dst_path = ghb_tree_path_get_index(dst_treepath);
2787         dst_folder = preset_is_folder(dst_path);
2788 
2789         drop_pos = GTK_TREE_VIEW_DROP_AFTER;
2790     }
2791 
2792     // Don't allow dropping folders into other folders, only before/after
2793     if (src_folder && dst_folder)
2794     {
2795         if (drop_pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)
2796             drop_pos = GTK_TREE_VIEW_DROP_BEFORE;
2797         else if (drop_pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER)
2798             drop_pos = GTK_TREE_VIEW_DROP_AFTER;
2799     }
2800 
2801     if (!src_folder && dst_folder)
2802     {
2803         // Don't allow dropping preset before a folder
2804         if (drop_pos == GTK_TREE_VIEW_DROP_BEFORE)
2805         {
2806             free(src_path);
2807             free(dst_path);
2808             gtk_tree_path_free(dst_treepath);
2809             return;
2810         }
2811         // Don't allow dropping preset after a folder, only into
2812         if (drop_pos == GTK_TREE_VIEW_DROP_AFTER)
2813             drop_pos = GTK_TREE_VIEW_DROP_INTO_OR_AFTER;
2814     }
2815 
2816     // Don't allow dropping into non-folder items
2817     if (!dst_folder)
2818     {
2819         if (drop_pos == GTK_TREE_VIEW_DROP_INTO_OR_BEFORE)
2820             drop_pos = GTK_TREE_VIEW_DROP_BEFORE;
2821         else if (drop_pos == GTK_TREE_VIEW_DROP_INTO_OR_AFTER)
2822             drop_pos = GTK_TREE_VIEW_DROP_AFTER;
2823     }
2824     free(dst_path);
2825     if (gtk_tree_model_get_iter(dst_model, &dst_iter, dst_treepath))
2826     {
2827         GtkTreeIter iter;
2828 
2829         // Insert new empty row in UI preset list
2830         // This logic determines the final position of the preset,
2831         // i.e. before, after or inside the target entry.
2832         // So the dst_path to move the preset to must be computed
2833         // after moving the entry in the UI list
2834         switch (drop_pos)
2835         {
2836             case GTK_TREE_VIEW_DROP_BEFORE:
2837                 gtk_tree_store_insert_before(GTK_TREE_STORE(dst_model),
2838                                             &iter, NULL, &dst_iter);
2839                 break;
2840 
2841             case GTK_TREE_VIEW_DROP_INTO_OR_BEFORE:
2842                 gtk_tree_store_insert(GTK_TREE_STORE(dst_model),
2843                                             &iter, &dst_iter, 0);
2844                 break;
2845 
2846             case GTK_TREE_VIEW_DROP_AFTER:
2847                 gtk_tree_store_insert_after(GTK_TREE_STORE(dst_model),
2848                                             &iter, NULL, &dst_iter);
2849                 break;
2850 
2851             case GTK_TREE_VIEW_DROP_INTO_OR_AFTER:
2852                 gtk_tree_store_insert_after(GTK_TREE_STORE(dst_model),
2853                                             &iter, &dst_iter, NULL);
2854                 break;
2855 
2856             default:
2857                 break;
2858         }
2859 
2860         // Move source preset at the desired location
2861         dst_path = ghb_tree_get_index(dst_model, &iter);
2862         hb_preset_move(src_path, dst_path);
2863         free(dst_path);
2864 
2865         // Remove the old entry in the UI list
2866         gtk_tree_store_remove(GTK_TREE_STORE(src_model), &src_iter);
2867 
2868         // UI elements were shuffled again.  recompute dst_path
2869         dst_path = ghb_tree_get_index(dst_model, &iter);
2870         presets_list_update_item(ud, dst_path, TRUE);
2871         select_preset2(ud, dst_path);
2872         free(dst_path);
2873 
2874         store_presets();
2875         ghb_presets_menu_reinit(ud);
2876     }
2877     gtk_tree_path_free(dst_treepath);
2878     free(src_path);
2879 }
2880 
2881 void
presets_row_expanded_cb(GtkTreeView * treeview,GtkTreeIter * iter,GtkTreePath * treepath,signal_user_data_t * ud)2882 presets_row_expanded_cb(
2883     GtkTreeView        *treeview,
2884     GtkTreeIter        *iter,
2885     GtkTreePath        *treepath,
2886     signal_user_data_t *ud)
2887 {
2888     hb_preset_index_t *path;
2889     gboolean           expanded;
2890     GhbValue          *dict;
2891 
2892     expanded = gtk_tree_view_row_expanded(treeview, treepath);
2893     path     = ghb_tree_path_get_index(treepath);
2894     dict     = hb_preset_get(path);
2895     free(path);
2896 
2897     // Sanity check
2898     if (!ghb_dict_get_bool(dict, "Folder"))
2899     {
2900         g_warning("presets_row_expand_cb: Desync between presets and list");
2901         return;
2902     }
2903     if (ghb_dict_get_bool(dict, "FolderOpen"))
2904     {
2905         if (expanded)
2906         {
2907             return;
2908         }
2909     }
2910     else if (!expanded)
2911     {
2912         return;
2913     }
2914     ghb_dict_set_bool(dict, "FolderOpen", expanded);
2915     store_presets();
2916 }
2917 
2918 // Makes a copy of the preset and assigns "PresetFullName" which
2919 // is use to look up the preset in the preset list should the need
2920 // arrise, e.g. saving changes to the preset.
2921 GhbValue*
ghb_get_current_preset(signal_user_data_t * ud)2922 ghb_get_current_preset(signal_user_data_t *ud)
2923 {
2924     GtkTreeView      *tv;
2925     GtkTreeModel     *tm;
2926     GtkTreeSelection *ts;
2927     GtkTreeIter       ti;
2928     GhbValue         *preset = NULL;
2929 
2930     tv = GTK_TREE_VIEW(GHB_WIDGET(ud->builder, "presets_list"));
2931     ts = gtk_tree_view_get_selection(tv);
2932     if (gtk_tree_selection_get_selected(ts, &tm, &ti))
2933     {
2934         hb_preset_index_t *path;
2935 
2936         path   = ghb_tree_get_index(tm, &ti);
2937         preset = hb_preset_get(path);
2938         if (preset != NULL)
2939         {
2940             char *fullname;
2941 
2942             preset   = hb_value_dup(preset);
2943             fullname = preset_get_fullname(path, "/", FALSE);
2944             ghb_dict_set_string(preset, "PresetFullName", fullname);
2945             free(fullname);
2946         }
2947         free(path);
2948     }
2949     return preset;
2950 }
2951 
2952 G_MODULE_EXPORT void
presets_list_selection_changed_cb(GtkTreeSelection * selection,signal_user_data_t * ud)2953 presets_list_selection_changed_cb(GtkTreeSelection *selection, signal_user_data_t *ud)
2954 {
2955     hb_preset_index_t * path;
2956 
2957     path   = get_selected_path(ud);
2958     if (path != NULL)
2959     {
2960         GhbValue *dict = hb_preset_get(path);
2961         if (!ghb_dict_get_bool(dict, "Folder") &&
2962             !ghb_dict_get_bool(ud->settings, "preset_reload"))
2963         {
2964             ghb_preset_to_settings(ud->settings, dict);
2965             char *fullname = preset_get_fullname(path, "/", FALSE);
2966             ghb_dict_set_string(ud->settings, "PresetFullName", fullname);
2967             free(fullname);
2968             ghb_set_current_title_settings(ud);
2969             ghb_load_post_settings(ud);
2970         }
2971         if (!ghb_dict_get_bool(dict, "Folder"))
2972         {
2973             GSimpleAction * action;
2974             GtkWidget     * widget;
2975 
2976             set_preset_menu_button_label(ud, path);
2977             action = G_SIMPLE_ACTION(g_action_map_lookup_action(
2978                                      G_ACTION_MAP(ud->app), "preset-reload"));
2979             g_simple_action_set_enabled(action, FALSE);
2980             widget = GHB_WIDGET(ud->builder, "preset_selection_modified_label");
2981             gtk_widget_set_visible(widget, FALSE);
2982             widget = GHB_WIDGET(ud->builder, "preset_selection_reload");
2983             gtk_widget_set_visible(widget, FALSE);
2984             widget = GHB_WIDGET(ud->builder, "preset_save_new");
2985             gtk_widget_set_visible(widget, FALSE);
2986         }
2987         int type = preset_get_type(path);
2988         GSimpleAction * action;
2989 
2990         action = G_SIMPLE_ACTION(g_action_map_lookup_action(
2991                                  G_ACTION_MAP(ud->app), "preset-rename"));
2992         g_simple_action_set_enabled(action, type == HB_PRESET_TYPE_CUSTOM);
2993         action = G_SIMPLE_ACTION(g_action_map_lookup_action(
2994                                  G_ACTION_MAP(ud->app), "preset-save"));
2995         g_simple_action_set_enabled(action, type == HB_PRESET_TYPE_CUSTOM);
2996         free(path);
2997     }
2998 }
2999 
3000 void
ghb_clear_presets_selection(signal_user_data_t * ud)3001 ghb_clear_presets_selection(signal_user_data_t *ud)
3002 {
3003     GtkTreeView      * treeview;
3004     GtkTreeSelection * selection;
3005     GSimpleAction    * action;
3006     GtkWidget        * widget;
3007 
3008     if (ud->dont_clear_presets) return;
3009     treeview  = GTK_TREE_VIEW(GHB_WIDGET(ud->builder, "presets_list"));
3010     selection = gtk_tree_view_get_selection(treeview);
3011     gtk_tree_selection_unselect_all(selection);
3012     ghb_dict_set_bool(ud->settings, "preset_modified", TRUE);
3013 
3014 
3015     action = G_SIMPLE_ACTION(g_action_map_lookup_action(G_ACTION_MAP(ud->app),
3016                                                         "preset-reload"));
3017     g_simple_action_set_enabled(action, TRUE);
3018     widget = GHB_WIDGET(ud->builder, "preset_selection_modified_label");
3019     gtk_widget_set_visible(widget, TRUE);
3020     widget = GHB_WIDGET(ud->builder, "preset_selection_reload");
3021     gtk_widget_set_visible(widget, TRUE);
3022     widget = GHB_WIDGET(ud->builder, "preset_save_new");
3023     gtk_widget_set_visible(widget, TRUE);
3024 }
3025 
3026 G_MODULE_EXPORT void
preset_default_action_cb(GSimpleAction * action,GVariant * param,signal_user_data_t * ud)3027 preset_default_action_cb(GSimpleAction *action, GVariant *param,
3028                          signal_user_data_t *ud)
3029 {
3030     hb_preset_index_t *path = get_selected_path(ud);
3031     if (path != NULL)
3032     {
3033         hb_value_t *dict = hb_preset_get(path);
3034         if (dict != NULL && !ghb_dict_get_bool(dict, "Folder"))
3035         {
3036             ghb_presets_list_clear_default(ud);
3037             hb_presets_clear_default();
3038             ghb_dict_set_bool(dict, "Default", 1);
3039             ghb_presets_list_show_default(ud);
3040             store_presets();
3041         }
3042         g_free(path);
3043     }
3044 }
3045 
3046 G_MODULE_EXPORT void
preset_edited_cb(GtkCellRendererText * cell,gchar * treepath_s,gchar * text,signal_user_data_t * ud)3047 preset_edited_cb(
3048     GtkCellRendererText *cell,
3049     gchar               *treepath_s,
3050     gchar               *text,
3051     signal_user_data_t  *ud)
3052 {
3053     GtkTreePath       *treepath;
3054     GtkTreeStore      *store;
3055     GtkTreeView       *treeview;
3056     GtkTreeIter        iter;
3057     GhbValue          *dict;
3058     hb_preset_index_t *path;
3059 
3060     treeview = GTK_TREE_VIEW(GHB_WIDGET(ud->builder, "presets_list"));
3061     store    = GTK_TREE_STORE(gtk_tree_view_get_model(treeview));
3062     treepath = gtk_tree_path_new_from_string(treepath_s);
3063     path     = ghb_tree_path_get_index(treepath);
3064     if (path != NULL)
3065     {
3066         dict = hb_preset_get(path);
3067         if (dict != NULL)
3068         {
3069             gtk_tree_model_get_iter(GTK_TREE_MODEL(store), &iter, treepath);
3070             gtk_tree_store_set(store, &iter, 0, text, -1);
3071 
3072             ghb_dict_set_string(dict, "PresetName", text);
3073             store_presets();
3074         }
3075     }
3076     gtk_tree_path_free(treepath);
3077     free(path);
3078 }
3079 
3080 G_MODULE_EXPORT void
preset_widget_changed_cb(GtkWidget * widget,signal_user_data_t * ud)3081 preset_widget_changed_cb(GtkWidget *widget, signal_user_data_t *ud)
3082 {
3083     ghb_widget_to_setting(ud->settings, widget);
3084     ghb_check_dependency(ud, widget, NULL);
3085 }
3086 
3087