2 * stash.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2008-2011 Nick Treleaven <nick(dot)treleaven(at)btinternet(dot)com>
5 * Copyright 2008-2011 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program 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. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 * Lightweight library for reading/writing @c GKeyFile settings and synchronizing widgets with
28 * Note: Stash should only depend on GLib and GTK, but currently has some minor
29 * dependencies on Geany's utils.c.
32 * 'Setting' is used only for data stored on disk or in memory.
33 * 'Pref' can also include visual widget information.
35 * @section Memory Usage
36 * Stash will not duplicate strings if they are normally static arrays, such as
37 * keyfile group names and key names, string default values, widget_id names, property names.
39 * @section String Settings
40 * String settings and other dynamically allocated settings will be initialized to NULL when
41 * added to a StashGroup (so they can safely be reassigned later).
43 * @section Widget Support
44 * Widgets very commonly used in configuration dialogs will be supported with their own function.
45 * Widgets less commonly used such as @c GtkExpander or widget settings that aren't commonly needed
46 * to be persistent won't be directly supported, to keep the library lightweight. However, you can
47 * use stash_group_add_widget_property() to also save these settings for any read/write widget
48 * property. Macros could be added for common widget properties such as @c GtkExpander:"expanded".
50 * @section settings-example Settings Example
51 * Here we have some settings for how to make a cup - whether it should be made of china
52 * and who's going to make it. (Yes, it's a stupid example).
53 * @include stash-example.c
54 * @note You might want to handle the warning/error conditions differently from above.
56 * @section prefs-example GUI Prefs Example
57 * For prefs, it's the same as the above example but you tell Stash to add widget prefs instead of
60 * This example uses lookup strings for widgets as they are more flexible than widget pointers.
61 * Code to load and save the settings is omitted - see the first example instead.
63 * Here we show a dialog with a toggle button for whether the cup should have a handle.
64 * @include stash-gui-example.c
65 * @note This example should also work for other widget containers besides dialogs, e.g. popup menus.
68 /* Implementation Note
69 * We use a GArray to hold prefs. It would be more efficient for user code to declare
70 * a static array of StashPref structs, but we don't do this because:
72 * * It would be more ugly (lots of casts and NULLs).
73 * * Less type checking.
74 * * The API would have to break when adding/changing fields.
76 * Usually the prefs code isn't what user code will spend most of its time doing, so this
77 * should be efficient enough. But, if desired we could add a stash_group_set_size() function
78 * to reduce reallocation (or perhaps use a different container).
80 * Note: Maybe using GSlice chunks with an extra 'next' pointer would be more efficient.
84 #include "geany.h" /* necessary for utils.h, otherwise use gtk/gtk.h */
85 #include <stdlib.h> /* only for atoi() */
86 #include <string.h> /* only for strcmp() */
87 #include "support.h" /* only for _("text") */
88 #include "utils.h" /* only for foreach_*, utils_get_setting_*(). Stash should not depend on Geany. */
89 #include "ui_utils.h" /* only for ui_lookup_object(). Stash should not depend on Geany. */
96 GType setting_type
; /* e.g. G_TYPE_INT */
97 gpointer setting
; /* Address of a variable */
98 const gchar
*key_name
;
99 gpointer default_value
; /* Default value, e.g. (gpointer)1 */
100 GType widget_type
; /* e.g. GTK_TYPE_TOGGLE_BUTTON */
101 StashWidgetID widget_id
; /* (GtkWidget*) or (gchar*) */
102 gpointer fields
; /* extra fields */
105 typedef struct StashPref StashPref
;
109 const gchar
*name
; /* group name to use in the keyfile */
110 GArray
*entries
; /* array of StashPref */
111 gboolean various
; /* mark group for display/edit in stash treeview */
112 gboolean use_defaults
; /* use default values if there's no keyfile entry */
115 typedef struct EnumWidget
117 StashWidgetID widget_id
;
123 typedef enum SettingAction
130 typedef enum PrefAction
138 static void handle_boolean_setting(StashGroup
*group
, StashPref
*se
,
139 GKeyFile
*config
, SettingAction action
)
141 gboolean
*setting
= se
->setting
;
146 *setting
= utils_get_setting_boolean(config
, group
->name
, se
->key_name
,
147 GPOINTER_TO_INT(se
->default_value
));
150 g_key_file_set_boolean(config
, group
->name
, se
->key_name
, *setting
);
156 static void handle_integer_setting(StashGroup
*group
, StashPref
*se
,
157 GKeyFile
*config
, SettingAction action
)
159 gint
*setting
= se
->setting
;
164 *setting
= utils_get_setting_integer(config
, group
->name
, se
->key_name
,
165 GPOINTER_TO_INT(se
->default_value
));
168 g_key_file_set_integer(config
, group
->name
, se
->key_name
, *setting
);
174 static void handle_string_setting(StashGroup
*group
, StashPref
*se
,
175 GKeyFile
*config
, SettingAction action
)
177 gchararray
*setting
= se
->setting
;
183 *setting
= utils_get_setting_string(config
, group
->name
, se
->key_name
,
187 g_key_file_set_string(config
, group
->name
, se
->key_name
,
188 *setting
? *setting
: "");
194 static void handle_strv_setting(StashGroup
*group
, StashPref
*se
,
195 GKeyFile
*config
, SettingAction action
)
197 gchararray
**setting
= se
->setting
;
202 g_strfreev(*setting
);
203 *setting
= g_key_file_get_string_list(config
, group
->name
, se
->key_name
,
205 if (*setting
== NULL
)
206 *setting
= g_strdupv(se
->default_value
);
211 /* don't try to save a NULL string vector */
212 const gchar
*dummy
[] = { "", NULL
};
213 const gchar
**strv
= *setting
? (const gchar
**)*setting
: dummy
;
215 g_key_file_set_string_list(config
, group
->name
, se
->key_name
,
216 strv
, g_strv_length((gchar
**)strv
));
223 static void keyfile_action(SettingAction action
, StashGroup
*group
, GKeyFile
*keyfile
)
227 foreach_array(StashPref
, entry
, group
->entries
)
229 /* don't override settings with default values */
230 if (!group
->use_defaults
&& action
== SETTING_READ
&&
231 !g_key_file_has_key(keyfile
, group
->name
, entry
->key_name
, NULL
))
234 switch (entry
->setting_type
)
237 handle_boolean_setting(group
, entry
, keyfile
, action
); break;
239 handle_integer_setting(group
, entry
, keyfile
, action
); break;
241 handle_string_setting(group
, entry
, keyfile
, action
); break;
243 /* Note: G_TYPE_STRV is not a constant, can't use case label */
244 if (entry
->setting_type
== G_TYPE_STRV
)
245 handle_strv_setting(group
, entry
, keyfile
, action
);
247 g_warning("Unhandled type for %s::%s in %s()!", group
->name
, entry
->key_name
,
254 /** Reads key values from @a keyfile into the group settings.
255 * @note You should still call this even if the keyfile couldn't be loaded from disk
256 * so that all Stash settings are initialized to defaults.
258 * @param keyfile Usually loaded from a configuration file first. */
259 void stash_group_load_from_key_file(StashGroup
*group
, GKeyFile
*keyfile
)
261 keyfile_action(SETTING_READ
, group
, keyfile
);
265 /** Writes group settings into key values in @a keyfile.
266 * @a keyfile is usually written to a configuration file afterwards.
268 * @param keyfile . */
269 void stash_group_save_to_key_file(StashGroup
*group
, GKeyFile
*keyfile
)
271 keyfile_action(SETTING_WRITE
, group
, keyfile
);
275 /** Reads group settings from a configuration file using @c GKeyFile.
276 * @note Stash settings will be initialized to defaults if the keyfile
277 * couldn't be loaded from disk.
279 * @param filename Filename of the file to read, in locale encoding.
280 * @return @c TRUE if a key file could be loaded.
281 * @see stash_group_load_from_key_file().
283 gboolean
stash_group_load_from_file(StashGroup
*group
, const gchar
*filename
)
288 keyfile
= g_key_file_new();
289 ret
= g_key_file_load_from_file(keyfile
, filename
, 0, NULL
);
290 /* even on failure we load settings to apply defaults */
291 stash_group_load_from_key_file(group
, keyfile
);
293 g_key_file_free(keyfile
);
298 /** Writes group settings to a configuration file using @c GKeyFile.
301 * @param filename Filename of the file to write, in locale encoding.
302 * @param flags Keyfile options - @c G_KEY_FILE_NONE is the most efficient.
303 * @return 0 if the file was successfully written, otherwise the @c errno of the
304 * failed operation is returned.
305 * @see stash_group_save_to_key_file().
307 gint
stash_group_save_to_file(StashGroup
*group
, const gchar
*filename
,
314 keyfile
= g_key_file_new();
315 /* if we need to keep comments or translations, try to load first */
317 g_key_file_load_from_file(keyfile
, filename
, flags
, NULL
);
319 stash_group_save_to_key_file(group
, keyfile
);
320 data
= g_key_file_to_data(keyfile
, NULL
, NULL
);
321 ret
= utils_write_file(filename
, data
);
323 g_key_file_free(keyfile
);
328 /** Creates a new group.
329 * @param name Name used for @c GKeyFile group.
331 StashGroup
*stash_group_new(const gchar
*name
)
333 StashGroup
*group
= g_new0(StashGroup
, 1);
336 group
->entries
= g_array_new(FALSE
, FALSE
, sizeof(StashPref
));
337 group
->use_defaults
= TRUE
;
344 void stash_group_free(StashGroup
*group
)
348 foreach_array(StashPref
, entry
, group
->entries
)
350 if (entry
->widget_type
== GTK_TYPE_RADIO_BUTTON
)
351 g_free(entry
->fields
);
352 else if (entry
->widget_type
== G_TYPE_PARAM
)
355 g_assert(entry
->fields
== NULL
); /* to prevent memory leaks, must handle fields above */
357 g_array_free(group
->entries
, TRUE
);
362 /* Used for selecting groups passed to stash_tree_setup().
363 * @c FALSE by default. */
364 void stash_group_set_various(StashGroup
*group
, gboolean various
)
366 group
->various
= various
;
370 /* When @c FALSE, Stash doesn't change the setting if there is no keyfile entry, so it
371 * remains whatever it was initialized/set to by user code.
372 * @c TRUE by default. */
373 void stash_group_set_use_defaults(StashGroup
*group
, gboolean use_defaults
)
375 group
->use_defaults
= use_defaults
;
380 add_pref(StashGroup
*group
, GType type
, gpointer setting
,
381 const gchar
*key_name
, gpointer default_value
)
383 StashPref entry
= {type
, setting
, key_name
, default_value
, G_TYPE_NONE
, NULL
, NULL
};
384 GArray
*array
= group
->entries
;
386 /* init any pointer settings to NULL so they can be freed later */
387 if (type
== G_TYPE_STRING
||
389 if (group
->use_defaults
)
390 *(gpointer
**)setting
= NULL
;
392 g_array_append_val(array
, entry
);
394 return &g_array_index(array
, StashPref
, array
->len
- 1);
398 /** Adds boolean setting.
400 * @param setting Address of setting variable.
401 * @param key_name Name for key in a @c GKeyFile.
402 * @param default_value Value to use if the key doesn't exist when loading. */
403 void stash_group_add_boolean(StashGroup
*group
, gboolean
*setting
,
404 const gchar
*key_name
, gboolean default_value
)
406 add_pref(group
, G_TYPE_BOOLEAN
, setting
, key_name
, GINT_TO_POINTER(default_value
));
410 /** Adds integer setting.
412 * @param setting Address of setting variable.
413 * @param key_name Name for key in a @c GKeyFile.
414 * @param default_value Value to use if the key doesn't exist when loading. */
415 void stash_group_add_integer(StashGroup
*group
, gint
*setting
,
416 const gchar
*key_name
, gint default_value
)
418 add_pref(group
, G_TYPE_INT
, setting
, key_name
, GINT_TO_POINTER(default_value
));
422 /** Adds string setting.
423 * The contents of @a setting will be initialized to @c NULL.
425 * @param setting Address of setting variable.
426 * @param key_name Name for key in a @c GKeyFile.
427 * @param default_value String to copy if the key doesn't exist when loading, or @c NULL. */
428 void stash_group_add_string(StashGroup
*group
, gchar
**setting
,
429 const gchar
*key_name
, const gchar
*default_value
)
431 add_pref(group
, G_TYPE_STRING
, setting
, key_name
, (gpointer
)default_value
);
435 /** Adds string vector setting (array of strings).
436 * The contents of @a setting will be initialized to @c NULL.
438 * @param setting Address of setting variable.
439 * @param key_name Name for key in a @c GKeyFile.
440 * @param default_value Vector to copy if the key doesn't exist when loading. Usually @c NULL. */
441 void stash_group_add_string_vector(StashGroup
*group
, gchar
***setting
,
442 const gchar
*key_name
, const gchar
**default_value
)
444 add_pref(group
, G_TYPE_STRV
, setting
, key_name
, (gpointer
)default_value
);
448 /* *** GTK-related functions *** */
450 static void handle_toggle_button(GtkWidget
*widget
, gboolean
*setting
,
456 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget
), *setting
);
459 *setting
= gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget
));
465 static void handle_spin_button(GtkWidget
*widget
, StashPref
*entry
,
468 gint
*setting
= entry
->setting
;
470 g_assert(entry
->setting_type
== G_TYPE_INT
); /* only int spin prefs */
475 gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget
), *setting
);
478 /* if the widget is focussed, the value might not be updated */
479 gtk_spin_button_update(GTK_SPIN_BUTTON(widget
));
480 *setting
= gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(widget
));
486 static void handle_combo_box(GtkWidget
*widget
, StashPref
*entry
,
489 gint
*setting
= entry
->setting
;
494 gtk_combo_box_set_active(GTK_COMBO_BOX(widget
), *setting
);
497 *setting
= gtk_combo_box_get_active(GTK_COMBO_BOX(widget
));
503 static void handle_entry(GtkWidget
*widget
, StashPref
*entry
,
506 gchararray
*setting
= entry
->setting
;
511 gtk_entry_set_text(GTK_ENTRY(widget
), *setting
);
515 *setting
= g_strdup(gtk_entry_get_text(GTK_ENTRY(widget
)));
521 static void handle_combo_box_entry(GtkWidget
*widget
, StashPref
*entry
,
524 widget
= gtk_bin_get_child(GTK_BIN(widget
));
525 handle_entry(widget
, entry
, action
);
530 /* taken from Glade 2.x generated support.c */
532 lookup_widget(GtkWidget
*widget
, const gchar
*widget_name
)
534 GtkWidget
*found_widget
;
536 (void) widget
; /* not used anymore */
538 found_widget
= GTK_WIDGET(ui_lookup_object(widget_name
));
540 g_warning ("Widget not found: %s", widget_name
);
546 get_widget(GtkWidget
*owner
, StashWidgetID widget_id
)
551 widget
= lookup_widget(owner
, (const gchar
*)widget_id
);
553 widget
= (GtkWidget
*)widget_id
;
555 if (!GTK_IS_WIDGET(widget
))
557 g_warning("Unknown widget in %s()!", G_STRFUNC
);
564 static void handle_radio_button(GtkWidget
*widget
, gint enum_id
, gboolean
*setting
,
570 if (*setting
== enum_id
)
571 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget
), TRUE
);
574 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget
)))
581 static void handle_radio_buttons(GtkWidget
*owner
, EnumWidget
*fields
,
585 EnumWidget
*field
= fields
;
587 GtkWidget
*widget
= NULL
;
591 widget
= get_widget(owner
, field
->widget_id
);
597 handle_radio_button(widget
, field
->enum_id
, setting
, action
);
599 if (!field
->widget_id
)
602 if (g_slist_length(gtk_radio_button_get_group(GTK_RADIO_BUTTON(widget
))) != count
)
603 g_warning("Missing/invalid radio button widget IDs found!");
607 static void handle_widget_property(GtkWidget
*widget
, StashPref
*entry
,
610 GObject
*object
= G_OBJECT(widget
);
611 const gchar
*name
= entry
->fields
;
616 g_object_set(object
, name
, entry
->setting
, NULL
);
619 if (entry
->setting_type
== G_TYPE_STRING
)
620 g_free(entry
->setting
);
621 /* TODO: Which other types need freeing here? */
623 g_object_get(object
, name
, entry
->setting
, NULL
);
629 static void pref_action(PrefAction action
, StashGroup
*group
, GtkWidget
*owner
)
633 foreach_array(StashPref
, entry
, group
->entries
)
637 /* ignore settings with no widgets */
638 if (entry
->widget_type
== G_TYPE_NONE
)
641 /* radio buttons have several widgets */
642 if (entry
->widget_type
== GTK_TYPE_RADIO_BUTTON
)
644 handle_radio_buttons(owner
, entry
->fields
, entry
->setting
, action
);
648 widget
= get_widget(owner
, entry
->widget_id
);
651 g_warning("Unknown widget for %s::%s in %s()!", group
->name
, entry
->key_name
,
656 /* note: can't use switch for GTK_TYPE macros */
657 if (entry
->widget_type
== GTK_TYPE_TOGGLE_BUTTON
)
658 handle_toggle_button(widget
, entry
->setting
, action
);
659 else if (entry
->widget_type
== GTK_TYPE_SPIN_BUTTON
)
660 handle_spin_button(widget
, entry
, action
);
661 else if (entry
->widget_type
== GTK_TYPE_COMBO_BOX
)
662 handle_combo_box(widget
, entry
, action
);
663 else if (entry
->widget_type
== GTK_TYPE_COMBO_BOX_ENTRY
)
664 handle_combo_box_entry(widget
, entry
, action
);
665 else if (entry
->widget_type
== GTK_TYPE_ENTRY
)
666 handle_entry(widget
, entry
, action
);
667 else if (entry
->widget_type
== G_TYPE_PARAM
)
668 handle_widget_property(widget
, entry
, action
);
670 g_warning("Unhandled type for %s::%s in %s()!", group
->name
, entry
->key_name
,
676 /** Applies Stash settings to widgets, usually called before displaying the widgets.
677 * The @a owner argument depends on which type you use for @ref StashWidgetID.
679 * @param owner If non-NULL, used to lookup widgets by name, otherwise
680 * widget pointers are assumed.
681 * @see stash_group_update(). */
682 void stash_group_display(StashGroup
*group
, GtkWidget
*owner
)
684 pref_action(PREF_DISPLAY
, group
, owner
);
688 /** Applies widget values to Stash settings, usually called after displaying the widgets.
689 * The @a owner argument depends on which type you use for @ref StashWidgetID.
691 * @param owner If non-NULL, used to lookup widgets by name, otherwise
692 * widget pointers are assumed.
693 * @see stash_group_display(). */
694 void stash_group_update(StashGroup
*group
, GtkWidget
*owner
)
696 pref_action(PREF_UPDATE
, group
, owner
);
701 add_widget_pref(StashGroup
*group
, GType setting_type
, gpointer setting
,
702 const gchar
*key_name
, gpointer default_value
,
703 GType widget_type
, StashWidgetID widget_id
)
706 add_pref(group
, setting_type
, setting
, key_name
, default_value
);
708 entry
->widget_type
= widget_type
;
709 entry
->widget_id
= widget_id
;
714 /** Adds a @c GtkToggleButton (or @c GtkCheckButton) widget pref.
716 * @param setting Address of setting variable.
717 * @param key_name Name for key in a @c GKeyFile.
718 * @param default_value Value to use if the key doesn't exist when loading.
719 * @param widget_id @c GtkWidget pointer or string to lookup widget later.
720 * @see stash_group_add_radio_buttons(). */
721 void stash_group_add_toggle_button(StashGroup
*group
, gboolean
*setting
,
722 const gchar
*key_name
, gboolean default_value
, StashWidgetID widget_id
)
724 add_widget_pref(group
, G_TYPE_BOOLEAN
, setting
, key_name
, GINT_TO_POINTER(default_value
),
725 GTK_TYPE_TOGGLE_BUTTON
, widget_id
);
729 /** Adds a @c GtkRadioButton widget group pref.
731 * @param setting Address of setting variable.
732 * @param key_name Name for key in a @c GKeyFile.
733 * @param default_value Value to use if the key doesn't exist when loading.
734 * @param widget_id @c GtkWidget pointer or string to lookup widget later.
735 * @param enum_id Enum value for @a widget_id.
736 * @param ... pairs of @a widget_id, @a enum_id.
737 * Example (using widget lookup strings, but widget pointers can also work):
740 * stash_group_add_radio_buttons(group, &which_one_setting, "which_one", BAR,
741 * "radio_foo", FOO, "radio_bar", BAR, NULL);
743 void stash_group_add_radio_buttons(StashGroup
*group
, gint
*setting
,
744 const gchar
*key_name
, gint default_value
,
745 StashWidgetID widget_id
, gint enum_id
, ...)
748 add_widget_pref(group
, G_TYPE_INT
, setting
, key_name
, GINT_TO_POINTER(default_value
),
749 GTK_TYPE_RADIO_BUTTON
, NULL
);
752 EnumWidget
*item
, *array
;
754 /* count pairs of args */
755 va_start(args
, enum_id
);
758 if (!va_arg(args
, gpointer
))
765 array
= g_new0(EnumWidget
, count
+ 1);
766 entry
->fields
= array
;
768 va_start(args
, enum_id
);
769 foreach_c_array(item
, array
, count
)
774 item
->widget_id
= widget_id
;
775 item
->enum_id
= enum_id
;
779 item
->widget_id
= va_arg(args
, gpointer
);
780 item
->enum_id
= va_arg(args
, gint
);
787 /** Adds a @c GtkSpinButton widget pref.
789 * @param setting Address of setting variable.
790 * @param key_name Name for key in a @c GKeyFile.
791 * @param default_value Value to use if the key doesn't exist when loading.
792 * @param widget_id @c GtkWidget pointer or string to lookup widget later. */
793 void stash_group_add_spin_button_integer(StashGroup
*group
, gint
*setting
,
794 const gchar
*key_name
, gint default_value
, StashWidgetID widget_id
)
796 add_widget_pref(group
, G_TYPE_INT
, setting
, key_name
, GINT_TO_POINTER(default_value
),
797 GTK_TYPE_SPIN_BUTTON
, widget_id
);
801 /** Adds a @c GtkComboBox widget pref.
803 * @param setting Address of setting variable.
804 * @param key_name Name for key in a @c GKeyFile.
805 * @param default_value Value to use if the key doesn't exist when loading.
806 * @param widget_id @c GtkWidget pointer or string to lookup widget later.
807 * @see stash_group_add_combo_box_entry(). */
808 void stash_group_add_combo_box(StashGroup
*group
, gint
*setting
,
809 const gchar
*key_name
, gint default_value
, StashWidgetID widget_id
)
811 add_widget_pref(group
, G_TYPE_INT
, setting
, key_name
, GINT_TO_POINTER(default_value
),
812 GTK_TYPE_COMBO_BOX
, widget_id
);
816 /** Adds a @c GtkComboBoxEntry widget pref.
818 * @param setting Address of setting variable.
819 * @param key_name Name for key in a @c GKeyFile.
820 * @param default_value Value to use if the key doesn't exist when loading.
821 * @param widget_id @c GtkWidget pointer or string to lookup widget later. */
822 /* We could maybe also have something like stash_group_add_combo_box_entry_with_menu()
823 * for the history list - or should that be stored as a separate setting? */
824 void stash_group_add_combo_box_entry(StashGroup
*group
, gchar
**setting
,
825 const gchar
*key_name
, const gchar
*default_value
, StashWidgetID widget_id
)
827 add_widget_pref(group
, G_TYPE_STRING
, setting
, key_name
, (gpointer
)default_value
,
828 GTK_TYPE_COMBO_BOX_ENTRY
, widget_id
);
832 /** Adds a @c GtkEntry widget pref.
834 * @param setting Address of setting variable.
835 * @param key_name Name for key in a @c GKeyFile.
836 * @param default_value Value to use if the key doesn't exist when loading.
837 * @param widget_id @c GtkWidget pointer or string to lookup widget later. */
838 void stash_group_add_entry(StashGroup
*group
, gchar
**setting
,
839 const gchar
*key_name
, const gchar
*default_value
, StashWidgetID widget_id
)
841 add_widget_pref(group
, G_TYPE_STRING
, setting
, key_name
, (gpointer
)default_value
,
842 GTK_TYPE_ENTRY
, widget_id
);
846 static GType
object_get_property_type(GObject
*object
, const gchar
*property_name
)
848 GObjectClass
*klass
= G_OBJECT_GET_CLASS(object
);
851 ps
= g_object_class_find_property(klass
, property_name
);
852 return ps
->value_type
;
856 /** Adds a widget's read/write property to the stash group.
857 * The property will be set when calling
858 * stash_group_display(), and read when calling stash_group_update().
860 * @param setting Address of e.g. an integer if using an integer property.
861 * @param key_name Name for key in a @c GKeyFile.
862 * @param default_value Value to use if the key doesn't exist when loading.
863 * Should be cast into a pointer e.g. with @c GINT_TO_POINTER().
864 * @param widget_id @c GtkWidget pointer or string to lookup widget later.
865 * @param property_name .
866 * @param type can be @c 0 if passing a @c GtkWidget as the @a widget_id argument to look it up from the
868 * @warning Currently only string GValue properties will be freed before setting; patch for
869 * other types - see @c handle_widget_property(). */
870 void stash_group_add_widget_property(StashGroup
*group
, gpointer setting
,
871 const gchar
*key_name
, gpointer default_value
, StashWidgetID widget_id
,
872 const gchar
*property_name
, GType type
)
875 type
= object_get_property_type(G_OBJECT(widget_id
), property_name
);
877 add_widget_pref(group
, type
, setting
, key_name
, default_value
,
878 G_TYPE_PARAM
, widget_id
)->fields
= (gchar
*)property_name
;
890 struct StashTreeValue
894 const gchar
*key_name
;
895 const gchar
*group_name
;
898 typedef struct StashTreeValue StashTreeValue
;
901 static void stash_tree_renderer_set_data(GtkCellLayout
*cell_layout
, GtkCellRenderer
*cell
,
902 GtkTreeModel
*model
, GtkTreeIter
*iter
, gpointer user_data
)
904 GType cell_type
= GPOINTER_TO_SIZE(user_data
);
905 StashTreeValue
*value
;
906 gboolean matches_type
;
908 gtk_tree_model_get(model
, iter
, STASH_TREE_VALUE
, &value
, -1);
909 matches_type
= value
->setting_type
== cell_type
;
910 g_object_set(cell
, "visible", matches_type
, "sensitive", matches_type
,
911 cell_type
== G_TYPE_BOOLEAN
? "activatable" : "editable", matches_type
, NULL
);
915 switch (value
->setting_type
)
918 g_object_set(cell
, "active", GPOINTER_TO_INT(value
->setting
), NULL
);
922 gchar
*text
= g_strdup_printf("%d", GPOINTER_TO_INT(value
->setting
));
923 g_object_set(cell
, "text", text
, NULL
);
928 g_object_set(cell
, "text", (gchararray
) value
->setting
, NULL
);
935 static void stash_tree_renderer_edited(gchar
*path_str
, gchar
*new_text
, GtkTreeModel
*model
)
939 StashTreeValue
*value
;
941 path
= gtk_tree_path_new_from_string(path_str
);
942 gtk_tree_model_get_iter(model
, &iter
, path
);
943 gtk_tree_model_get(model
, &iter
, STASH_TREE_VALUE
, &value
, -1);
945 switch (value
->setting_type
)
948 value
->setting
= GINT_TO_POINTER(!GPOINTER_TO_INT(value
->setting
));
951 value
->setting
= GINT_TO_POINTER(atoi(new_text
));
954 g_free(value
->setting
);
955 value
->setting
= g_strdup(new_text
);
959 gtk_tree_model_row_changed(model
, path
, &iter
);
960 gtk_tree_path_free(path
);
964 static void stash_tree_boolean_toggled(GtkCellRendererToggle
*cell
, gchar
*path_str
,
967 stash_tree_renderer_edited(path_str
, NULL
, model
);
971 static void stash_tree_string_edited(GtkCellRenderer
*cell
, gchar
*path_str
, gchar
*new_text
,
974 stash_tree_renderer_edited(path_str
, new_text
, model
);
978 static gboolean
stash_tree_discard_value(GtkTreeModel
*model
, GtkTreePath
*path
,
979 GtkTreeIter
*iter
, gpointer user_data
)
981 StashTreeValue
*value
;
983 gtk_tree_model_get(model
, iter
, STASH_TREE_VALUE
, &value
, -1);
984 if (value
->setting_type
== G_TYPE_STRING
)
985 g_free(value
->setting
);
992 static void stash_tree_destroy_cb(GtkWidget
*widget
, gpointer user_data
)
994 GtkTreeModel
*model
= gtk_tree_view_get_model(GTK_TREE_VIEW(widget
));
995 gtk_tree_model_foreach(model
, stash_tree_discard_value
, NULL
);
999 typedef void (*stash_foreach_pref_func
)(StashGroup
*group
, StashPref
*entry
, gpointer container
,
1002 static void stash_foreach_various_pref(GPtrArray
*group_array
, stash_foreach_pref_func pref_func
,
1003 gpointer container
, PrefAction action
)
1009 foreach_ptr_array(group
, i
, group_array
)
1013 foreach_array(StashPref
, entry
, group
->entries
)
1014 pref_func(group
, entry
, container
, action
);
1020 static void stash_tree_append_pref(StashGroup
*group
, StashPref
*entry
, GtkListStore
*store
,
1024 StashTreeValue
*value
;
1026 value
= g_new(StashTreeValue
, 1);
1028 value
->setting_type
= entry
->setting_type
;
1029 value
->setting
= NULL
;
1030 value
->key_name
= entry
->key_name
;
1031 value
->group_name
= group
->name
;
1033 gtk_list_store_append(store
, &iter
);
1034 gtk_list_store_set(store
, &iter
, STASH_TREE_NAME
, value
->key_name
,
1035 STASH_TREE_VALUE
, value
, -1);
1039 /* Setups a simple editor for stash preferences based on the widget arguments.
1040 * group_array - Array of groups which's settings will be edited.
1041 * tree - GtkTreeView in which to edit the preferences. Must be empty. */
1042 void stash_tree_setup(GPtrArray
*group_array
, GtkTreeView
*tree
)
1044 GtkListStore
*store
;
1045 GtkTreeModel
*model
;
1046 GtkCellRenderer
*cell
;
1047 GtkTreeViewColumn
*column
;
1048 GtkObject
*adjustment
;
1050 store
= gtk_list_store_new(STASH_TREE_COUNT
, G_TYPE_STRING
, G_TYPE_POINTER
);
1051 stash_foreach_various_pref(group_array
,
1052 (stash_foreach_pref_func
) stash_tree_append_pref
, store
, PREF_DISPLAY
);
1053 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store
), STASH_TREE_NAME
,
1054 GTK_SORT_ASCENDING
);
1056 model
= GTK_TREE_MODEL(store
);
1057 gtk_tree_view_set_model(tree
, model
);
1058 g_object_unref(G_OBJECT(store
));
1059 g_signal_connect(tree
, "destroy", G_CALLBACK(stash_tree_destroy_cb
), NULL
);
1061 cell
= gtk_cell_renderer_text_new();
1062 column
= gtk_tree_view_column_new_with_attributes(_("Name"), cell
, "text",
1063 STASH_TREE_NAME
, NULL
);
1064 gtk_tree_view_column_set_sort_column_id(column
, STASH_TREE_NAME
);
1065 gtk_tree_view_column_set_sort_indicator(column
, TRUE
);
1066 gtk_tree_view_append_column(tree
, column
);
1068 column
= gtk_tree_view_column_new();
1069 gtk_tree_view_column_set_title(column
, _("Value"));
1070 gtk_tree_view_append_column(tree
, column
);
1071 /* boolean renderer */
1072 cell
= gtk_cell_renderer_toggle_new();
1073 g_signal_connect(cell
, "toggled", G_CALLBACK(stash_tree_boolean_toggled
), model
);
1074 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(column
), cell
, FALSE
);
1075 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(column
), cell
,
1076 stash_tree_renderer_set_data
, GSIZE_TO_POINTER(G_TYPE_BOOLEAN
), NULL
);
1077 /* string renderer */
1078 cell
= gtk_cell_renderer_text_new();
1079 g_object_set(cell
, "ellipsize", PANGO_ELLIPSIZE_END
, NULL
);
1080 g_signal_connect(cell
, "edited", G_CALLBACK(stash_tree_string_edited
), model
);
1081 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(column
), cell
, TRUE
);
1082 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(column
), cell
,
1083 stash_tree_renderer_set_data
, GSIZE_TO_POINTER(G_TYPE_STRING
), NULL
);
1084 /* integer renderer */
1085 cell
= gtk_cell_renderer_spin_new();
1086 adjustment
= gtk_adjustment_new(0, G_MININT
, G_MAXINT
, 1, 10, 0);
1087 g_object_set(cell
, "adjustment", adjustment
, NULL
);
1088 g_signal_connect(cell
, "edited", G_CALLBACK(stash_tree_string_edited
), model
);
1089 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(column
), cell
, FALSE
);
1090 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(column
), cell
,
1091 stash_tree_renderer_set_data
, GSIZE_TO_POINTER(G_TYPE_INT
), NULL
);
1095 static void stash_tree_display_pref(StashTreeValue
*value
, StashPref
*entry
)
1097 switch (entry
->setting_type
)
1099 case G_TYPE_BOOLEAN
:
1100 value
->setting
= GINT_TO_POINTER(*(gboolean
*) entry
->setting
);
1103 value
->setting
= GINT_TO_POINTER(*(gint
*) entry
->setting
);
1107 g_free(value
->setting
);
1108 value
->setting
= g_strdup(*(gchararray
*) entry
->setting
);
1112 g_warning("Unhandled type for %s::%s in %s()!", value
->group_name
,
1113 entry
->key_name
, G_STRFUNC
);
1118 static void stash_tree_update_pref(StashTreeValue
*value
, StashPref
*entry
)
1120 gpointer
*setting
= value
->setting
;
1122 switch (entry
->setting_type
)
1124 case G_TYPE_BOOLEAN
:
1125 *(gboolean
*) entry
->setting
= GPOINTER_TO_INT(setting
);
1128 *(gint
*) entry
->setting
= GPOINTER_TO_INT(setting
);
1132 gchararray
*text
= entry
->setting
;
1134 *text
= g_strdup((gchararray
) setting
);
1138 g_warning("Unhandled type for %s::%s in %s()!", value
->group_name
,
1139 value
->key_name
, G_STRFUNC
);
1143 /* These functions can handle about 200 settings on a 1GHz x86 CPU in ~0.06 seconds.
1144 * For 250+ settings, you'd better write something more efficient. */
1145 static void stash_tree_handle_pref(StashGroup
*group
, StashPref
*entry
, GtkTreeModel
*model
,
1149 gboolean valid
= gtk_tree_model_get_iter_first(model
, &iter
);
1150 StashTreeValue
*value
;
1154 gtk_tree_model_get(model
, &iter
, STASH_TREE_VALUE
, &value
, -1);
1156 if (strcmp(group
->name
, value
->group_name
) == 0 &&
1157 strcmp(entry
->key_name
, value
->key_name
) == 0)
1162 stash_tree_display_pref(value
, entry
);
1165 stash_tree_update_pref(value
, entry
);
1172 valid
= gtk_tree_model_iter_next(model
, &iter
);
1177 void stash_tree_display(GPtrArray
*group_array
, GtkTreeView
*tree
)
1179 stash_foreach_various_pref(group_array
, (stash_foreach_pref_func
) stash_tree_handle_pref
,
1180 gtk_tree_view_get_model(tree
), PREF_DISPLAY
);
1184 void stash_tree_update(GPtrArray
*group_array
, GtkTreeView
*tree
)
1186 stash_foreach_various_pref(group_array
, (stash_foreach_pref_func
) stash_tree_handle_pref
,
1187 gtk_tree_view_get_model(tree
), PREF_UPDATE
);