2 * stash.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2008 The Geany contributors
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program 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 General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 * Lightweight library for reading/writing @c GKeyFile settings and synchronizing widgets with
26 * Note: Stash should only depend on GLib and GTK, but currently has some minor
27 * dependencies on Geany's utils.c.
30 * 'Setting' is used only for data stored on disk or in memory.
31 * 'Pref' can also include visual widget information.
33 * @section Memory Usage
34 * Stash will not duplicate strings if they are normally static arrays, such as
35 * keyfile group names and key names, string default values, widget_id names, property names.
37 * @section String Settings
38 * String settings and other dynamically allocated settings will be initialized to NULL when
39 * added to a StashGroup (so they can safely be reassigned later).
41 * @section Widget Support
42 * Widgets very commonly used in configuration dialogs will be supported with their own function.
43 * Widgets less commonly used such as @c GtkExpander or widget settings that aren't commonly needed
44 * to be persistent won't be directly supported, to keep the library lightweight. However, you can
45 * use stash_group_add_widget_property() to also save these settings for any read/write widget
46 * property. Macros could be added for common widget properties such as @c GtkExpander:"expanded".
48 * @section settings-example Settings Example
49 * Here we have some settings for a cup - whether it is made of porcelain, who made it,
50 * how many we have, and how much they cost. (Yes, it's a stupid example).
51 * @include stash-example.c
52 * @note You might want to handle the warning/error conditions differently from above.
54 * @section prefs-example GUI Prefs Example
55 * For prefs, it's the same as the above example but you tell Stash to add widget prefs instead of
58 * This example uses lookup strings for widgets as they are more flexible than widget pointers.
59 * Code to load and save the settings is omitted - see the first example instead.
61 * Here we show a dialog with a toggle button for whether the cup should have a handle.
62 * @include stash-gui-example.c
63 * @note This example should also work for other widget containers besides dialogs, e.g. popup menus.
66 /* Implementation Note
67 * We dynamically allocate prefs. It would be more efficient for user code to declare
68 * a static array of StashPref structs, but we don't do this because:
70 * * It would be more ugly (lots of casts and NULLs).
71 * * Less type checking.
72 * * The API & ABI would have to break when adding/changing fields.
74 * Usually the prefs code isn't what user code will spend most of its time doing, so this
75 * should be efficient enough.
84 #include "support.h" /* only for _("text") */
85 #include "utils.h" /* only for foreach_*, utils_get_setting_*(). Stash should not depend on Geany. */
87 #include <stdlib.h> /* only for atoi() */
90 /* GTK3 removed ComboBoxEntry, but we need a value to differentiate combo box with and
91 * without entries, and it must not collide with other GTypes */
92 #define TYPE_COMBO_BOX_ENTRY get_combo_box_entry_type()
93 static GType
get_combo_box_entry_type(void)
95 static gsize type
= 0;
96 if (g_once_init_enter(&type
))
98 GType g_type
= g_type_register_static_simple(GTK_TYPE_COMBO_BOX
, "dummy-combo-box-entry",
99 sizeof(GtkComboBoxClass
), NULL
, sizeof(GtkComboBox
), NULL
, G_TYPE_FLAG_ABSTRACT
);
100 g_once_init_leave(&type
, g_type
);
105 /* storage for StashPref default values */
114 GtkWidget
*widget_val
;
119 GType setting_type
; /* e.g. G_TYPE_INT */
120 gpointer setting
; /* Address of a variable */
121 const gchar
*key_name
;
122 union Value default_value
; /* Default value, as per setting_type above, e.g. .int_val */
123 GType widget_type
; /* e.g. GTK_TYPE_TOGGLE_BUTTON */
124 StashWidgetID widget_id
; /* (GtkWidget*) or (gchar*) */
127 struct EnumWidget
*radio_buttons
;
128 const gchar
*property_name
;
129 } extra
; /* extra fields depending on widget_type */
132 typedef struct StashPref StashPref
;
136 guint refcount
; /* ref count for GBoxed implementation */
137 const gchar
*name
; /* group name to use in the keyfile */
138 GPtrArray
*entries
; /* array of (StashPref*) */
139 gboolean various
; /* mark group for display/edit in stash treeview */
140 const gchar
*prefix
; /* text to display for Various UI instead of name */
141 gboolean use_defaults
; /* use default values if there's no keyfile entry */
144 typedef struct EnumWidget
146 StashWidgetID widget_id
;
152 typedef enum SettingAction
159 typedef enum PrefAction
167 static void handle_boolean_setting(StashGroup
*group
, StashPref
*se
,
168 GKeyFile
*config
, SettingAction action
)
170 gboolean
*setting
= se
->setting
;
175 *setting
= utils_get_setting_boolean(config
, group
->name
, se
->key_name
,
176 se
->default_value
.bool_val
);
179 g_key_file_set_boolean(config
, group
->name
, se
->key_name
, *setting
);
185 static void handle_double_setting(StashGroup
*group
, StashPref
*se
,
186 GKeyFile
*config
, SettingAction action
)
188 gdouble
*setting
= se
->setting
;
193 *setting
= utils_get_setting_double(config
, group
->name
, se
->key_name
,
194 se
->default_value
.double_val
);
197 g_key_file_set_double(config
, group
->name
, se
->key_name
, *setting
);
203 static void handle_integer_setting(StashGroup
*group
, StashPref
*se
,
204 GKeyFile
*config
, SettingAction action
)
206 gint
*setting
= se
->setting
;
211 *setting
= utils_get_setting_integer(config
, group
->name
, se
->key_name
,
212 se
->default_value
.int_val
);
215 g_key_file_set_integer(config
, group
->name
, se
->key_name
, *setting
);
221 static void handle_string_setting(StashGroup
*group
, StashPref
*se
,
222 GKeyFile
*config
, SettingAction action
)
224 gchararray
*setting
= se
->setting
;
230 *setting
= utils_get_setting_string(config
, group
->name
, se
->key_name
,
231 se
->default_value
.str_val
);
234 g_key_file_set_string(config
, group
->name
, se
->key_name
,
235 *setting
? *setting
: "");
241 static void handle_strv_setting(StashGroup
*group
, StashPref
*se
,
242 GKeyFile
*config
, SettingAction action
)
244 gchararray
**setting
= se
->setting
;
249 g_strfreev(*setting
);
250 *setting
= g_key_file_get_string_list(config
, group
->name
, se
->key_name
,
252 if (*setting
== NULL
)
253 *setting
= g_strdupv(se
->default_value
.strv_val
);
258 /* don't try to save a NULL string vector */
259 const gchar
*dummy
[] = { "", NULL
};
260 const gchar
**strv
= *setting
? (const gchar
**)*setting
: dummy
;
262 g_key_file_set_string_list(config
, group
->name
, se
->key_name
,
263 strv
, g_strv_length((gchar
**)strv
));
270 static void keyfile_action(SettingAction action
, StashGroup
*group
, GKeyFile
*keyfile
)
275 foreach_ptr_array(entry
, i
, group
->entries
)
277 /* don't override settings with default values */
278 if (!group
->use_defaults
&& action
== SETTING_READ
&&
279 !g_key_file_has_key(keyfile
, group
->name
, entry
->key_name
, NULL
))
282 switch (entry
->setting_type
)
285 handle_boolean_setting(group
, entry
, keyfile
, action
); break;
287 handle_integer_setting(group
, entry
, keyfile
, action
); break;
289 handle_double_setting(group
, entry
, keyfile
, action
); break;
291 handle_string_setting(group
, entry
, keyfile
, action
); break;
293 /* Note: G_TYPE_STRV is not a constant, can't use case label */
294 if (entry
->setting_type
== G_TYPE_STRV
)
295 handle_strv_setting(group
, entry
, keyfile
, action
);
297 g_warning("Unhandled type for %s::%s in %s()!", group
->name
, entry
->key_name
,
304 /** Reads key values from @a keyfile into the group settings.
305 * @note You should still call this even if the keyfile couldn't be loaded from disk
306 * so that all Stash settings are initialized to defaults.
308 * @param keyfile Usually loaded from a configuration file first. */
310 void stash_group_load_from_key_file(StashGroup
*group
, GKeyFile
*keyfile
)
312 keyfile_action(SETTING_READ
, group
, keyfile
);
316 /** Writes group settings into key values in @a keyfile.
317 * @a keyfile is usually written to a configuration file afterwards.
319 * @param keyfile . */
321 void stash_group_save_to_key_file(StashGroup
*group
, GKeyFile
*keyfile
)
323 keyfile_action(SETTING_WRITE
, group
, keyfile
);
327 /** Reads group settings from a configuration file using @c GKeyFile.
328 * @note Stash settings will be initialized to defaults if the keyfile
329 * couldn't be loaded from disk.
331 * @param filename Filename of the file to read, in locale encoding.
332 * @return @c TRUE if a key file could be loaded.
333 * @see stash_group_load_from_key_file().
336 gboolean
stash_group_load_from_file(StashGroup
*group
, const gchar
*filename
)
341 keyfile
= g_key_file_new();
342 ret
= g_key_file_load_from_file(keyfile
, filename
, 0, NULL
);
343 /* even on failure we load settings to apply defaults */
344 stash_group_load_from_key_file(group
, keyfile
);
346 g_key_file_free(keyfile
);
351 /** Writes group settings to a configuration file using @c GKeyFile.
354 * @param filename Filename of the file to write, in locale encoding.
355 * @param flags Keyfile options - @c G_KEY_FILE_NONE is the most efficient.
356 * @return 0 if the file was successfully written, otherwise the @c errno of the
357 * failed operation is returned.
358 * @see stash_group_save_to_key_file().
361 gint
stash_group_save_to_file(StashGroup
*group
, const gchar
*filename
,
368 keyfile
= g_key_file_new();
369 /* if we need to keep comments or translations, try to load first */
371 g_key_file_load_from_file(keyfile
, filename
, flags
, NULL
);
373 stash_group_save_to_key_file(group
, keyfile
);
374 data
= g_key_file_to_data(keyfile
, NULL
, NULL
);
375 ret
= utils_write_file(filename
, data
);
377 g_key_file_free(keyfile
);
382 static void free_stash_pref(StashPref
*pref
)
384 if (pref
->widget_type
== GTK_TYPE_RADIO_BUTTON
)
385 g_free(pref
->extra
.radio_buttons
);
387 g_slice_free(StashPref
, pref
);
391 /** Creates a new group.
392 * @param name Name used for @c GKeyFile group.
395 StashGroup
*stash_group_new(const gchar
*name
)
397 StashGroup
*group
= g_slice_new0(StashGroup
);
400 group
->entries
= g_ptr_array_new_with_free_func((GDestroyNotify
) free_stash_pref
);
401 group
->use_defaults
= TRUE
;
407 /** Frees the memory allocated for setting values in a group.
408 * Useful e.g. to avoid freeing strings individually.
409 * @note This is *not* called by stash_group_free().
412 void stash_group_free_settings(StashGroup
*group
)
417 foreach_ptr_array(entry
, i
, group
->entries
)
419 if (entry
->setting_type
== G_TYPE_STRING
)
420 g_free(*(gchararray
*) entry
->setting
);
421 else if (entry
->setting_type
== G_TYPE_STRV
)
422 g_strfreev(*(gchararray
**) entry
->setting
);
426 *(gpointer
**) entry
->setting
= NULL
;
431 static StashGroup
*stash_group_dup(StashGroup
*src
)
433 g_atomic_int_inc(&src
->refcount
);
442 void stash_group_free(StashGroup
*group
)
444 if (g_atomic_int_dec_and_test(&group
->refcount
))
446 g_ptr_array_free(group
->entries
, TRUE
);
447 g_slice_free(StashGroup
, group
);
452 /** Gets the GBoxed-derived GType for StashGroup
454 * @return StashGroup type . */
456 GType
stash_group_get_type(void);
458 G_DEFINE_BOXED_TYPE(StashGroup
, stash_group
, stash_group_dup
, stash_group_free
);
461 /* Used for selecting groups passed to stash_tree_setup().
462 * @param various @c FALSE by default.
463 * @param prefix @nullable Group prefix or @c NULL to use @c group->name. */
464 void stash_group_set_various(StashGroup
*group
, gboolean various
,
467 group
->various
= various
;
468 group
->prefix
= prefix
;
472 /* When @c FALSE, Stash doesn't change the setting if there is no keyfile entry, so it
473 * remains whatever it was initialized/set to by user code.
474 * @c TRUE by default. */
475 void stash_group_set_use_defaults(StashGroup
*group
, gboolean use_defaults
)
477 group
->use_defaults
= use_defaults
;
482 add_pref(StashGroup
*group
, GType type
, gpointer setting
,
483 const gchar
*key_name
, union Value default_value
)
485 StashPref
*entry
= g_slice_new(StashPref
);
487 *entry
= (StashPref
) {type
, setting
, key_name
, default_value
, G_TYPE_NONE
, NULL
, {NULL
}};
489 /* init any pointer settings to NULL so they can be freed later */
490 if (type
== G_TYPE_STRING
|| type
== G_TYPE_STRV
) {
491 if (group
->use_defaults
)
492 *(gpointer
**)setting
= NULL
;
495 g_ptr_array_add(group
->entries
, entry
);
500 /** Adds boolean setting.
502 * @param setting Address of setting variable.
503 * @param key_name Name for key in a @c GKeyFile.
504 * @param default_value Value to use if the key doesn't exist when loading. */
506 void stash_group_add_boolean(StashGroup
*group
, gboolean
*setting
,
507 const gchar
*key_name
, gboolean default_value
)
509 add_pref(group
, G_TYPE_BOOLEAN
, setting
, key_name
, (union Value
) {.bool_val
= default_value
});
513 /** Adds double setting.
515 * @param setting Address of setting variable.
516 * @param key_name Name for key in a @c GKeyFile.
517 * @param default_value Value to use if the key doesn't exist when loading. */
519 void stash_group_add_double(StashGroup
*group
, gdouble
*setting
,
520 const gchar
*key_name
, gdouble default_value
)
522 add_pref(group
, G_TYPE_DOUBLE
, setting
, key_name
, (union Value
) {.double_val
= default_value
});
526 /** Adds integer setting.
528 * @param setting Address of setting variable.
529 * @param key_name Name for key in a @c GKeyFile.
530 * @param default_value Value to use if the key doesn't exist when loading. */
532 void stash_group_add_integer(StashGroup
*group
, gint
*setting
,
533 const gchar
*key_name
, gint default_value
)
535 add_pref(group
, G_TYPE_INT
, setting
, key_name
, (union Value
) {.int_val
= default_value
});
539 /** Adds string setting.
540 * The contents of @a setting will be initialized to @c NULL.
542 * @param setting Address of setting variable.
543 * @param key_name Name for key in a @c GKeyFile.
544 * @param default_value @nullable String to copy if the key doesn't exist when loading, or @c NULL. */
546 void stash_group_add_string(StashGroup
*group
, gchar
**setting
,
547 const gchar
*key_name
, const gchar
*default_value
)
549 add_pref(group
, G_TYPE_STRING
, setting
, key_name
, (union Value
) {.str_val
= (gchar
*) default_value
});
553 /** Adds string vector setting (array of strings).
554 * The contents of @a setting will be initialized to @c NULL.
556 * @param setting Address of setting variable.
557 * @param key_name Name for key in a @c GKeyFile.
558 * @param default_value Vector to copy if the key doesn't exist when loading. Usually @c NULL. */
560 void stash_group_add_string_vector(StashGroup
*group
, gchar
***setting
,
561 const gchar
*key_name
, const gchar
**default_value
)
563 add_pref(group
, G_TYPE_STRV
, setting
, key_name
, (union Value
) {.strv_val
= (gchar
**) default_value
});
567 /* *** GTK-related functions *** */
569 static void handle_toggle_button(GtkWidget
*widget
, gboolean
*setting
,
575 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget
), *setting
);
578 *setting
= gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget
));
584 static void handle_spin_button(GtkWidget
*widget
, StashPref
*entry
,
587 gint
*setting
= entry
->setting
;
589 g_assert(entry
->setting_type
== G_TYPE_INT
); /* only int spin prefs */
594 gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget
), *setting
);
597 /* if the widget is focussed, the value might not be updated */
598 gtk_spin_button_update(GTK_SPIN_BUTTON(widget
));
599 *setting
= gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(widget
));
605 static void handle_combo_box(GtkWidget
*widget
, StashPref
*entry
,
608 gint
*setting
= entry
->setting
;
613 gtk_combo_box_set_active(GTK_COMBO_BOX(widget
), *setting
);
616 *setting
= gtk_combo_box_get_active(GTK_COMBO_BOX(widget
));
622 static void handle_entry(GtkWidget
*widget
, StashPref
*entry
,
625 gchararray
*setting
= entry
->setting
;
630 gtk_entry_set_text(GTK_ENTRY(widget
), *setting
);
634 *setting
= g_strdup(gtk_entry_get_text(GTK_ENTRY(widget
)));
640 static void handle_combo_box_entry(GtkWidget
*widget
, StashPref
*entry
,
643 widget
= gtk_bin_get_child(GTK_BIN(widget
));
644 handle_entry(widget
, entry
, action
);
648 /* taken from Glade 2.x generated support.c */
650 lookup_widget(GtkWidget
*widget
, const gchar
*widget_name
)
652 GtkWidget
*parent
, *found_widget
;
654 g_return_val_if_fail(widget
!= NULL
, NULL
);
655 g_return_val_if_fail(widget_name
!= NULL
, NULL
);
659 if (GTK_IS_MENU(widget
))
660 parent
= gtk_menu_get_attach_widget(GTK_MENU(widget
));
662 parent
= gtk_widget_get_parent(widget
);
664 parent
= (GtkWidget
*) g_object_get_data(G_OBJECT(widget
), "GladeParentKey");
670 found_widget
= (GtkWidget
*) g_object_get_data(G_OBJECT(widget
), widget_name
);
671 if (G_UNLIKELY(found_widget
== NULL
))
672 g_warning("Widget not found: %s", widget_name
);
678 get_widget(GtkWidget
*owner
, StashWidgetID widget_id
)
683 widget
= lookup_widget(owner
, (const gchar
*)widget_id
);
685 widget
= (GtkWidget
*)widget_id
;
687 if (!GTK_IS_WIDGET(widget
))
689 g_warning("Unknown widget in %s()!", G_STRFUNC
);
696 static void handle_radio_button(GtkWidget
*widget
, gint enum_id
, gboolean
*setting
,
702 if (*setting
== enum_id
)
703 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget
), TRUE
);
706 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget
)))
713 static void handle_radio_buttons(GtkWidget
*owner
, StashPref
*entry
,
716 EnumWidget
*field
= entry
->extra
.radio_buttons
;
718 GtkWidget
*widget
= NULL
;
722 widget
= get_widget(owner
, field
->widget_id
);
728 handle_radio_button(widget
, field
->enum_id
, entry
->setting
, action
);
730 if (!field
->widget_id
)
733 if (g_slist_length(gtk_radio_button_get_group(GTK_RADIO_BUTTON(widget
))) != count
)
734 g_warning("Missing/invalid radio button widget IDs found!");
738 static void handle_widget_property(GtkWidget
*widget
, StashPref
*entry
,
741 GObject
*object
= G_OBJECT(widget
);
742 const gchar
*name
= entry
->extra
.property_name
;
747 if (entry
->setting_type
== G_TYPE_BOOLEAN
)
748 g_object_set(object
, name
, *(gboolean
*)entry
->setting
, NULL
);
749 else if (entry
->setting_type
== G_TYPE_INT
)
750 g_object_set(object
, name
, *(gint
*)entry
->setting
, NULL
);
751 else if (entry
->setting_type
== G_TYPE_STRING
)
752 g_object_set(object
, name
, *(gchararray
*)entry
->setting
, NULL
);
753 else if (entry
->setting_type
== G_TYPE_STRV
)
754 g_object_set(object
, name
, *(gchararray
**)entry
->setting
, NULL
);
757 g_warning("Unhandled type %s for %s in %s()!", g_type_name(entry
->setting_type
),
758 entry
->key_name
, G_STRFUNC
);
762 if (entry
->setting_type
== G_TYPE_STRING
)
763 g_free(*(gchararray
*)entry
->setting
);
764 else if (entry
->setting_type
== G_TYPE_STRV
)
765 g_strfreev(*(gchararray
**)entry
->setting
);
767 g_object_get(object
, name
, entry
->setting
, NULL
);
773 static void pref_action(PrefAction action
, StashGroup
*group
, GtkWidget
*owner
)
778 foreach_ptr_array(entry
, i
, group
->entries
)
782 /* ignore settings with no widgets */
783 if (entry
->widget_type
== G_TYPE_NONE
)
786 /* radio buttons have several widgets */
787 if (entry
->widget_type
== GTK_TYPE_RADIO_BUTTON
)
789 handle_radio_buttons(owner
, entry
, action
);
793 widget
= get_widget(owner
, entry
->widget_id
);
796 g_warning("Unknown widget for %s::%s in %s()!", group
->name
, entry
->key_name
,
801 /* note: can't use switch for GTK_TYPE macros */
802 if (entry
->widget_type
== GTK_TYPE_TOGGLE_BUTTON
)
803 handle_toggle_button(widget
, entry
->setting
, action
);
804 else if (entry
->widget_type
== GTK_TYPE_SPIN_BUTTON
)
805 handle_spin_button(widget
, entry
, action
);
806 else if (entry
->widget_type
== GTK_TYPE_COMBO_BOX
)
807 handle_combo_box(widget
, entry
, action
);
808 else if (entry
->widget_type
== TYPE_COMBO_BOX_ENTRY
)
809 handle_combo_box_entry(widget
, entry
, action
);
810 else if (entry
->widget_type
== GTK_TYPE_ENTRY
)
811 handle_entry(widget
, entry
, action
);
812 else if (entry
->widget_type
== G_TYPE_PARAM
)
813 handle_widget_property(widget
, entry
, action
);
815 g_warning("Unhandled type for %s::%s in %s()!", group
->name
, entry
->key_name
,
821 /** Applies Stash settings to widgets, usually called before displaying the widgets.
822 * The @a owner argument depends on which type you use for @ref StashWidgetID.
824 * @param owner If non-NULL, used to lookup widgets by name, otherwise
825 * widget pointers are assumed.
826 * @see stash_group_update(). */
828 void stash_group_display(StashGroup
*group
, GtkWidget
*owner
)
830 pref_action(PREF_DISPLAY
, group
, owner
);
834 /** Applies widget values to Stash settings, usually called after displaying the widgets.
835 * The @a owner argument depends on which type you use for @ref StashWidgetID.
837 * @param owner If non-NULL, used to lookup widgets by name, otherwise
838 * widget pointers are assumed.
839 * @see stash_group_display(). */
841 void stash_group_update(StashGroup
*group
, GtkWidget
*owner
)
843 pref_action(PREF_UPDATE
, group
, owner
);
848 add_widget_pref(StashGroup
*group
, GType setting_type
, gpointer setting
,
849 const gchar
*key_name
, union Value default_value
,
850 GType widget_type
, StashWidgetID widget_id
)
853 add_pref(group
, setting_type
, setting
, key_name
, default_value
);
855 entry
->widget_type
= widget_type
;
856 entry
->widget_id
= widget_id
;
861 /** Adds a @c GtkToggleButton (or @c GtkCheckButton) widget pref.
863 * @param setting Address of setting variable.
864 * @param key_name Name for key in a @c GKeyFile.
865 * @param default_value Value to use if the key doesn't exist when loading.
866 * @param widget_id @c GtkWidget pointer or string to lookup widget later.
867 * @see stash_group_add_radio_buttons(). */
869 void stash_group_add_toggle_button(StashGroup
*group
, gboolean
*setting
,
870 const gchar
*key_name
, gboolean default_value
, StashWidgetID widget_id
)
872 add_widget_pref(group
, G_TYPE_BOOLEAN
, setting
, key_name
, (union Value
) {.bool_val
= default_value
},
873 GTK_TYPE_TOGGLE_BUTTON
, widget_id
);
877 /** Adds a @c GtkRadioButton widget group pref.
879 * @param setting Address of setting variable.
880 * @param key_name Name for key in a @c GKeyFile.
881 * @param default_value Value to use if the key doesn't exist when loading.
882 * @param widget_id @c GtkWidget pointer or string to lookup widget later.
883 * @param enum_id Enum value for @a widget_id.
884 * @param ... pairs of @a widget_id, @a enum_id.
885 * Example (using widget lookup strings, but widget pointers can also work):
888 * stash_group_add_radio_buttons(group, &which_one_setting, "which_one", BAR,
889 * "radio_foo", FOO, "radio_bar", BAR, NULL);
892 void stash_group_add_radio_buttons(StashGroup
*group
, gint
*setting
,
893 const gchar
*key_name
, gint default_value
,
894 StashWidgetID widget_id
, gint enum_id
, ...)
897 add_widget_pref(group
, G_TYPE_INT
, setting
, key_name
, (union Value
) {.int_val
= default_value
},
898 GTK_TYPE_RADIO_BUTTON
, NULL
);
901 EnumWidget
*item
, *array
;
903 /* count pairs of args */
904 va_start(args
, enum_id
);
907 if (!va_arg(args
, gpointer
))
914 array
= g_new0(EnumWidget
, count
+ 1);
915 entry
->extra
.radio_buttons
= array
;
917 va_start(args
, enum_id
);
918 foreach_c_array(item
, array
, count
)
923 item
->widget_id
= widget_id
;
924 item
->enum_id
= enum_id
;
928 item
->widget_id
= va_arg(args
, gpointer
);
929 item
->enum_id
= va_arg(args
, gint
);
936 /** Adds a @c GtkSpinButton widget pref.
938 * @param setting Address of setting variable.
939 * @param key_name Name for key in a @c GKeyFile.
940 * @param default_value Value to use if the key doesn't exist when loading.
941 * @param widget_id @c GtkWidget pointer or string to lookup widget later. */
943 void stash_group_add_spin_button_integer(StashGroup
*group
, gint
*setting
,
944 const gchar
*key_name
, gint default_value
, StashWidgetID widget_id
)
946 add_widget_pref(group
, G_TYPE_INT
, setting
, key_name
,
947 (union Value
) {.int_val
= default_value
}, GTK_TYPE_SPIN_BUTTON
, widget_id
);
951 /** Adds a @c GtkComboBox widget pref.
953 * @param setting Address of setting variable.
954 * @param key_name Name for key in a @c GKeyFile.
955 * @param default_value Value to use if the key doesn't exist when loading.
956 * @param widget_id @c GtkWidget pointer or string to lookup widget later.
957 * @see stash_group_add_combo_box_entry(). */
959 void stash_group_add_combo_box(StashGroup
*group
, gint
*setting
,
960 const gchar
*key_name
, gint default_value
, StashWidgetID widget_id
)
962 add_widget_pref(group
, G_TYPE_INT
, setting
, key_name
,
963 (union Value
) {.int_val
= default_value
}, GTK_TYPE_COMBO_BOX
, widget_id
);
967 /** Adds a @c GtkComboBoxEntry widget pref.
969 * @param setting Address of setting variable.
970 * @param key_name Name for key in a @c GKeyFile.
971 * @param default_value Value to use if the key doesn't exist when loading.
972 * @param widget_id @c GtkWidget pointer or string to lookup widget later. */
973 /* We could maybe also have something like stash_group_add_combo_box_entry_with_menu()
974 * for the history list - or should that be stored as a separate setting? */
976 void stash_group_add_combo_box_entry(StashGroup
*group
, gchar
**setting
,
977 const gchar
*key_name
, const gchar
*default_value
, StashWidgetID widget_id
)
979 add_widget_pref(group
, G_TYPE_STRING
, setting
, key_name
,
980 (union Value
) {.str_val
= (gchar
*) default_value
}, TYPE_COMBO_BOX_ENTRY
, widget_id
);
984 /** Adds a @c GtkEntry widget pref.
986 * @param setting Address of setting variable.
987 * @param key_name Name for key in a @c GKeyFile.
988 * @param default_value Value to use if the key doesn't exist when loading.
989 * @param widget_id @c GtkWidget pointer or string to lookup widget later. */
991 void stash_group_add_entry(StashGroup
*group
, gchar
**setting
,
992 const gchar
*key_name
, const gchar
*default_value
, StashWidgetID widget_id
)
994 add_widget_pref(group
, G_TYPE_STRING
, setting
, key_name
,
995 (union Value
) {.str_val
= (gchar
*) default_value
}, GTK_TYPE_ENTRY
, widget_id
);
999 static GType
object_get_property_type(GObject
*object
, const gchar
*property_name
)
1001 GObjectClass
*klass
= G_OBJECT_GET_CLASS(object
);
1004 ps
= g_object_class_find_property(klass
, property_name
);
1005 return ps
->value_type
;
1009 /** Adds a widget's read/write property to the stash group.
1010 * The property will be set when calling
1011 * stash_group_display(), and read when calling stash_group_update().
1013 * @param setting Address of e.g. an integer if using an integer property.
1014 * @param key_name Name for key in a @c GKeyFile.
1015 * @param default_value Value to use if the key doesn't exist when loading.
1016 * Should be cast into a pointer e.g. with @c GINT_TO_POINTER().
1017 * @param widget_id @c GtkWidget pointer or string to lookup widget later.
1018 * @param property_name .
1019 * @param type can be @c 0 if passing a @c GtkWidget as the @a widget_id argument to look it up from the
1021 * @warning Currently only string GValue properties will be freed before setting; patch for
1022 * other types - see @c handle_widget_property(). */
1024 void stash_group_add_widget_property(StashGroup
*group
, gpointer setting
,
1025 const gchar
*key_name
, gpointer default_value
, StashWidgetID widget_id
,
1026 const gchar
*property_name
, GType type
)
1031 type
= object_get_property_type(G_OBJECT(widget_id
), property_name
);
1033 entry
= add_widget_pref(group
, type
, setting
, key_name
,
1034 (union Value
) {.ptr_val
= default_value
}, G_TYPE_PARAM
, widget_id
);
1035 entry
->extra
.property_name
= property_name
;
1047 struct StashTreeValue
1049 const gchar
*group_name
;
1053 gchararray tree_string
;
1058 typedef struct StashTreeValue StashTreeValue
;
1061 static void stash_tree_renderer_set_data(GtkCellLayout
*cell_layout
, GtkCellRenderer
*cell
,
1062 GtkTreeModel
*model
, GtkTreeIter
*iter
, gpointer user_data
)
1064 GType cell_type
= GPOINTER_TO_SIZE(user_data
);
1065 StashTreeValue
*value
;
1067 gboolean matches_type
;
1069 gtk_tree_model_get(model
, iter
, STASH_TREE_VALUE
, &value
, -1);
1071 matches_type
= pref
->setting_type
== cell_type
;
1072 g_object_set(cell
, "visible", matches_type
, "sensitive", matches_type
,
1073 cell_type
== G_TYPE_BOOLEAN
? "activatable" : "editable", matches_type
, NULL
);
1077 switch (pref
->setting_type
)
1079 case G_TYPE_BOOLEAN
:
1080 g_object_set(cell
, "active", value
->data
.tree_int
, NULL
);
1084 gchar
*text
= g_strdup_printf("%d", value
->data
.tree_int
);
1085 g_object_set(cell
, "text", text
, NULL
);
1090 g_object_set(cell
, "text", value
->data
.tree_string
, NULL
);
1097 static void stash_tree_renderer_edited(gchar
*path_str
, gchar
*new_text
, GtkTreeModel
*model
)
1101 StashTreeValue
*value
;
1104 path
= gtk_tree_path_new_from_string(path_str
);
1105 gtk_tree_model_get_iter(model
, &iter
, path
);
1106 gtk_tree_model_get(model
, &iter
, STASH_TREE_VALUE
, &value
, -1);
1109 switch (pref
->setting_type
)
1111 case G_TYPE_BOOLEAN
:
1112 value
->data
.tree_int
= !value
->data
.tree_int
;
1115 value
->data
.tree_int
= atoi(new_text
);
1118 SETPTR(value
->data
.tree_string
, g_strdup(new_text
));
1122 gtk_tree_model_row_changed(model
, path
, &iter
);
1123 gtk_tree_path_free(path
);
1127 static void stash_tree_boolean_toggled(GtkCellRendererToggle
*cell
, gchar
*path_str
,
1128 GtkTreeModel
*model
)
1130 stash_tree_renderer_edited(path_str
, NULL
, model
);
1134 static void stash_tree_string_edited(GtkCellRenderer
*cell
, gchar
*path_str
, gchar
*new_text
,
1135 GtkTreeModel
*model
)
1137 stash_tree_renderer_edited(path_str
, new_text
, model
);
1141 static gboolean
stash_tree_discard_value(GtkTreeModel
*model
, GtkTreePath
*path
,
1142 GtkTreeIter
*iter
, gpointer user_data
)
1144 StashTreeValue
*value
;
1146 gtk_tree_model_get(model
, iter
, STASH_TREE_VALUE
, &value
, -1);
1147 /* don't access value->pref as it might already have been freed */
1148 g_free(value
->data
.tree_string
);
1155 static void stash_tree_destroy_cb(GtkWidget
*widget
, gpointer user_data
)
1157 GtkTreeModel
*model
= gtk_tree_view_get_model(GTK_TREE_VIEW(widget
));
1158 gtk_tree_model_foreach(model
, stash_tree_discard_value
, NULL
);
1162 static void stash_tree_append_pref(StashGroup
*group
, StashPref
*entry
, GtkListStore
*store
,
1166 StashTreeValue
*value
;
1169 value
= g_new0(StashTreeValue
, 1);
1171 value
->group_name
= group
->name
;
1172 value
->pref
= entry
;
1174 gtk_list_store_append(store
, &iter
);
1175 text
= g_strconcat(group
->prefix
? group
->prefix
: group
->name
,
1176 ".", entry
->key_name
, NULL
);
1177 gtk_list_store_set(store
, &iter
, STASH_TREE_NAME
, text
,
1178 STASH_TREE_VALUE
, value
, -1);
1183 static void stash_tree_append_prefs(GPtrArray
*group_array
,
1184 GtkListStore
*store
, PrefAction action
)
1190 foreach_ptr_array(group
, i
, group_array
)
1194 foreach_ptr_array(entry
, j
, group
->entries
)
1195 stash_tree_append_pref(group
, entry
, store
, action
);
1201 /* Setups a simple editor for stash preferences based on the widget arguments.
1202 * group_array - Array of groups which's settings will be edited.
1203 * tree - GtkTreeView in which to edit the preferences. Must be empty. */
1204 void stash_tree_setup(GPtrArray
*group_array
, GtkTreeView
*tree
)
1206 GtkListStore
*store
;
1207 GtkTreeModel
*model
;
1208 GtkCellRenderer
*cell
;
1209 GtkTreeViewColumn
*column
;
1210 GtkAdjustment
*adjustment
;
1212 store
= gtk_list_store_new(STASH_TREE_COUNT
, G_TYPE_STRING
, G_TYPE_POINTER
);
1213 stash_tree_append_prefs(group_array
, store
, PREF_DISPLAY
);
1214 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store
), STASH_TREE_NAME
,
1215 GTK_SORT_ASCENDING
);
1217 model
= GTK_TREE_MODEL(store
);
1218 gtk_tree_view_set_model(tree
, model
);
1219 g_object_unref(G_OBJECT(store
));
1220 g_signal_connect(tree
, "destroy", G_CALLBACK(stash_tree_destroy_cb
), NULL
);
1222 cell
= gtk_cell_renderer_text_new();
1223 column
= gtk_tree_view_column_new_with_attributes(_("Name"), cell
, "text",
1224 STASH_TREE_NAME
, NULL
);
1225 gtk_tree_view_column_set_sort_column_id(column
, STASH_TREE_NAME
);
1226 gtk_tree_view_column_set_sort_indicator(column
, TRUE
);
1227 gtk_tree_view_append_column(tree
, column
);
1229 column
= gtk_tree_view_column_new();
1230 gtk_tree_view_column_set_title(column
, _("Value"));
1231 gtk_tree_view_append_column(tree
, column
);
1232 /* boolean renderer */
1233 cell
= gtk_cell_renderer_toggle_new();
1234 g_signal_connect(cell
, "toggled", G_CALLBACK(stash_tree_boolean_toggled
), model
);
1235 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(column
), cell
, FALSE
);
1236 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(column
), cell
,
1237 stash_tree_renderer_set_data
, GSIZE_TO_POINTER(G_TYPE_BOOLEAN
), NULL
);
1238 /* string renderer */
1239 cell
= gtk_cell_renderer_text_new();
1240 g_object_set(cell
, "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
1241 g_signal_connect(cell
, "edited", G_CALLBACK(stash_tree_string_edited
), model
);
1242 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(column
), cell
, TRUE
);
1243 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(column
), cell
,
1244 stash_tree_renderer_set_data
, GSIZE_TO_POINTER(G_TYPE_STRING
), NULL
);
1245 /* integer renderer */
1246 cell
= gtk_cell_renderer_spin_new();
1247 adjustment
= GTK_ADJUSTMENT(gtk_adjustment_new(0, G_MININT
, G_MAXINT
, 1, 10, 0));
1248 g_object_set(cell
, "adjustment", adjustment
, NULL
);
1249 g_signal_connect(cell
, "edited", G_CALLBACK(stash_tree_string_edited
), model
);
1250 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(column
), cell
, FALSE
);
1251 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(column
), cell
,
1252 stash_tree_renderer_set_data
, GSIZE_TO_POINTER(G_TYPE_INT
), NULL
);
1256 static void stash_tree_display_pref(StashTreeValue
*value
, StashPref
*entry
)
1258 switch (entry
->setting_type
)
1260 case G_TYPE_BOOLEAN
:
1261 value
->data
.tree_int
= *(gboolean
*) entry
->setting
;
1264 value
->data
.tree_int
= *(gint
*) entry
->setting
;
1267 SETPTR(value
->data
.tree_string
, g_strdup(*(gchararray
*) entry
->setting
));
1270 g_warning("Unhandled type for %s::%s in %s()!", value
->group_name
,
1271 entry
->key_name
, G_STRFUNC
);
1276 static void stash_tree_update_pref(StashTreeValue
*value
, StashPref
*entry
)
1278 switch (entry
->setting_type
)
1280 case G_TYPE_BOOLEAN
:
1281 *(gboolean
*) entry
->setting
= value
->data
.tree_int
;
1284 *(gint
*) entry
->setting
= value
->data
.tree_int
;
1288 gchararray
*text
= entry
->setting
;
1289 SETPTR(*text
, g_strdup(value
->data
.tree_string
));
1293 g_warning("Unhandled type for %s::%s in %s()!", value
->group_name
,
1294 entry
->key_name
, G_STRFUNC
);
1299 static void stash_tree_action(GtkTreeModel
*model
, PrefAction action
)
1302 gboolean valid
= gtk_tree_model_get_iter_first(model
, &iter
);
1303 StashTreeValue
*value
;
1307 gtk_tree_model_get(model
, &iter
, STASH_TREE_VALUE
, &value
, -1);
1312 stash_tree_display_pref(value
, value
->pref
);
1315 stash_tree_update_pref(value
, value
->pref
);
1318 valid
= gtk_tree_model_iter_next(model
, &iter
);
1323 void stash_tree_display(GtkTreeView
*tree
)
1325 stash_tree_action(gtk_tree_view_get_model(tree
), PREF_DISPLAY
);
1329 void stash_tree_update(GtkTreeView
*tree
)
1331 stash_tree_action(gtk_tree_view_get_model(tree
), PREF_UPDATE
);