Add note to ui_hookup_widget() doc comments.
[geany-mirror.git] / src / stash.c
blob8b77e5f3d500ccb5e7b720032ae89d6887f65bcf
1 /*
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,
20 * MA 02110-1301, USA.
23 /**
24 * @file stash.h
25 * Lightweight library for reading/writing @c GKeyFile settings and synchronizing widgets with
26 * C variables.
28 * Note: Stash should only depend on GLib and GTK, but currently has some minor
29 * dependencies on Geany's utils.c.
31 * @section Terms
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
58 * just data settings.
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. */
91 #include "stash.h"
94 struct StashPref
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;
107 struct StashGroup
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;
118 gint enum_id;
120 EnumWidget;
123 typedef enum SettingAction
125 SETTING_READ,
126 SETTING_WRITE
128 SettingAction;
130 typedef enum PrefAction
132 PREF_DISPLAY,
133 PREF_UPDATE
135 PrefAction;
138 static void handle_boolean_setting(StashGroup *group, StashPref *se,
139 GKeyFile *config, SettingAction action)
141 gboolean *setting = se->setting;
143 switch (action)
145 case SETTING_READ:
146 *setting = utils_get_setting_boolean(config, group->name, se->key_name,
147 GPOINTER_TO_INT(se->default_value));
148 break;
149 case SETTING_WRITE:
150 g_key_file_set_boolean(config, group->name, se->key_name, *setting);
151 break;
156 static void handle_integer_setting(StashGroup *group, StashPref *se,
157 GKeyFile *config, SettingAction action)
159 gint *setting = se->setting;
161 switch (action)
163 case SETTING_READ:
164 *setting = utils_get_setting_integer(config, group->name, se->key_name,
165 GPOINTER_TO_INT(se->default_value));
166 break;
167 case SETTING_WRITE:
168 g_key_file_set_integer(config, group->name, se->key_name, *setting);
169 break;
174 static void handle_string_setting(StashGroup *group, StashPref *se,
175 GKeyFile *config, SettingAction action)
177 gchararray *setting = se->setting;
179 switch (action)
181 case SETTING_READ:
182 g_free(*setting);
183 *setting = utils_get_setting_string(config, group->name, se->key_name,
184 se->default_value);
185 break;
186 case SETTING_WRITE:
187 g_key_file_set_string(config, group->name, se->key_name,
188 *setting ? *setting : "");
189 break;
194 static void handle_strv_setting(StashGroup *group, StashPref *se,
195 GKeyFile *config, SettingAction action)
197 gchararray **setting = se->setting;
199 switch (action)
201 case SETTING_READ:
202 g_strfreev(*setting);
203 *setting = g_key_file_get_string_list(config, group->name, se->key_name,
204 NULL, NULL);
205 if (*setting == NULL)
206 *setting = g_strdupv(se->default_value);
207 break;
209 case SETTING_WRITE:
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));
217 break;
223 static void keyfile_action(SettingAction action, StashGroup *group, GKeyFile *keyfile)
225 StashPref *entry;
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))
232 continue;
234 switch (entry->setting_type)
236 case G_TYPE_BOOLEAN:
237 handle_boolean_setting(group, entry, keyfile, action); break;
238 case G_TYPE_INT:
239 handle_integer_setting(group, entry, keyfile, action); break;
240 case G_TYPE_STRING:
241 handle_string_setting(group, entry, keyfile, action); break;
242 default:
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);
246 else
247 g_warning("Unhandled type for %s::%s in %s()!", group->name, entry->key_name,
248 G_STRFUNC);
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.
257 * @param group .
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.
267 * @param group .
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.
278 * @param group .
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)
285 GKeyFile *keyfile;
286 gboolean ret;
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);
294 return ret;
298 /** Writes group settings to a configuration file using @c GKeyFile.
300 * @param group .
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,
308 GKeyFileFlags flags)
310 GKeyFile *keyfile;
311 gchar *data;
312 gint ret;
314 keyfile = g_key_file_new();
315 /* if we need to keep comments or translations, try to load first */
316 if (flags)
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);
322 g_free(data);
323 g_key_file_free(keyfile);
324 return ret;
328 /** Creates a new group.
329 * @param name Name used for @c GKeyFile group.
330 * @return Group. */
331 StashGroup *stash_group_new(const gchar *name)
333 StashGroup *group = g_new0(StashGroup, 1);
335 group->name = name;
336 group->entries = g_array_new(FALSE, FALSE, sizeof(StashPref));
337 group->use_defaults = TRUE;
338 return group;
342 /** Frees a group.
343 * @param group . */
344 void stash_group_free(StashGroup *group)
346 StashPref *entry;
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)
353 continue;
354 else
355 g_assert(entry->fields == NULL); /* to prevent memory leaks, must handle fields above */
357 g_array_free(group->entries, TRUE);
358 g_free(group);
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;
379 static StashPref *
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 ||
388 type == G_TYPE_STRV)
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.
399 * @param group .
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.
411 * @param group .
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.
424 * @param group .
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.
437 * @param group .
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,
451 PrefAction action)
453 switch (action)
455 case PREF_DISPLAY:
456 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), *setting);
457 break;
458 case PREF_UPDATE:
459 *setting = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
460 break;
465 static void handle_spin_button(GtkWidget *widget, StashPref *entry,
466 PrefAction action)
468 gint *setting = entry->setting;
470 g_assert(entry->setting_type == G_TYPE_INT); /* only int spin prefs */
472 switch (action)
474 case PREF_DISPLAY:
475 gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget), *setting);
476 break;
477 case PREF_UPDATE:
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));
481 break;
486 static void handle_combo_box(GtkWidget *widget, StashPref *entry,
487 PrefAction action)
489 gint *setting = entry->setting;
491 switch (action)
493 case PREF_DISPLAY:
494 gtk_combo_box_set_active(GTK_COMBO_BOX(widget), *setting);
495 break;
496 case PREF_UPDATE:
497 *setting = gtk_combo_box_get_active(GTK_COMBO_BOX(widget));
498 break;
503 static void handle_entry(GtkWidget *widget, StashPref *entry,
504 PrefAction action)
506 gchararray *setting = entry->setting;
508 switch (action)
510 case PREF_DISPLAY:
511 gtk_entry_set_text(GTK_ENTRY(widget), *setting);
512 break;
513 case PREF_UPDATE:
514 g_free(*setting);
515 *setting = g_strdup(gtk_entry_get_text(GTK_ENTRY(widget)));
516 break;
521 static void handle_combo_box_entry(GtkWidget *widget, StashPref *entry,
522 PrefAction action)
524 widget = gtk_bin_get_child(GTK_BIN(widget));
525 handle_entry(widget, entry, action);
529 /* FIXME */
530 /* taken from Glade 2.x generated support.c */
531 static GtkWidget*
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));
539 if (!found_widget)
540 g_warning ("Widget not found: %s", widget_name);
541 return found_widget;
545 static GtkWidget *
546 get_widget(GtkWidget *owner, StashWidgetID widget_id)
548 GtkWidget *widget;
550 if (owner)
551 widget = lookup_widget(owner, (const gchar *)widget_id);
552 else
553 widget = (GtkWidget *)widget_id;
555 if (!GTK_IS_WIDGET(widget))
557 g_warning("Unknown widget in %s()!", G_STRFUNC);
558 return NULL;
560 return widget;
564 static void handle_radio_button(GtkWidget *widget, gint enum_id, gboolean *setting,
565 PrefAction action)
567 switch (action)
569 case PREF_DISPLAY:
570 if (*setting == enum_id)
571 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), TRUE);
572 break;
573 case PREF_UPDATE:
574 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
575 *setting = enum_id;
576 break;
581 static void handle_radio_buttons(GtkWidget *owner, EnumWidget *fields,
582 gboolean *setting,
583 PrefAction action)
585 EnumWidget *field = fields;
586 gsize count = 0;
587 GtkWidget *widget = NULL;
589 while (1)
591 widget = get_widget(owner, field->widget_id);
593 if (!widget)
594 continue;
596 count++;
597 handle_radio_button(widget, field->enum_id, setting, action);
598 field++;
599 if (!field->widget_id)
600 break;
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,
608 PrefAction action)
610 GObject *object = G_OBJECT(widget);
611 const gchar *name = entry->fields;
613 switch (action)
615 case PREF_DISPLAY:
616 g_object_set(object, name, entry->setting, NULL);
617 break;
618 case PREF_UPDATE:
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);
624 break;
629 static void pref_action(PrefAction action, StashGroup *group, GtkWidget *owner)
631 StashPref *entry;
633 foreach_array(StashPref, entry, group->entries)
635 GtkWidget *widget;
637 /* ignore settings with no widgets */
638 if (entry->widget_type == G_TYPE_NONE)
639 continue;
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);
645 continue;
648 widget = get_widget(owner, entry->widget_id);
649 if (!widget)
651 g_warning("Unknown widget for %s::%s in %s()!", group->name, entry->key_name,
652 G_STRFUNC);
653 continue;
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);
669 else
670 g_warning("Unhandled type for %s::%s in %s()!", group->name, entry->key_name,
671 G_STRFUNC);
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.
678 * @param group .
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.
690 * @param group .
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);
700 static StashPref *
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)
705 StashPref *entry =
706 add_pref(group, setting_type, setting, key_name, default_value);
708 entry->widget_type = widget_type;
709 entry->widget_id = widget_id;
710 return entry;
714 /** Adds a @c GtkToggleButton (or @c GtkCheckButton) widget pref.
715 * @param group .
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.
730 * @param group .
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):
738 * @code
739 * enum {FOO, BAR};
740 * stash_group_add_radio_buttons(group, &which_one_setting, "which_one", BAR,
741 * "radio_foo", FOO, "radio_bar", BAR, NULL);
742 * @endcode */
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, ...)
747 StashPref *entry =
748 add_widget_pref(group, G_TYPE_INT, setting, key_name, GINT_TO_POINTER(default_value),
749 GTK_TYPE_RADIO_BUTTON, NULL);
750 va_list args;
751 gsize count = 1;
752 EnumWidget *item, *array;
754 /* count pairs of args */
755 va_start(args, enum_id);
756 while (1)
758 if (!va_arg(args, gpointer))
759 break;
760 va_arg(args, gint);
761 count++;
763 va_end(args);
765 array = g_new0(EnumWidget, count + 1);
766 entry->fields = array;
768 va_start(args, enum_id);
769 foreach_c_array(item, array, count)
771 if (item == array)
773 /* first element */
774 item->widget_id = widget_id;
775 item->enum_id = enum_id;
777 else
779 item->widget_id = va_arg(args, gpointer);
780 item->enum_id = va_arg(args, gint);
783 va_end(args);
787 /** Adds a @c GtkSpinButton widget pref.
788 * @param group .
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.
802 * @param group .
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.
817 * @param group .
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.
833 * @param group .
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);
849 GParamSpec *ps;
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().
859 * @param group .
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
867 * @c GObject data.
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)
874 if (!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;
882 enum
884 STASH_TREE_NAME,
885 STASH_TREE_VALUE,
886 STASH_TREE_COUNT
890 struct StashTreeValue
892 GType setting_type;
893 gpointer setting;
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);
913 if (matches_type)
915 switch (value->setting_type)
917 case G_TYPE_BOOLEAN:
918 g_object_set(cell, "active", GPOINTER_TO_INT(value->setting), NULL);
919 break;
920 case G_TYPE_INT:
922 gchar *text = g_strdup_printf("%d", GPOINTER_TO_INT(value->setting));
923 g_object_set(cell, "text", text, NULL);
924 g_free(text);
925 break;
927 case G_TYPE_STRING:
928 g_object_set(cell, "text", (gchararray) value->setting, NULL);
929 break;
935 static void stash_tree_renderer_edited(gchar *path_str, gchar *new_text, GtkTreeModel *model)
937 GtkTreePath *path;
938 GtkTreeIter iter;
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)
947 case G_TYPE_BOOLEAN:
948 value->setting = GINT_TO_POINTER(!GPOINTER_TO_INT(value->setting));
949 break;
950 case G_TYPE_INT:
951 value->setting = GINT_TO_POINTER(atoi(new_text));
952 break;
953 case G_TYPE_STRING:
954 g_free(value->setting);
955 value->setting = g_strdup(new_text);
956 break;
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,
965 GtkTreeModel *model)
967 stash_tree_renderer_edited(path_str, NULL, model);
971 static void stash_tree_string_edited(GtkCellRenderer *cell, gchar *path_str, gchar *new_text,
972 GtkTreeModel *model)
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);
986 g_free(value);
988 return FALSE;
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,
1000 PrefAction action);
1002 static void stash_foreach_various_pref(GPtrArray *group_array, stash_foreach_pref_func pref_func,
1003 gpointer container, PrefAction action)
1005 StashGroup *group;
1006 guint i;
1007 StashPref *entry;
1009 foreach_ptr_array(group, i, group_array)
1011 if (group->various)
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,
1021 PrefAction action)
1023 GtkTreeIter iter;
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);
1101 break;
1102 case G_TYPE_INT:
1103 value->setting = GINT_TO_POINTER(*(gint *) entry->setting);
1104 break;
1105 case G_TYPE_STRING:
1107 g_free(value->setting);
1108 value->setting = g_strdup(*(gchararray *) entry->setting);
1109 break;
1111 default:
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);
1126 break;
1127 case G_TYPE_INT:
1128 *(gint *) entry->setting = GPOINTER_TO_INT(setting);
1129 break;
1130 case G_TYPE_STRING:
1132 gchararray *text = entry->setting;
1133 g_free(*text);
1134 *text = g_strdup((gchararray) setting);
1135 break;
1137 default:
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,
1146 PrefAction action)
1148 GtkTreeIter iter;
1149 gboolean valid = gtk_tree_model_get_iter_first(model, &iter);
1150 StashTreeValue *value;
1152 while (valid)
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)
1159 switch (action)
1161 case PREF_DISPLAY:
1162 stash_tree_display_pref(value, entry);
1163 break;
1164 case PREF_UPDATE:
1165 stash_tree_update_pref(value, entry);
1166 break;
1169 break;
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);