1 /*
2 * Copyright (C) 2019-2021 Alexandros Theodotou <alex at zrythm dot org>
3 *
4 * This file is part of Zrythm
5 *
6 * Zrythm is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU Affero General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * Zrythm is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU Affero General Public License for more details.
15 *
16 * You should have received a copy of the GNU Affero General Public License
17 * along with Zrythm. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20 #include "zrythm-config.h"
21
22 #include <locale.h>
23
24 #include "audio/engine.h"
25 #include "gui/widgets/main_window.h"
26 #include "gui/widgets/active_hardware_mb.h"
27 #include "gui/widgets/preferences.h"
28 #include "plugins/plugin_gtk.h"
29 #include "project.h"
30 #include "settings/settings.h"
31 #include "utils/flags.h"
32 #include "utils/localization.h"
33 #include "utils/gtk.h"
34 #include "utils/objects.h"
35 #include "utils/resources.h"
36 #include "utils/string.h"
37 #include "utils/ui.h"
38 #include "zrythm.h"
39 #include "zrythm_app.h"
40
41 #include <gtk/gtk.h>
42
43 #include <glib/gi18n.h>
44
45 G_DEFINE_TYPE (
46 PreferencesWidget,
47 preferences_widget,
48 GTK_TYPE_DIALOG)
49
50 typedef struct CallbackData
51 {
52 PreferencesWidget * preferences_widget;
53 SubgroupInfo * info;
54 char * key;
55 } CallbackData;
56
57 #define KEY_IS(a,b,c) \
58 (string_is_equal (group, _(a)) && \
59 string_is_equal (subgroup, _(b)) && \
60 string_is_equal (key, c))
61
62 static void
on_simple_string_entry_changed(GtkEditable * editable,CallbackData * data)63 on_simple_string_entry_changed (
64 GtkEditable * editable,
65 CallbackData * data)
66 {
67 char * str =
68 gtk_editable_get_chars (editable, 0, -1);
69 g_return_if_fail (str);
70 g_settings_set_string (
71 data->info->settings, data->key, str);
72 g_free (str);
73 }
74
75 static void
on_path_entry_changed(GtkEditable * editable,CallbackData * data)76 on_path_entry_changed (
77 GtkEditable * editable,
78 CallbackData * data)
79 {
80 char * str =
81 gtk_editable_get_chars (editable, 0, -1);
82 g_return_if_fail (str);
83 char ** split_str =
84 g_strsplit (str, G_SEARCHPATH_SEPARATOR_S, 0);
85 g_settings_set_strv (
86 data->info->settings, data->key,
87 (const char * const *) split_str);
88 g_strfreev (split_str);
89 g_free (str);
90 }
91
92 static void
on_enum_combo_box_active_changed(GtkComboBox * combo,CallbackData * data)93 on_enum_combo_box_active_changed (
94 GtkComboBox * combo,
95 CallbackData * data)
96 {
97 g_settings_set_enum (
98 data->info->settings, data->key,
99 gtk_combo_box_get_active (combo));
100 }
101
102 static void
on_string_combo_box_active_changed(GtkComboBoxText * combo,CallbackData * data)103 on_string_combo_box_active_changed (
104 GtkComboBoxText * combo,
105 CallbackData * data)
106 {
107 g_settings_set_string (
108 data->info->settings, data->key,
109 gtk_combo_box_text_get_active_text (combo));
110 }
111
112 static void
on_backends_combo_box_active_changed(GtkComboBox * combo,CallbackData * data)113 on_backends_combo_box_active_changed (
114 GtkComboBox * combo,
115 CallbackData * data)
116 {
117 g_settings_set_enum (
118 data->info->settings, data->key,
119 atoi (gtk_combo_box_get_active_id (combo)));
120 }
121
122 static void
on_file_set(GtkFileChooserButton * widget,CallbackData * data)123 on_file_set (
124 GtkFileChooserButton * widget,
125 CallbackData * data)
126 {
127 GFile * file =
128 gtk_file_chooser_get_file (
129 GTK_FILE_CHOOSER (widget));
130 char * str =
131 g_file_get_path (file);
132 g_settings_set_string (
133 data->info->settings, data->key, str);
134 g_free (str);
135 g_object_unref (file);
136 }
137
138 static void
font_scale_adjustment_changed(GtkAdjustment * adjustment,void * data)139 font_scale_adjustment_changed (
140 GtkAdjustment * adjustment,
141 void * data)
142 {
143 double factor =
144 gtk_adjustment_get_value (adjustment);
145 zrythm_app_set_font_scale (zrythm_app, factor);
146 }
147
148 static void
on_closure_notify_delete_data(CallbackData * data,GClosure * closure)149 on_closure_notify_delete_data (
150 CallbackData * data,
151 GClosure * closure)
152 {
153 free (data);
154 }
155
156 /** Path type. */
157 typedef enum PathType
158 {
159 /** Not a path. */
160 PATH_TYPE_NONE,
161
162 /** Single entry separated by G_SEARCHPATH_SEPARATOR_S. */
163 PATH_TYPE_ENTRY,
164
165 /** File chooser button. */
166 PATH_TYPE_FILE,
167
168 /** File chooser button for directories. */
169 PATH_TYPE_DIRECTORY,
170 } PathType;
171
172 /**
173 * Returns if the key is a path or not.
174 */
175 static PathType
get_path_type(const char * group,const char * subgroup,const char * key)176 get_path_type (
177 const char * group,
178 const char * subgroup,
179 const char * key)
180 {
181 if (KEY_IS ("General", "Paths", "zrythm-dir"))
182 {
183 return PATH_TYPE_DIRECTORY;
184 }
185 else if (
186 KEY_IS (
187 "Plugins", "Paths",
188 "vst-search-paths-windows") ||
189 KEY_IS (
190 "Plugins", "Paths", "sfz-search-paths") ||
191 KEY_IS (
192 "Plugins", "Paths", "sf2-search-paths"))
193 {
194 return PATH_TYPE_ENTRY;
195 }
196
197 return PATH_TYPE_NONE;
198 }
199
200 static bool
should_be_hidden(const char * group,const char * subgroup,const char * key)201 should_be_hidden (
202 const char * group,
203 const char * subgroup,
204 const char * key)
205 {
206 return
207 #ifndef _WOE32
208 KEY_IS (
209 "Plugins", "Paths",
210 "vst-search-paths-windows") ||
211 #endif
212 #ifndef HAVE_CARLA
213 KEY_IS (
214 "Plugins", "Paths", "sfz-search-paths") ||
215 KEY_IS (
216 "Plugins", "Paths", "sf2-search-paths") ||
217 #endif
218 (AUDIO_ENGINE->audio_backend !=
219 AUDIO_BACKEND_SDL &&
220 KEY_IS (
221 "General", "Engine",
222 "sdl-audio-device-name")) ||
223 (!audio_backend_is_rtaudio (
224 AUDIO_ENGINE->audio_backend) &&
225 KEY_IS (
226 "General", "Engine",
227 "rtaudio-audio-device-name")) ||
228 (AUDIO_ENGINE->audio_backend ==
229 AUDIO_BACKEND_JACK &&
230 KEY_IS (
231 "General", "Engine", "sample-rate")) ||
232 (AUDIO_ENGINE->audio_backend ==
233 AUDIO_BACKEND_JACK &&
234 KEY_IS (
235 "General", "Engine", "buffer-size"));
236 }
237
238 static void
get_range_vals(GVariant * range,GVariant * current_var,const GVariantType * type,double * lower,double * upper,double * current)239 get_range_vals (
240 GVariant * range,
241 GVariant * current_var,
242 const GVariantType * type,
243 double * lower,
244 double * upper,
245 double * current)
246 {
247 GVariant * range_vals =
248 g_variant_get_child_value (range, 1);
249 range_vals =
250 g_variant_get_child_value (range_vals, 0);
251 GVariant * lower_var =
252 g_variant_get_child_value (range_vals, 0);
253 GVariant * upper_var =
254 g_variant_get_child_value (range_vals, 1);
255
256 #define TYPE_EQUALS(type2) \
257 string_is_equal ( \
258 (const char *) type, \
259 (const char *) G_VARIANT_TYPE_##type2)
260
261 if (TYPE_EQUALS (INT32))
262 {
263 *lower =
264 (double) g_variant_get_int32 (lower_var);
265 *upper =
266 (double) g_variant_get_int32 (upper_var);
267 *current =
268 (double)
269 g_variant_get_int32 (current_var);
270 }
271 else if (TYPE_EQUALS (UINT32))
272 {
273 *lower =
274 (double)
275 g_variant_get_uint32 (lower_var);
276 *upper =
277 (double)
278 g_variant_get_uint32 (upper_var);
279 *current =
280 (double)
281 g_variant_get_uint32 (current_var);
282 }
283 else if (TYPE_EQUALS (DOUBLE))
284 {
285 *lower =
286 g_variant_get_double (lower_var);
287 *upper =
288 (double)
289 g_variant_get_double (upper_var);
290 *current =
291 (double)
292 g_variant_get_double (current_var);
293 }
294 #undef TYPE_EQUALS
295
296 g_variant_unref (range_vals);
297 g_variant_unref (lower_var);
298 g_variant_unref (upper_var);
299 }
300
301 static GtkWidget *
make_control(PreferencesWidget * self,int group_idx,int subgroup_idx,const char * key)302 make_control (
303 PreferencesWidget * self,
304 int group_idx,
305 int subgroup_idx,
306 const char * key)
307 {
308 SubgroupInfo * info =
309 &self->subgroup_infos[group_idx][subgroup_idx];
310 const char * group = info->group_name;
311 const char * subgroup = info->name;
312 GSettingsSchemaKey * schema_key =
313 g_settings_schema_get_key (
314 info->schema, key);
315 const GVariantType * type =
316 g_settings_schema_key_get_value_type (
317 schema_key);
318 GVariant * current_var =
319 g_settings_get_value (info->settings, key);
320 GVariant * range =
321 g_settings_schema_key_get_range (
322 schema_key);
323
324 #if 0
325 g_message ("%s",
326 g_variant_get_type_string (current_var));
327 #endif
328
329 #define TYPE_EQUALS(type2) \
330 string_is_equal ( \
331 (const char *) type, \
332 (const char *) G_VARIANT_TYPE_##type2)
333
334 GtkWidget * widget = NULL;
335 if (KEY_IS (
336 "General", "Engine",
337 "rtaudio-audio-device-name") ||
338 KEY_IS (
339 "General", "Engine",
340 "sdl-audio-device-name"))
341 {
342 widget = gtk_combo_box_text_new ();
343 ui_setup_device_name_combo_box (
344 GTK_COMBO_BOX_TEXT (widget));
345 CallbackData * data =
346 object_new (CallbackData);
347 data->info = info;
348 data->preferences_widget = self;
349 data->key = g_strdup (key);
350 g_signal_connect_data (
351 G_OBJECT (widget), "changed",
352 G_CALLBACK (
353 on_string_combo_box_active_changed),
354 data,
355 (GClosureNotify)
356 on_closure_notify_delete_data,
357 G_CONNECT_AFTER);
358 }
359 else if (KEY_IS (
360 "General", "Engine", "midi-backend"))
361 {
362 widget = gtk_combo_box_new ();
363 ui_setup_midi_backends_combo_box (
364 GTK_COMBO_BOX (widget));
365 CallbackData * data =
366 object_new (CallbackData);
367 data->info = info;
368 data->preferences_widget = self;
369 data->key = g_strdup (key);
370 g_signal_connect_data (
371 G_OBJECT (widget), "changed",
372 G_CALLBACK (
373 on_backends_combo_box_active_changed),
374 data,
375 (GClosureNotify)
376 on_closure_notify_delete_data,
377 G_CONNECT_AFTER);
378 }
379 else if (KEY_IS (
380 "General", "Engine", "audio-backend"))
381 {
382 widget = gtk_combo_box_new ();
383 ui_setup_audio_backends_combo_box (
384 GTK_COMBO_BOX (widget));
385 CallbackData * data =
386 object_new (CallbackData);
387 data->info = info;
388 data->preferences_widget = self;
389 data->key = g_strdup (key);
390 g_signal_connect_data (
391 G_OBJECT (widget), "changed",
392 G_CALLBACK (
393 on_backends_combo_box_active_changed),
394 data,
395 (GClosureNotify)
396 on_closure_notify_delete_data,
397 G_CONNECT_AFTER);
398 }
399 else if (KEY_IS (
400 "General", "Engine",
401 "audio-inputs"))
402 {
403 widget =
404 g_object_new (
405 ACTIVE_HARDWARE_MB_WIDGET_TYPE, NULL);
406 active_hardware_mb_widget_setup (
407 Z_ACTIVE_HARDWARE_MB_WIDGET (widget),
408 F_INPUT, F_NOT_MIDI, S_P_GENERAL_ENGINE,
409 "audio-inputs");
410 }
411 else if (KEY_IS (
412 "General", "Engine",
413 "midi-controllers"))
414 {
415 widget =
416 g_object_new (
417 ACTIVE_HARDWARE_MB_WIDGET_TYPE, NULL);
418 active_hardware_mb_widget_setup (
419 Z_ACTIVE_HARDWARE_MB_WIDGET (widget),
420 F_INPUT, F_MIDI, S_P_GENERAL_ENGINE,
421 "midi-controllers");
422 }
423 else if (KEY_IS ("UI", "General", "font-scale"))
424 {
425 double lower = 0.f, upper = 1.f, current = 0.f;
426 get_range_vals (
427 range, current_var, type, &lower, &upper, ¤t);
428 widget =
429 gtk_box_new (
430 GTK_ORIENTATION_HORIZONTAL, 2);
431 gtk_widget_set_visible (widget, true);
432 GtkWidget * scale =
433 gtk_scale_new_with_range (
434 GTK_ORIENTATION_HORIZONTAL, lower, upper,
435 0.1);
436 gtk_widget_set_visible (scale, true);
437 gtk_widget_set_hexpand (scale, true);
438 gtk_scale_add_mark (
439 GTK_SCALE (scale), 1.0, GTK_POS_TOP, NULL);
440 gtk_container_add (
441 GTK_CONTAINER (widget), scale);
442 GtkAdjustment * adj =
443 gtk_range_get_adjustment (
444 GTK_RANGE (scale));
445 gtk_adjustment_set_value (adj, current);
446 g_settings_bind (
447 info->settings, key, adj, "value",
448 G_SETTINGS_BIND_DEFAULT);
449 g_signal_connect (
450 adj, "value-changed",
451 G_CALLBACK (font_scale_adjustment_changed),
452 NULL);
453 }
454 else if (TYPE_EQUALS (BOOLEAN))
455 {
456 widget = gtk_switch_new ();
457 g_settings_bind (
458 info->settings, key, widget, "state",
459 G_SETTINGS_BIND_DEFAULT);
460 }
461 else if (TYPE_EQUALS (INT32) ||
462 TYPE_EQUALS (UINT32) ||
463 TYPE_EQUALS (DOUBLE))
464 {
465 double lower = 0.f, upper = 1.f, current = 0.f;
466 get_range_vals (
467 range, current_var, type, &lower, &upper, ¤t);
468 GtkAdjustment * adj =
469 gtk_adjustment_new (
470 current, lower, upper, 1.0, 1.0, 1.0);
471 widget =
472 gtk_spin_button_new (
473 adj, 1, TYPE_EQUALS (DOUBLE) ? 3 : 0);
474 g_settings_bind (
475 info->settings, key, widget, "value",
476 G_SETTINGS_BIND_DEFAULT);
477 }
478 else if (TYPE_EQUALS (STRING))
479 {
480 PathType path_type =
481 get_path_type (
482 info->group_name, info->name, key);
483 if (path_type == PATH_TYPE_DIRECTORY ||
484 path_type == PATH_TYPE_FILE)
485 {
486 widget =
487 gtk_file_chooser_button_new (
488 path_type == PATH_TYPE_DIRECTORY ?
489 _("Select a folder") :
490 _("Select a file"),
491 path_type == PATH_TYPE_DIRECTORY ?
492 GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER :
493 GTK_FILE_CHOOSER_ACTION_OPEN);
494 char * path =
495 g_settings_get_string (
496 info->settings, key);
497 gtk_file_chooser_set_current_folder (
498 GTK_FILE_CHOOSER (widget),
499 path);
500 g_free (path);
501 CallbackData * data =
502 object_new (CallbackData);
503 data->info = info;
504 data->preferences_widget = self;
505 data->key = g_strdup (key);
506 g_signal_connect_data (
507 G_OBJECT (widget), "file-set",
508 G_CALLBACK (on_file_set),
509 data,
510 (GClosureNotify)
511 on_closure_notify_delete_data,
512 G_CONNECT_AFTER);
513 }
514 else if (path_type == PATH_TYPE_NONE)
515 {
516 /* map enums */
517 const char ** strv = NULL;
518 const cyaml_strval_t * cyaml_strv = NULL;
519 size_t size = 0;
520
521 #define SET_STRV_IF_MATCH(a,b,c,arr_name) \
522 if (KEY_IS (a,b,c)) \
523 { \
524 strv = arr_name; \
525 size = G_N_ELEMENTS (arr_name); \
526 }
527
528 #define SET_STRV_IF_MATCH_W_COUNT(a,b,c,arr_name, \
529 count) \
530 if (KEY_IS (a,b,c)) \
531 { \
532 strv = arr_name; \
533 size = count; \
534 }
535
536 #define SET_STRV_FROM_CYAML_IF_MATCH( \
537 a,b,c,arr_name) \
538 if (KEY_IS (a,b,c)) \
539 { \
540 cyaml_strv = arr_name; \
541 size = G_N_ELEMENTS (arr_name); \
542 }
543
544 SET_STRV_IF_MATCH (
545 "General", "Engine", "audio-backend",
546 audio_backend_str);
547 SET_STRV_IF_MATCH (
548 "General", "Engine", "midi-backend",
549 midi_backend_str);
550 SET_STRV_IF_MATCH (
551 "General", "Engine", "sample-rate",
552 sample_rate_str);
553 SET_STRV_IF_MATCH (
554 "General", "Engine", "buffer-size",
555 buffer_size_str);
556 SET_STRV_FROM_CYAML_IF_MATCH (
557 "Editing", "Audio", "fade-algorithm",
558 curve_algorithm_strings);
559 SET_STRV_FROM_CYAML_IF_MATCH (
560 "Editing", "Automation",
561 "curve-algorithm",
562 curve_algorithm_strings);
563 SET_STRV_IF_MATCH_W_COUNT (
564 "UI", "General", "language",
565 localization_get_language_strings_w_codes (),
566 NUM_LL_LANGUAGES);
567 SET_STRV_IF_MATCH (
568 "UI", "General", "graphic-detail",
569 ui_detail_str);
570 SET_STRV_IF_MATCH (
571 "DSP", "Pan", "pan-algorithm",
572 pan_algorithm_str);
573 SET_STRV_IF_MATCH (
574 "DSP", "Pan", "pan-law",
575 pan_law_str);
576
577 #undef SET_STRV_IF_MATCH
578
579 if (strv || cyaml_strv)
580 {
581 widget =
582 gtk_combo_box_text_new ();
583 for (size_t i = 0; i < size; i++)
584 {
585 if (cyaml_strv)
586 {
587 gtk_combo_box_text_append (
588 GTK_COMBO_BOX_TEXT (widget),
589 cyaml_strv[i].str,
590 _(cyaml_strv[i].str));
591 }
592 else if (strv)
593 {
594 gtk_combo_box_text_append (
595 GTK_COMBO_BOX_TEXT (widget),
596 strv[i], _(strv[i]));
597 }
598 }
599 gtk_combo_box_set_active (
600 GTK_COMBO_BOX (widget),
601 g_settings_get_enum (
602 info->settings, key));
603 CallbackData * data =
604 object_new (CallbackData);
605 data->info = info;
606 data->preferences_widget = self;
607 data->key = g_strdup (key);
608 g_signal_connect_data (
609 G_OBJECT (widget), "changed",
610 G_CALLBACK (
611 on_enum_combo_box_active_changed),
612 data,
613 (GClosureNotify)
614 on_closure_notify_delete_data,
615 G_CONNECT_AFTER);
616 }
617 /* else if not a string array */
618 else
619 {
620 /* create basic string entry control */
621 widget = gtk_entry_new ();
622 char * current_val =
623 g_settings_get_string (
624 info->settings, key);
625 gtk_entry_set_text (
626 GTK_ENTRY (widget), current_val);
627 g_free (current_val);
628 CallbackData * data =
629 object_new (CallbackData);
630 data->info = info;
631 data->preferences_widget = self;
632 data->key = g_strdup (key);
633 g_signal_connect_data (
634 G_OBJECT (widget), "changed",
635 G_CALLBACK (
636 on_simple_string_entry_changed),
637 data,
638 (GClosureNotify)
639 on_closure_notify_delete_data,
640 G_CONNECT_AFTER);
641 }
642 }
643 }
644 else if (TYPE_EQUALS (STRING_ARRAY))
645 {
646 if (get_path_type (
647 info->group_name, info->name, key) ==
648 PATH_TYPE_ENTRY)
649 {
650 widget = gtk_entry_new ();
651 char ** paths =
652 g_settings_get_strv (
653 info->settings, key);
654 char * joined_str =
655 g_strjoinv (G_SEARCHPATH_SEPARATOR_S, paths);
656 gtk_entry_set_text (
657 GTK_ENTRY (widget), joined_str);
658 g_free (joined_str);
659 g_strfreev (paths);
660 CallbackData * data =
661 object_new (CallbackData);
662 data->info = info;
663 data->preferences_widget = self;
664 data->key = g_strdup (key);
665 g_signal_connect_data (
666 G_OBJECT (widget), "changed",
667 G_CALLBACK (on_path_entry_changed),
668 data,
669 (GClosureNotify)
670 on_closure_notify_delete_data,
671 G_CONNECT_AFTER);
672 }
673 }
674 #if 0
675 else if (string_is_equal (
676 g_variant_get_type_string (
677 current_var), "ai"))
678 {
679 }
680 #endif
681
682 #undef TYPE_EQUALS
683
684 g_warn_if_fail (widget);
685
686 return widget;
687 }
688
689 static void
add_subgroup(PreferencesWidget * self,int group_idx,int subgroup_idx,GtkSizeGroup * size_group)690 add_subgroup (
691 PreferencesWidget * self,
692 int group_idx,
693 int subgroup_idx,
694 GtkSizeGroup * size_group)
695 {
696 SubgroupInfo * info =
697 &self->subgroup_infos[group_idx][subgroup_idx];
698
699 const char * localized_subgroup_name =
700 info->name;
701 g_message (
702 "adding subgroup %s (%s)",
703 info->name, localized_subgroup_name);
704
705 /* create a section for the subgroup */
706 GtkWidget * page_box =
707 gtk_notebook_get_nth_page (
708 self->group_notebook, info->group_idx);
709 GtkWidget * label =
710 plugin_gtk_new_label (
711 localized_subgroup_name, true, false,
712 0.f, 0.5f);
713 gtk_widget_set_visible (label, true);
714 gtk_container_add (
715 GTK_CONTAINER (page_box), label);
716
717 char ** keys =
718 g_settings_schema_list_keys (info->schema);
719 int i = 0;
720 int num_controls = 0;
721 char * key;
722 while ((key = keys[i++]))
723 {
724 GSettingsSchemaKey * schema_key =
725 g_settings_schema_get_key (
726 info->schema, key);
727 /* note: this is already translated */
728 const char * summary =
729 g_settings_schema_key_get_summary (
730 schema_key);
731 /* note: this is already translated */
732 const char * description =
733 g_settings_schema_key_get_description (
734 schema_key);
735
736 if (string_is_equal (key, "info") ||
737 should_be_hidden (
738 info->group_name, info->name, key))
739 continue;
740
741 g_message ("adding control for %s", key);
742
743 /* create a box to add controls */
744 GtkWidget * box =
745 gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
746 gtk_widget_set_visible (box, true);
747 gtk_container_add (
748 GTK_CONTAINER (page_box), box);
749
750 /* add label */
751 GtkWidget * lbl =
752 plugin_gtk_new_label (
753 summary, false, false, 1.f, 0.5f);
754 gtk_widget_set_visible (lbl, true);
755 gtk_container_add (
756 GTK_CONTAINER (box), lbl);
757 gtk_size_group_add_widget (
758 size_group, lbl);
759
760 /* add control */
761 GtkWidget * widget =
762 make_control (
763 self, group_idx, subgroup_idx, key);
764 if (widget)
765 {
766 gtk_widget_set_visible (widget, true);
767 if (GTK_IS_SWITCH (widget))
768 {
769 gtk_widget_set_halign (
770 widget, GTK_ALIGN_START);
771 }
772 else
773 {
774 gtk_widget_set_hexpand (widget, true);
775 }
776 gtk_widget_set_tooltip_text (
777 widget, description);
778 gtk_container_add (
779 GTK_CONTAINER (box), widget);
780 num_controls++;
781 }
782 else
783 {
784 g_warning ("no widget for %s", key);
785 }
786 }
787
788 /* Remove label if no controls added */
789 if (num_controls == 0)
790 {
791 gtk_container_remove (
792 GTK_CONTAINER (page_box), label);
793 }
794 }
795
796 static void
add_group(PreferencesWidget * self,int group_idx)797 add_group (
798 PreferencesWidget * self,
799 int group_idx)
800 {
801 GSettingsSchemaSource * source =
802 g_settings_schema_source_get_default ();
803 char ** non_relocatable;
804 g_settings_schema_source_list_schemas (
805 source, 1, &non_relocatable, NULL);
806
807 /* loop once to get the max subgroup index and
808 * group name */
809 char * schema_str;
810 const char * group_name = NULL;
811 const char * subgroup_name = NULL;
812 int i = 0;
813 int max_subgroup_idx = 0;
814 while ((schema_str = non_relocatable[i++]))
815 {
816 if (!string_contains_substr (
817 schema_str,
818 GSETTINGS_ZRYTHM_PREFIX ".preferences"))
819 continue;
820
821 /* get the preferences.x.y schema */
822 GSettingsSchema * schema =
823 g_settings_schema_source_lookup (
824 source, schema_str, 1);
825 g_return_if_fail (schema);
826
827 GSettings * settings =
828 g_settings_new (schema_str);
829 GVariant * info_val =
830 g_settings_get_value (
831 settings, "info");
832 int this_group_idx =
833 (int)
834 g_variant_get_int32 (
835 g_variant_get_child_value (
836 info_val, 0));
837
838 if (this_group_idx != group_idx)
839 continue;
840
841 GSettingsSchemaKey * info_key =
842 g_settings_schema_get_key (
843 schema, "info");
844 /* note: this is already translated */
845 group_name =
846 g_settings_schema_key_get_summary (info_key);
847 /* note: this is already translated */
848 subgroup_name =
849 g_settings_schema_key_get_description (
850 info_key);
851
852 /* get max subgroup index */
853 int subgroup_idx =
854 (int)
855 g_variant_get_int32 (
856 g_variant_get_child_value (
857 info_val, 1));
858 SubgroupInfo * nfo =
859 &self->subgroup_infos[
860 group_idx][subgroup_idx];
861 nfo->schema = schema;
862 nfo->settings = settings;
863 nfo->group_name = group_name;
864 nfo->name = subgroup_name;
865 nfo->group_idx = group_idx;
866 nfo->subgroup_idx = subgroup_idx;
867 if (subgroup_idx > max_subgroup_idx)
868 max_subgroup_idx = subgroup_idx;
869 }
870
871 const char * localized_group_name = group_name;
872 g_message (
873 "adding group %s (%s)",
874 group_name, localized_group_name);
875
876 /* create a page for the group */
877 GtkWidget * box =
878 gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
879 gtk_widget_set_visible (box, true);
880 z_gtk_widget_set_margin (box, 4);
881 gtk_notebook_append_page (
882 self->group_notebook, box,
883 plugin_gtk_new_label (
884 localized_group_name, true, false, 0.f, 0.5f));
885
886 /* create a sizegroup for the labels */
887 GtkSizeGroup * size_group =
888 gtk_size_group_new (
889 GTK_SIZE_GROUP_HORIZONTAL);
890
891 /* add each subgroup */
892 for (int j = 0; j <= max_subgroup_idx; j++)
893 {
894 add_subgroup (self, group_idx, j, size_group);
895 }
896 }
897
898 static void
on_window_closed(GtkWidget * object,PreferencesWidget * self)899 on_window_closed (
900 GtkWidget *object,
901 PreferencesWidget * self)
902 {
903 GtkWidget * dialog =
904 gtk_message_dialog_new_with_markup (
905 GTK_WINDOW (MAIN_WINDOW),
906 GTK_DIALOG_MODAL |
907 GTK_DIALOG_DESTROY_WITH_PARENT,
908 GTK_MESSAGE_INFO,
909 GTK_BUTTONS_OK,
910 _("Some changes will only take "
911 "effect after you restart %s"),
912 PROGRAM_NAME);
913 gtk_window_set_transient_for (
914 GTK_WINDOW (dialog),
915 GTK_WINDOW (self));
916 gtk_dialog_run (GTK_DIALOG (dialog));
917 gtk_widget_destroy (GTK_WIDGET (dialog));
918
919 MAIN_WINDOW->preferences_opened = false;
920 }
921
922 /**
923 * Sets up the preferences widget.
924 */
925 PreferencesWidget *
preferences_widget_new()926 preferences_widget_new ()
927 {
928 PreferencesWidget * self =
929 g_object_new (
930 PREFERENCES_WIDGET_TYPE,
931 "title", _("Preferences"),
932 NULL);
933
934 for (int i = 0; i <= 6; i++)
935 {
936 add_group (self, i);
937 }
938
939 g_signal_connect (
940 G_OBJECT (self), "destroy",
941 G_CALLBACK (on_window_closed), self);
942
943 return self;
944 }
945
946 static void
preferences_widget_class_init(PreferencesWidgetClass * _klass)947 preferences_widget_class_init (
948 PreferencesWidgetClass * _klass)
949 {
950 }
951
952 static void
preferences_widget_init(PreferencesWidget * self)953 preferences_widget_init (
954 PreferencesWidget * self)
955 {
956 self->group_notebook =
957 GTK_NOTEBOOK (gtk_notebook_new ());
958 gtk_widget_set_visible (
959 GTK_WIDGET (self->group_notebook), true);
960 gtk_container_add (
961 GTK_CONTAINER (
962 gtk_dialog_get_content_area (
963 GTK_DIALOG (self))),
964 GTK_WIDGET (self->group_notebook));
965 }
966