Add 'Refresh' popup menu item (part of geany-plugins #2999858).
[geany-mirror.git] / src / stash.c
blob21a5ce7307df9a41c4faeaf0ec140c2b2fa09794
1 /*
2 * stash.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2008-2010 Nick Treleaven <nick(dot)treleaven(at)btinternet(dot)com>
5 * Copyright 2008-2010 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.
22 * $Id$
25 /**
26 * @file stash.h
27 * Lightweight library for reading/writing @c GKeyFile settings and synchronizing widgets with
28 * C variables.
30 * Note: Stash should only depend on GLib and GTK, but currently has some minor
31 * dependencies on Geany's utils.c.
33 * @section Terms
34 * 'Setting' is used only for data stored on disk or in memory.
35 * 'Pref' can also include visual widget information.
37 * @section Memory Usage
38 * Stash will not duplicate strings if they are normally static arrays, such as
39 * keyfile group names and key names, string default values, widget_id names, property names.
41 * @section String Settings
42 * String settings and other dynamically allocated settings will be initialized to NULL when
43 * added to a StashGroup (so they can safely be reassigned later).
45 * @section Widget Support
46 * Widgets very commonly used in configuration dialogs will be supported with their own function.
47 * Widgets less commonly used such as @c GtkExpander or widget settings that aren't commonly needed
48 * to be persistent won't be directly supported, to keep the library lightweight. However, you can
49 * use stash_group_add_widget_property() to also save these settings for any read/write widget
50 * property. Macros could be added for common widget properties such as @c GtkExpander:"expanded".
52 * @section settings-example Settings 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 add widget prefs instead of e.g.
58 * boolean settings.
60 * This example uses lookup strings for widgets as they are more flexible than widget pointers.
61 * @code
62 gboolean want_handle;
63 GtkWidget *parent;
64 GtkWidget *my_check_button;
66 stash_group_add_toggle_button(group, &want_handle, "handle", TRUE, "check_handle");
67 ...
68 gtk_container_add(GTK_CONTAINER(parent), my_check_button);
69 ui_hookup_widget(parent, my_check_button, "check_handle");
70 ...
71 stash_group_display(group, parent);
72 * @endcode
73 * Now let the user manipulate widgets. To synchronize the Stash settings afterwards, call:
74 * @code
75 stash_group_update(group, parent);
76 * @endcode
79 /* Implementation Note
80 * We use a GArray to hold prefs. It would be more efficient for user code to declare
81 * a static array of StashPref structs, but we don't do this because:
83 * * It would be more ugly (lots of casts and NULLs).
84 * * Less type checking.
85 * * The API would have to break when adding/changing fields.
87 * Usually the prefs code isn't what user code will spend most of its time doing, so this
88 * should be efficient enough. But, if desired we could add a stash_group_set_size() function
89 * to reduce reallocation.
91 * TODO: Maybe using GSlice chunks with an extra 'next' pointer would be more (memory) efficient.
95 #include <gtk/gtk.h>
97 #include "stash.h"
98 #include "utils.h" /* only for foreach_*, utils_get_setting_*(). Stash should not depend on Geany. */
101 struct StashPref
103 GType setting_type; /* e.g. G_TYPE_INT */
104 gpointer setting;
105 const gchar *key_name;
106 gpointer default_value;
107 GType widget_type; /* e.g. GTK_TYPE_TOGGLE_BUTTON */
108 StashWidgetID widget_id; /* (GtkWidget*) or (gchar*) */
109 gpointer fields; /* extra fields */
112 typedef struct StashPref StashPref;
114 struct StashGroup
116 const gchar *name; /* group name to use in the keyfile */
117 GArray *entries; /* array of StashPref */
118 gboolean write_once; /* only write settings if they don't already exist */
119 gboolean use_defaults; /* use default values if there's no keyfile entry */
122 typedef struct EnumWidget
124 StashWidgetID widget_id;
125 gint enum_id;
127 EnumWidget;
130 typedef enum SettingAction
132 SETTING_READ,
133 SETTING_WRITE
135 SettingAction;
137 typedef enum PrefAction
139 PREF_DISPLAY,
140 PREF_UPDATE
142 PrefAction;
145 static void handle_boolean_setting(StashGroup *group, StashPref *se,
146 GKeyFile *config, SettingAction action)
148 gboolean *setting = se->setting;
150 switch (action)
152 case SETTING_READ:
153 *setting = utils_get_setting_boolean(config, group->name, se->key_name,
154 GPOINTER_TO_INT(se->default_value));
155 break;
156 case SETTING_WRITE:
157 g_key_file_set_boolean(config, group->name, se->key_name, *setting);
158 break;
163 static void handle_integer_setting(StashGroup *group, StashPref *se,
164 GKeyFile *config, SettingAction action)
166 gboolean *setting = se->setting;
168 switch (action)
170 case SETTING_READ:
171 *setting = utils_get_setting_integer(config, group->name, se->key_name,
172 GPOINTER_TO_INT(se->default_value));
173 break;
174 case SETTING_WRITE:
175 g_key_file_set_integer(config, group->name, se->key_name, *setting);
176 break;
181 static void handle_string_setting(StashGroup *group, StashPref *se,
182 GKeyFile *config, SettingAction action)
184 gchararray *setting = se->setting;
186 switch (action)
188 case SETTING_READ:
189 g_free(*setting);
190 *setting = utils_get_setting_string(config, group->name, se->key_name,
191 se->default_value);
192 break;
193 case SETTING_WRITE:
194 g_key_file_set_string(config, group->name, se->key_name,
195 *setting ? *setting : "");
196 break;
201 static void handle_strv_setting(StashGroup *group, StashPref *se,
202 GKeyFile *config, SettingAction action)
204 gchararray **setting = se->setting;
206 switch (action)
208 case SETTING_READ:
209 g_strfreev(*setting);
210 *setting = g_key_file_get_string_list(config, group->name, se->key_name,
211 NULL, NULL);
212 if (*setting == NULL)
213 *setting = g_strdupv(se->default_value);
214 break;
216 case SETTING_WRITE:
218 /* don't try to save a NULL string vector */
219 gchar *dummy[] = { "", NULL };
220 gchar **strv = *setting ? *setting : dummy;
222 g_key_file_set_string_list(config, group->name, se->key_name,
223 (const gchar**)strv, g_strv_length(strv));
224 break;
230 static void keyfile_action(SettingAction action, StashGroup *group, GKeyFile *keyfile)
232 StashPref *entry;
234 foreach_array(StashPref, entry, group->entries)
236 /* don't overwrite write_once prefs */
237 if (group->write_once && action == SETTING_WRITE &&
238 g_key_file_has_key(keyfile, group->name, entry->key_name, NULL))
239 continue;
240 /* don't override settings with default values */
241 if (!group->use_defaults && action == SETTING_READ &&
242 !g_key_file_has_key(keyfile, group->name, entry->key_name, NULL))
243 continue;
245 switch (entry->setting_type)
247 case G_TYPE_BOOLEAN:
248 handle_boolean_setting(group, entry, keyfile, action); break;
249 case G_TYPE_INT:
250 handle_integer_setting(group, entry, keyfile, action); break;
251 case G_TYPE_STRING:
252 handle_string_setting(group, entry, keyfile, action); break;
253 default:
254 /* G_TYPE_STRV is not a constant */
255 if (entry->setting_type == G_TYPE_STRV)
256 handle_strv_setting(group, entry, keyfile, action);
257 else
258 g_warning("Unhandled type for %s::%s in %s()!", group->name, entry->key_name,
259 G_STRFUNC);
265 /** Reads key values from @a keyfile into the group settings.
266 * @note You should still call this even if the keyfile couldn't be loaded from disk
267 * so that all Stash settings are initialized to defaults.
268 * @param group .
269 * @param keyfile Usually loaded from a configuration file first. */
270 void stash_group_load_from_key_file(StashGroup *group, GKeyFile *keyfile)
272 keyfile_action(SETTING_READ, group, keyfile);
276 /** Writes group settings into key values in @a keyfile.
277 * @a keyfile is usually written to a configuration file afterwards.
278 * @param group .
279 * @param keyfile . */
280 void stash_group_save_to_key_file(StashGroup *group, GKeyFile *keyfile)
282 keyfile_action(SETTING_WRITE, group, keyfile);
286 /** Reads group settings from a configuration file using @c GKeyFile.
287 * @note Stash settings will be initialized to defaults if the keyfile
288 * couldn't be loaded from disk.
289 * @param group .
290 * @param filename Filename of the file to read, in locale encoding.
291 * @return @c TRUE if a key file could be loaded.
292 * @see stash_group_load_from_key_file().
294 gboolean stash_group_load_from_file(StashGroup *group, const gchar *filename)
296 GKeyFile *keyfile;
297 gboolean ret;
299 keyfile = g_key_file_new();
300 ret = g_key_file_load_from_file(keyfile, filename, 0, NULL);
301 /* even on failure we load settings to apply defaults */
302 stash_group_load_from_key_file(group, keyfile);
304 g_key_file_free(keyfile);
305 return ret;
309 /** Writes group settings to a configuration file using @c GKeyFile.
311 * @param group .
312 * @param filename Filename of the file to write, in locale encoding.
313 * @param flags Keyfile options - @c G_KEY_FILE_NONE is the most efficient.
314 * @return 0 if the file was successfully written, otherwise the @c errno of the
315 * failed operation is returned.
316 * @see stash_group_save_to_key_file().
318 gint stash_group_save_to_file(StashGroup *group, const gchar *filename,
319 GKeyFileFlags flags)
321 GKeyFile *keyfile;
322 gchar *data;
323 gint ret;
325 keyfile = g_key_file_new();
326 /* if we need to keep comments or translations, try to load first */
327 if (flags)
328 g_key_file_load_from_file(keyfile, filename, flags, NULL);
330 stash_group_save_to_key_file(group, keyfile);
331 data = g_key_file_to_data(keyfile, NULL, NULL);
332 ret = utils_write_file(filename, data);
333 g_free(data);
334 g_key_file_free(keyfile);
335 return ret;
339 /** Creates a new group.
340 * @param name Name used for @c GKeyFile group.
341 * @return Group. */
342 StashGroup *stash_group_new(const gchar *name)
344 StashGroup *group = g_new0(StashGroup, 1);
346 group->name = name;
347 group->entries = g_array_new(FALSE, FALSE, sizeof(StashPref));
348 group->use_defaults = TRUE;
349 return group;
353 /** Frees a group.
354 * @param group . */
355 void stash_group_free(StashGroup *group)
357 StashPref *entry;
359 foreach_array(StashPref, entry, group->entries)
361 if (entry->widget_type == GTK_TYPE_RADIO_BUTTON)
362 g_free(entry->fields);
363 else if (entry->widget_type == G_TYPE_PARAM)
364 continue;
365 else
366 g_assert(entry->fields == NULL); /* to prevent memory leaks, must handle fields above */
368 g_array_free(group->entries, TRUE);
369 g_free(group);
373 /* Useful so the user can edit the keyfile manually while the program is running,
374 * and the setting won't be overridden.
375 * @c FALSE by default. */
376 void stash_group_set_write_once(StashGroup *group, gboolean write_once)
378 group->write_once = write_once;
382 /* When @c FALSE, Stash doesn't change the setting if there is no keyfile entry, so it
383 * remains whatever it was initialized/set to by user code.
384 * @c TRUE by default. */
385 void stash_group_set_use_defaults(StashGroup *group, gboolean use_defaults)
387 group->use_defaults = use_defaults;
391 static StashPref *
392 add_pref(StashGroup *group, GType type, gpointer setting,
393 const gchar *key_name, gpointer default_value)
395 StashPref entry = {type, setting, key_name, default_value, G_TYPE_NONE, NULL, NULL};
396 GArray *array = group->entries;
398 /* init any pointer settings to NULL so they can be freed later */
399 if (type == G_TYPE_STRING ||
400 type == G_TYPE_STRV)
401 if (group->use_defaults)
402 *(gpointer**)setting = NULL;
404 g_array_append_val(array, entry);
406 return &g_array_index(array, StashPref, array->len - 1);
410 /** Adds boolean 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_boolean(StashGroup *group, gboolean *setting,
416 const gchar *key_name, gboolean default_value)
418 add_pref(group, G_TYPE_BOOLEAN, setting, key_name, GINT_TO_POINTER(default_value));
422 /** Adds integer setting.
423 * @param group .
424 * @param setting Address of setting variable.
425 * @param key_name Name for key in a @c GKeyFile.
426 * @param default_value Value to use if the key doesn't exist when loading. */
427 void stash_group_add_integer(StashGroup *group, gint *setting,
428 const gchar *key_name, gint default_value)
430 add_pref(group, G_TYPE_INT, setting, key_name, GINT_TO_POINTER(default_value));
434 /** Adds string setting.
435 * The contents of @a setting will be initialized to @c NULL.
436 * @param group .
437 * @param setting Address of setting variable.
438 * @param key_name Name for key in a @c GKeyFile.
439 * @param default_value String to copy if the key doesn't exist when loading, or @c NULL. */
440 void stash_group_add_string(StashGroup *group, gchar **setting,
441 const gchar *key_name, const gchar *default_value)
443 add_pref(group, G_TYPE_STRING, setting, key_name, (gpointer)default_value);
447 /** Adds string vector setting (array of strings).
448 * The contents of @a setting will be initialized to @c NULL.
449 * @param group .
450 * @param setting Address of setting variable.
451 * @param key_name Name for key in a @c GKeyFile.
452 * @param default_value Vector to copy if the key doesn't exist when loading. Usually @c NULL. */
453 void stash_group_add_string_vector(StashGroup *group, gchar ***setting,
454 const gchar *key_name, const gchar **default_value)
456 add_pref(group, G_TYPE_STRV, setting, key_name, (gpointer)default_value);
460 /* *** GTK-related functions *** */
462 static void handle_toggle_button(GtkWidget *widget, gboolean *setting,
463 PrefAction action)
465 switch (action)
467 case PREF_DISPLAY:
468 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), *setting);
469 break;
470 case PREF_UPDATE:
471 *setting = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
472 break;
477 static void handle_spin_button(GtkWidget *widget, StashPref *entry,
478 PrefAction action)
480 gint *setting = entry->setting;
482 g_assert(entry->setting_type == G_TYPE_INT); /* only int spin prefs */
484 switch (action)
486 case PREF_DISPLAY:
487 gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget), *setting);
488 break;
489 case PREF_UPDATE:
490 /* if the widget is focussed, the value might not be updated */
491 gtk_spin_button_update(GTK_SPIN_BUTTON(widget));
492 *setting = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(widget));
493 break;
498 static void handle_combo_box(GtkWidget *widget, StashPref *entry,
499 PrefAction action)
501 gint *setting = entry->setting;
503 switch (action)
505 case PREF_DISPLAY:
506 gtk_combo_box_set_active(GTK_COMBO_BOX(widget), *setting);
507 break;
508 case PREF_UPDATE:
509 *setting = gtk_combo_box_get_active(GTK_COMBO_BOX(widget));
510 break;
515 static void handle_entry(GtkWidget *widget, StashPref *entry,
516 PrefAction action)
518 gchararray *setting = entry->setting;
520 switch (action)
522 case PREF_DISPLAY:
523 gtk_entry_set_text(GTK_ENTRY(widget), *setting);
524 break;
525 case PREF_UPDATE:
526 g_free(*setting);
527 *setting = g_strdup(gtk_entry_get_text(GTK_ENTRY(widget)));
528 break;
533 static void handle_combo_box_entry(GtkWidget *widget, StashPref *entry,
534 PrefAction action)
536 widget = gtk_bin_get_child(GTK_BIN(widget));
537 handle_entry(widget, entry, action);
541 /* taken from Glade 2.x generated support.c */
542 static GtkWidget*
543 lookup_widget(GtkWidget *widget, const gchar *widget_name)
545 GtkWidget *parent, *found_widget;
547 for (;;)
549 if (GTK_IS_MENU (widget))
550 parent = gtk_menu_get_attach_widget (GTK_MENU (widget));
551 else
552 parent = widget->parent;
553 if (!parent)
554 parent = (GtkWidget*) g_object_get_data (G_OBJECT (widget), "GladeParentKey");
555 if (parent == NULL)
556 break;
557 widget = parent;
560 found_widget = (GtkWidget*) g_object_get_data (G_OBJECT (widget), widget_name);
561 if (!found_widget)
562 g_warning ("Widget not found: %s", widget_name);
563 return found_widget;
567 static GtkWidget *
568 get_widget(GtkWidget *owner, StashWidgetID widget_id)
570 GtkWidget *widget = widget_id;
572 if (owner)
574 const gchar *widget_name = widget_id;
576 widget = lookup_widget(owner, widget_name);
578 if (!GTK_IS_WIDGET(widget))
580 g_warning("Unknown widget in %s()!", G_STRFUNC);
581 return NULL;
583 return widget;
587 static void handle_radio_button(GtkWidget *widget, gint enum_id, gboolean *setting,
588 PrefAction action)
590 switch (action)
592 case PREF_DISPLAY:
593 if (*setting == enum_id)
594 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), TRUE);
595 break;
596 case PREF_UPDATE:
597 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
598 *setting = enum_id;
599 break;
604 static void handle_radio_buttons(GtkWidget *owner, EnumWidget *fields,
605 gboolean *setting,
606 PrefAction action)
608 EnumWidget *field = fields;
609 gsize count = 0;
610 GtkWidget *widget = NULL;
612 while (1)
614 widget = get_widget(owner, field->widget_id);
616 if (!widget)
617 continue;
619 count++;
620 handle_radio_button(widget, field->enum_id, setting, action);
621 field++;
622 if (!field->widget_id)
623 break;
625 if (g_slist_length(gtk_radio_button_get_group(GTK_RADIO_BUTTON(widget))) != count)
626 g_warning("Missing/invalid radio button widget IDs found!");
630 static void handle_widget_property(GtkWidget *widget, StashPref *entry,
631 PrefAction action)
633 GObject *object = G_OBJECT(widget);
634 const gchar *name = entry->fields;
636 switch (action)
638 case PREF_DISPLAY:
639 g_object_set(object, name, entry->setting, NULL);
640 break;
641 case PREF_UPDATE:
642 if (entry->setting_type == G_TYPE_STRING)
643 g_free(entry->setting);
644 /* TODO: Which other types need freeing here? */
646 g_object_get(object, name, entry->setting, NULL);
647 break;
652 static void pref_action(PrefAction action, StashGroup *group, GtkWidget *owner)
654 StashPref *entry;
656 foreach_array(StashPref, entry, group->entries)
658 GtkWidget *widget;
660 /* ignore settings with no widgets */
661 if (entry->widget_type == G_TYPE_NONE)
662 continue;
664 /* radio buttons have several widgets */
665 if (entry->widget_type == GTK_TYPE_RADIO_BUTTON)
667 handle_radio_buttons(owner, entry->fields, entry->setting, action);
668 continue;
671 widget = get_widget(owner, entry->widget_id);
672 if (!widget)
674 g_warning("Unknown widget for %s::%s in %s()!", group->name, entry->key_name,
675 G_STRFUNC);
676 continue;
679 /* note: can't use switch for GTK_TYPE macros */
680 if (entry->widget_type == GTK_TYPE_TOGGLE_BUTTON)
681 handle_toggle_button(widget, entry->setting, action);
682 else if (entry->widget_type == GTK_TYPE_SPIN_BUTTON)
683 handle_spin_button(widget, entry, action);
684 else if (entry->widget_type == GTK_TYPE_COMBO_BOX)
685 handle_combo_box(widget, entry, action);
686 else if (entry->widget_type == GTK_TYPE_COMBO_BOX_ENTRY)
687 handle_combo_box_entry(widget, entry, action);
688 else if (entry->widget_type == GTK_TYPE_ENTRY)
689 handle_entry(widget, entry, action);
690 else if (entry->widget_type == G_TYPE_PARAM)
691 handle_widget_property(widget, entry, action);
692 else
693 g_warning("Unhandled type for %s::%s in %s()!", group->name, entry->key_name,
694 G_STRFUNC);
699 /** Applies Stash settings to widgets, usually called before displaying the widgets.
700 * The @a owner argument depends on which type you use for @ref StashWidgetID.
701 * @param group .
702 * @param owner If non-NULL, used to lookup widgets by name, otherwise
703 * widget pointers are assumed.
704 * @see stash_group_update(). */
705 void stash_group_display(StashGroup *group, GtkWidget *owner)
707 pref_action(PREF_DISPLAY, group, owner);
711 /** Applies widget values to Stash settings, usually called after displaying the widgets.
712 * The @a owner argument depends on which type you use for @ref StashWidgetID.
713 * @param group .
714 * @param owner If non-NULL, used to lookup widgets by name, otherwise
715 * widget pointers are assumed.
716 * @see stash_group_display(). */
717 void stash_group_update(StashGroup *group, GtkWidget *owner)
719 pref_action(PREF_UPDATE, group, owner);
723 static StashPref *
724 add_widget_pref(StashGroup *group, GType setting_type, gpointer setting,
725 const gchar *key_name, gpointer default_value,
726 GType widget_type, StashWidgetID widget_id)
728 StashPref *entry =
729 add_pref(group, setting_type, setting, key_name, default_value);
731 entry->widget_type = widget_type;
732 entry->widget_id = widget_id;
733 return entry;
737 /** Adds a @c GtkToggleButton (or @c GtkCheckButton) widget pref.
738 * @param group .
739 * @param setting Address of setting variable.
740 * @param key_name Name for key in a @c GKeyFile.
741 * @param default_value Value to use if the key doesn't exist when loading.
742 * @param widget_id @c GtkWidget pointer or string to lookup widget later.
743 * @see stash_group_add_radio_buttons(). */
744 void stash_group_add_toggle_button(StashGroup *group, gboolean *setting,
745 const gchar *key_name, gboolean default_value, StashWidgetID widget_id)
747 add_widget_pref(group, G_TYPE_BOOLEAN, setting, key_name, GINT_TO_POINTER(default_value),
748 GTK_TYPE_TOGGLE_BUTTON, widget_id);
752 /** Adds a @c GtkRadioButton widget group pref.
753 * @param group .
754 * @param setting Address of setting variable.
755 * @param key_name Name for key in a @c GKeyFile.
756 * @param default_value Value to use if the key doesn't exist when loading.
757 * @param widget_id @c GtkWidget pointer or string to lookup widget later.
758 * @param enum_id Enum value for @a widget_id.
759 * @param ... pairs of @a widget_id, @a enum_id.
760 * Example (using widget lookup strings, but widget pointers can also work):
761 * @code
762 * enum {FOO, BAR};
763 * stash_group_add_radio_buttons(group, &which_one_setting, "which_one", BAR,
764 * "radio_foo", FOO, "radio_bar", BAR, NULL);
765 * @endcode */
766 void stash_group_add_radio_buttons(StashGroup *group, gint *setting,
767 const gchar *key_name, gint default_value,
768 StashWidgetID widget_id, gint enum_id, ...)
770 StashPref *entry =
771 add_widget_pref(group, G_TYPE_INT, setting, key_name, GINT_TO_POINTER(default_value),
772 GTK_TYPE_RADIO_BUTTON, NULL);
773 va_list args;
774 gsize count = 1;
775 EnumWidget *item, *array;
777 /* count pairs of args */
778 va_start(args, enum_id);
779 while (1)
781 gint dummy;
783 if (!va_arg(args, gpointer))
784 break;
785 dummy = va_arg(args, gint);
786 count++;
788 va_end(args);
790 array = g_new0(EnumWidget, count + 1);
791 entry->fields = array;
793 va_start(args, enum_id);
794 foreach_c_array(item, array, count)
796 if (item == array)
798 /* first element */
799 item->widget_id = widget_id;
800 item->enum_id = enum_id;
802 else
804 item->widget_id = va_arg(args, gpointer);
805 item->enum_id = va_arg(args, gint);
808 va_end(args);
812 /** Adds a @c GtkSpinButton widget pref.
813 * @param group .
814 * @param setting Address of setting variable.
815 * @param key_name Name for key in a @c GKeyFile.
816 * @param default_value Value to use if the key doesn't exist when loading.
817 * @param widget_id @c GtkWidget pointer or string to lookup widget later. */
818 void stash_group_add_spin_button_integer(StashGroup *group, gint *setting,
819 const gchar *key_name, gint default_value, StashWidgetID widget_id)
821 add_widget_pref(group, G_TYPE_INT, setting, key_name, GINT_TO_POINTER(default_value),
822 GTK_TYPE_SPIN_BUTTON, widget_id);
826 /** Adds a @c GtkComboBox widget pref.
827 * @param group .
828 * @param setting Address of setting variable.
829 * @param key_name Name for key in a @c GKeyFile.
830 * @param default_value Value to use if the key doesn't exist when loading.
831 * @param widget_id @c GtkWidget pointer or string to lookup widget later.
832 * @see stash_group_add_combo_box_entry(). */
833 void stash_group_add_combo_box(StashGroup *group, gint *setting,
834 const gchar *key_name, gint default_value, StashWidgetID widget_id)
836 add_widget_pref(group, G_TYPE_INT, setting, key_name, GINT_TO_POINTER(default_value),
837 GTK_TYPE_COMBO_BOX, widget_id);
841 /** Adds a @c GtkComboBoxEntry widget pref.
842 * @param group .
843 * @param setting Address of setting variable.
844 * @param key_name Name for key in a @c GKeyFile.
845 * @param default_value Value to use if the key doesn't exist when loading.
846 * @param widget_id @c GtkWidget pointer or string to lookup widget later. */
847 /* We could maybe also have something like stash_group_add_combo_box_entry_with_menu()
848 * for the history list - or should that be stored as a separate setting? */
849 void stash_group_add_combo_box_entry(StashGroup *group, gchar **setting,
850 const gchar *key_name, const gchar *default_value, StashWidgetID widget_id)
852 add_widget_pref(group, G_TYPE_STRING, setting, key_name, (gpointer)default_value,
853 GTK_TYPE_COMBO_BOX_ENTRY, widget_id);
857 /** Adds a @c GtkEntry widget pref.
858 * @param group .
859 * @param setting Address of setting variable.
860 * @param key_name Name for key in a @c GKeyFile.
861 * @param default_value Value to use if the key doesn't exist when loading.
862 * @param widget_id @c GtkWidget pointer or string to lookup widget later. */
863 void stash_group_add_entry(StashGroup *group, gchar **setting,
864 const gchar *key_name, const gchar *default_value, StashWidgetID widget_id)
866 add_widget_pref(group, G_TYPE_STRING, setting, key_name, (gpointer)default_value,
867 GTK_TYPE_ENTRY, widget_id);
871 static GType object_get_property_type(GObject *object, const gchar *property_name)
873 GObjectClass *klass = G_OBJECT_GET_CLASS(object);
874 GParamSpec *ps;
876 ps = g_object_class_find_property(klass, property_name);
877 return ps->value_type;
881 /** Adds a widget's read/write property to the stash group.
882 * The property will be set when calling
883 * stash_group_display(), and read when calling stash_group_update().
884 * @param group .
885 * @param setting Address of e.g. an integer if using an integer property.
886 * @param key_name Name for key in a @c GKeyFile.
887 * @param default_value Value to use if the key doesn't exist when loading.
888 * Should be cast into a pointer e.g. with @c GINT_TO_POINTER().
889 * @param widget_id @c GtkWidget pointer or string to lookup widget later.
890 * @param property_name .
891 * @param type can be @c 0 if passing a @c GtkWidget as the @a widget_id argument to look it up from the
892 * @c GObject data.
893 * @warning Currently only string GValue properties will be freed before setting; patch for
894 * other types - see @c handle_widget_property(). */
895 void stash_group_add_widget_property(StashGroup *group, gpointer setting,
896 const gchar *key_name, gpointer default_value, StashWidgetID widget_id,
897 const gchar *property_name, GType type)
899 if (!type)
900 type = object_get_property_type(G_OBJECT(widget_id), property_name);
902 add_widget_pref(group, type, setting, key_name, default_value,
903 G_TYPE_PARAM, widget_id)->fields = (gchar*)property_name;