If the current word's tag is on the current line, make Go to Tag
[geany-mirror.git] / src / stash.c
blob809f942f4dac906f668c94e633719f1a56aeb68a
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 * Here we have some settings for how to make a cup - whether it should be made of china
54 * and who's going to make it. (Yes, it's a stupid example).
55 * @include stash-example.c
56 * @note You might want to handle the warning/error conditions differently from above.
58 * @section prefs-example GUI Prefs Example
59 * For prefs, it's the same as the above example but you tell Stash to add widget prefs instead of
60 * just data settings.
62 * This example uses lookup strings for widgets as they are more flexible than widget pointers.
63 * Code to load and save the settings is omitted - see the first example instead.
65 * Here we show a dialog with a toggle button for whether the cup should have a handle.
66 * @include stash-gui-example.c
67 * @note This example should also work for other widget containers besides dialogs, e.g. popup menus.
70 /* Implementation Note
71 * We use a GArray to hold prefs. It would be more efficient for user code to declare
72 * a static array of StashPref structs, but we don't do this because:
74 * * It would be more ugly (lots of casts and NULLs).
75 * * Less type checking.
76 * * The API would have to break when adding/changing fields.
78 * Usually the prefs code isn't what user code will spend most of its time doing, so this
79 * should be efficient enough. But, if desired we could add a stash_group_set_size() function
80 * to reduce reallocation (or perhaps use a different container).
82 * Note: Maybe using GSlice chunks with an extra 'next' pointer would be more efficient.
86 #include "geany.h" /* necessary for utils.h, otherwise use gtk/gtk.h */
87 #include "utils.h" /* only for foreach_*, utils_get_setting_*(). Stash should not depend on Geany. */
89 #include "stash.h"
92 struct StashPref
94 GType setting_type; /* e.g. G_TYPE_INT */
95 gpointer setting; /* Address of a variable */
96 const gchar *key_name;
97 gpointer default_value; /* Default value, e.g. (gpointer)1 */
98 GType widget_type; /* e.g. GTK_TYPE_TOGGLE_BUTTON */
99 StashWidgetID widget_id; /* (GtkWidget*) or (gchar*) */
100 gpointer fields; /* extra fields */
103 typedef struct StashPref StashPref;
105 struct StashGroup
107 const gchar *name; /* group name to use in the keyfile */
108 GArray *entries; /* array of StashPref */
109 gboolean write_once; /* only write settings if they don't already exist */
110 gboolean use_defaults; /* use default values if there's no keyfile entry */
113 typedef struct EnumWidget
115 StashWidgetID widget_id;
116 gint enum_id;
118 EnumWidget;
121 typedef enum SettingAction
123 SETTING_READ,
124 SETTING_WRITE
126 SettingAction;
128 typedef enum PrefAction
130 PREF_DISPLAY,
131 PREF_UPDATE
133 PrefAction;
136 static void handle_boolean_setting(StashGroup *group, StashPref *se,
137 GKeyFile *config, SettingAction action)
139 gboolean *setting = se->setting;
141 switch (action)
143 case SETTING_READ:
144 *setting = utils_get_setting_boolean(config, group->name, se->key_name,
145 GPOINTER_TO_INT(se->default_value));
146 break;
147 case SETTING_WRITE:
148 g_key_file_set_boolean(config, group->name, se->key_name, *setting);
149 break;
154 static void handle_integer_setting(StashGroup *group, StashPref *se,
155 GKeyFile *config, SettingAction action)
157 gint *setting = se->setting;
159 switch (action)
161 case SETTING_READ:
162 *setting = utils_get_setting_integer(config, group->name, se->key_name,
163 GPOINTER_TO_INT(se->default_value));
164 break;
165 case SETTING_WRITE:
166 g_key_file_set_integer(config, group->name, se->key_name, *setting);
167 break;
172 static void handle_string_setting(StashGroup *group, StashPref *se,
173 GKeyFile *config, SettingAction action)
175 gchararray *setting = se->setting;
177 switch (action)
179 case SETTING_READ:
180 g_free(*setting);
181 *setting = utils_get_setting_string(config, group->name, se->key_name,
182 se->default_value);
183 break;
184 case SETTING_WRITE:
185 g_key_file_set_string(config, group->name, se->key_name,
186 *setting ? *setting : "");
187 break;
192 static void handle_strv_setting(StashGroup *group, StashPref *se,
193 GKeyFile *config, SettingAction action)
195 gchararray **setting = se->setting;
197 switch (action)
199 case SETTING_READ:
200 g_strfreev(*setting);
201 *setting = g_key_file_get_string_list(config, group->name, se->key_name,
202 NULL, NULL);
203 if (*setting == NULL)
204 *setting = g_strdupv(se->default_value);
205 break;
207 case SETTING_WRITE:
209 /* don't try to save a NULL string vector */
210 gchar *dummy[] = { "", NULL };
211 gchar **strv = *setting ? *setting : dummy;
213 g_key_file_set_string_list(config, group->name, se->key_name,
214 (const gchar**)strv, g_strv_length(strv));
215 break;
221 static void keyfile_action(SettingAction action, StashGroup *group, GKeyFile *keyfile)
223 StashPref *entry;
225 foreach_array(StashPref, entry, group->entries)
227 gpointer tmp = entry->setting;
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 /* don't overwrite write_once prefs */
235 if (group->write_once && action == SETTING_WRITE)
237 if (g_key_file_has_key(keyfile, group->name, entry->key_name, NULL))
238 continue;
239 /* We temporarily use the default value for writing */
240 entry->setting = &entry->default_value;
242 switch (entry->setting_type)
244 case G_TYPE_BOOLEAN:
245 handle_boolean_setting(group, entry, keyfile, action); break;
246 case G_TYPE_INT:
247 handle_integer_setting(group, entry, keyfile, action); break;
248 case G_TYPE_STRING:
249 handle_string_setting(group, entry, keyfile, action); break;
250 default:
251 /* Note: G_TYPE_STRV is not a constant, can't use case label */
252 if (entry->setting_type == G_TYPE_STRV)
253 handle_strv_setting(group, entry, keyfile, action);
254 else
255 g_warning("Unhandled type for %s::%s in %s()!", group->name, entry->key_name,
256 G_STRFUNC);
258 if (group->write_once && action == SETTING_WRITE)
259 entry->setting = tmp;
264 /** Reads key values from @a keyfile into the group settings.
265 * @note You should still call this even if the keyfile couldn't be loaded from disk
266 * so that all Stash settings are initialized to defaults.
267 * @param group .
268 * @param keyfile Usually loaded from a configuration file first. */
269 void stash_group_load_from_key_file(StashGroup *group, GKeyFile *keyfile)
271 keyfile_action(SETTING_READ, group, keyfile);
275 /** Writes group settings into key values in @a keyfile.
276 * @a keyfile is usually written to a configuration file afterwards.
277 * @param group .
278 * @param keyfile . */
279 void stash_group_save_to_key_file(StashGroup *group, GKeyFile *keyfile)
281 keyfile_action(SETTING_WRITE, group, keyfile);
285 /** Reads group settings from a configuration file using @c GKeyFile.
286 * @note Stash settings will be initialized to defaults if the keyfile
287 * couldn't be loaded from disk.
288 * @param group .
289 * @param filename Filename of the file to read, in locale encoding.
290 * @return @c TRUE if a key file could be loaded.
291 * @see stash_group_load_from_key_file().
293 gboolean stash_group_load_from_file(StashGroup *group, const gchar *filename)
295 GKeyFile *keyfile;
296 gboolean ret;
298 keyfile = g_key_file_new();
299 ret = g_key_file_load_from_file(keyfile, filename, 0, NULL);
300 /* even on failure we load settings to apply defaults */
301 stash_group_load_from_key_file(group, keyfile);
303 g_key_file_free(keyfile);
304 return ret;
308 /** Writes group settings to a configuration file using @c GKeyFile.
310 * @param group .
311 * @param filename Filename of the file to write, in locale encoding.
312 * @param flags Keyfile options - @c G_KEY_FILE_NONE is the most efficient.
313 * @return 0 if the file was successfully written, otherwise the @c errno of the
314 * failed operation is returned.
315 * @see stash_group_save_to_key_file().
317 gint stash_group_save_to_file(StashGroup *group, const gchar *filename,
318 GKeyFileFlags flags)
320 GKeyFile *keyfile;
321 gchar *data;
322 gint ret;
324 keyfile = g_key_file_new();
325 /* if we need to keep comments or translations, try to load first */
326 if (flags)
327 g_key_file_load_from_file(keyfile, filename, flags, NULL);
329 stash_group_save_to_key_file(group, keyfile);
330 data = g_key_file_to_data(keyfile, NULL, NULL);
331 ret = utils_write_file(filename, data);
332 g_free(data);
333 g_key_file_free(keyfile);
334 return ret;
338 /** Creates a new group.
339 * @param name Name used for @c GKeyFile group.
340 * @return Group. */
341 StashGroup *stash_group_new(const gchar *name)
343 StashGroup *group = g_new0(StashGroup, 1);
345 group->name = name;
346 group->entries = g_array_new(FALSE, FALSE, sizeof(StashPref));
347 group->use_defaults = TRUE;
348 return group;
352 /** Frees a group.
353 * @param group . */
354 void stash_group_free(StashGroup *group)
356 StashPref *entry;
358 foreach_array(StashPref, entry, group->entries)
360 if (entry->widget_type == GTK_TYPE_RADIO_BUTTON)
361 g_free(entry->fields);
362 else if (entry->widget_type == G_TYPE_PARAM)
363 continue;
364 else
365 g_assert(entry->fields == NULL); /* to prevent memory leaks, must handle fields above */
367 g_array_free(group->entries, TRUE);
368 g_free(group);
372 /* Useful so the user can edit the keyfile manually while the program is running,
373 * and the setting won't be overridden.
374 * @c FALSE by default. */
375 void stash_group_set_write_once(StashGroup *group, gboolean write_once)
377 group->write_once = write_once;
381 /* When @c FALSE, Stash doesn't change the setting if there is no keyfile entry, so it
382 * remains whatever it was initialized/set to by user code.
383 * @c TRUE by default. */
384 void stash_group_set_use_defaults(StashGroup *group, gboolean use_defaults)
386 group->use_defaults = use_defaults;
390 static StashPref *
391 add_pref(StashGroup *group, GType type, gpointer setting,
392 const gchar *key_name, gpointer default_value)
394 StashPref entry = {type, setting, key_name, default_value, G_TYPE_NONE, NULL, NULL};
395 GArray *array = group->entries;
397 /* init any pointer settings to NULL so they can be freed later */
398 if (type == G_TYPE_STRING ||
399 type == G_TYPE_STRV)
400 if (group->use_defaults)
401 *(gpointer**)setting = NULL;
403 g_array_append_val(array, entry);
405 return &g_array_index(array, StashPref, array->len - 1);
409 /** Adds boolean setting.
410 * @param group .
411 * @param setting Address of setting variable.
412 * @param key_name Name for key in a @c GKeyFile.
413 * @param default_value Value to use if the key doesn't exist when loading. */
414 void stash_group_add_boolean(StashGroup *group, gboolean *setting,
415 const gchar *key_name, gboolean default_value)
417 add_pref(group, G_TYPE_BOOLEAN, setting, key_name, GINT_TO_POINTER(default_value));
421 /** Adds integer setting.
422 * @param group .
423 * @param setting Address of setting variable.
424 * @param key_name Name for key in a @c GKeyFile.
425 * @param default_value Value to use if the key doesn't exist when loading. */
426 void stash_group_add_integer(StashGroup *group, gint *setting,
427 const gchar *key_name, gint default_value)
429 add_pref(group, G_TYPE_INT, setting, key_name, GINT_TO_POINTER(default_value));
433 /** Adds string setting.
434 * The contents of @a setting will be initialized to @c NULL.
435 * @param group .
436 * @param setting Address of setting variable.
437 * @param key_name Name for key in a @c GKeyFile.
438 * @param default_value String to copy if the key doesn't exist when loading, or @c NULL. */
439 void stash_group_add_string(StashGroup *group, gchar **setting,
440 const gchar *key_name, const gchar *default_value)
442 add_pref(group, G_TYPE_STRING, setting, key_name, (gpointer)default_value);
446 /** Adds string vector setting (array of strings).
447 * The contents of @a setting will be initialized to @c NULL.
448 * @param group .
449 * @param setting Address of setting variable.
450 * @param key_name Name for key in a @c GKeyFile.
451 * @param default_value Vector to copy if the key doesn't exist when loading. Usually @c NULL. */
452 void stash_group_add_string_vector(StashGroup *group, gchar ***setting,
453 const gchar *key_name, const gchar **default_value)
455 add_pref(group, G_TYPE_STRV, setting, key_name, (gpointer)default_value);
459 /* *** GTK-related functions *** */
461 static void handle_toggle_button(GtkWidget *widget, gboolean *setting,
462 PrefAction action)
464 switch (action)
466 case PREF_DISPLAY:
467 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), *setting);
468 break;
469 case PREF_UPDATE:
470 *setting = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
471 break;
476 static void handle_spin_button(GtkWidget *widget, StashPref *entry,
477 PrefAction action)
479 gint *setting = entry->setting;
481 g_assert(entry->setting_type == G_TYPE_INT); /* only int spin prefs */
483 switch (action)
485 case PREF_DISPLAY:
486 gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget), *setting);
487 break;
488 case PREF_UPDATE:
489 /* if the widget is focussed, the value might not be updated */
490 gtk_spin_button_update(GTK_SPIN_BUTTON(widget));
491 *setting = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(widget));
492 break;
497 static void handle_combo_box(GtkWidget *widget, StashPref *entry,
498 PrefAction action)
500 gint *setting = entry->setting;
502 switch (action)
504 case PREF_DISPLAY:
505 gtk_combo_box_set_active(GTK_COMBO_BOX(widget), *setting);
506 break;
507 case PREF_UPDATE:
508 *setting = gtk_combo_box_get_active(GTK_COMBO_BOX(widget));
509 break;
514 static void handle_entry(GtkWidget *widget, StashPref *entry,
515 PrefAction action)
517 gchararray *setting = entry->setting;
519 switch (action)
521 case PREF_DISPLAY:
522 gtk_entry_set_text(GTK_ENTRY(widget), *setting);
523 break;
524 case PREF_UPDATE:
525 g_free(*setting);
526 *setting = g_strdup(gtk_entry_get_text(GTK_ENTRY(widget)));
527 break;
532 static void handle_combo_box_entry(GtkWidget *widget, StashPref *entry,
533 PrefAction action)
535 widget = gtk_bin_get_child(GTK_BIN(widget));
536 handle_entry(widget, entry, action);
540 /* taken from Glade 2.x generated support.c */
541 static GtkWidget*
542 lookup_widget(GtkWidget *widget, const gchar *widget_name)
544 GtkWidget *parent, *found_widget;
546 for (;;)
548 if (GTK_IS_MENU (widget))
549 parent = gtk_menu_get_attach_widget (GTK_MENU (widget));
550 else
551 parent = widget->parent;
552 if (!parent)
553 parent = (GtkWidget*) g_object_get_data (G_OBJECT (widget), "GladeParentKey");
554 if (parent == NULL)
555 break;
556 widget = parent;
559 found_widget = (GtkWidget*) g_object_get_data (G_OBJECT (widget), widget_name);
560 if (!found_widget)
561 g_warning ("Widget not found: %s", widget_name);
562 return found_widget;
566 static GtkWidget *
567 get_widget(GtkWidget *owner, StashWidgetID widget_id)
569 GtkWidget *widget = widget_id;
571 if (owner)
573 const gchar *widget_name = widget_id;
575 widget = lookup_widget(owner, widget_name);
577 if (!GTK_IS_WIDGET(widget))
579 g_warning("Unknown widget in %s()!", G_STRFUNC);
580 return NULL;
582 return widget;
586 static void handle_radio_button(GtkWidget *widget, gint enum_id, gboolean *setting,
587 PrefAction action)
589 switch (action)
591 case PREF_DISPLAY:
592 if (*setting == enum_id)
593 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), TRUE);
594 break;
595 case PREF_UPDATE:
596 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
597 *setting = enum_id;
598 break;
603 static void handle_radio_buttons(GtkWidget *owner, EnumWidget *fields,
604 gboolean *setting,
605 PrefAction action)
607 EnumWidget *field = fields;
608 gsize count = 0;
609 GtkWidget *widget = NULL;
611 while (1)
613 widget = get_widget(owner, field->widget_id);
615 if (!widget)
616 continue;
618 count++;
619 handle_radio_button(widget, field->enum_id, setting, action);
620 field++;
621 if (!field->widget_id)
622 break;
624 if (g_slist_length(gtk_radio_button_get_group(GTK_RADIO_BUTTON(widget))) != count)
625 g_warning("Missing/invalid radio button widget IDs found!");
629 static void handle_widget_property(GtkWidget *widget, StashPref *entry,
630 PrefAction action)
632 GObject *object = G_OBJECT(widget);
633 const gchar *name = entry->fields;
635 switch (action)
637 case PREF_DISPLAY:
638 g_object_set(object, name, entry->setting, NULL);
639 break;
640 case PREF_UPDATE:
641 if (entry->setting_type == G_TYPE_STRING)
642 g_free(entry->setting);
643 /* TODO: Which other types need freeing here? */
645 g_object_get(object, name, entry->setting, NULL);
646 break;
651 static void pref_action(PrefAction action, StashGroup *group, GtkWidget *owner)
653 StashPref *entry;
655 foreach_array(StashPref, entry, group->entries)
657 GtkWidget *widget;
659 /* ignore settings with no widgets */
660 if (entry->widget_type == G_TYPE_NONE)
661 continue;
663 /* radio buttons have several widgets */
664 if (entry->widget_type == GTK_TYPE_RADIO_BUTTON)
666 handle_radio_buttons(owner, entry->fields, entry->setting, action);
667 continue;
670 widget = get_widget(owner, entry->widget_id);
671 if (!widget)
673 g_warning("Unknown widget for %s::%s in %s()!", group->name, entry->key_name,
674 G_STRFUNC);
675 continue;
678 /* note: can't use switch for GTK_TYPE macros */
679 if (entry->widget_type == GTK_TYPE_TOGGLE_BUTTON)
680 handle_toggle_button(widget, entry->setting, action);
681 else if (entry->widget_type == GTK_TYPE_SPIN_BUTTON)
682 handle_spin_button(widget, entry, action);
683 else if (entry->widget_type == GTK_TYPE_COMBO_BOX)
684 handle_combo_box(widget, entry, action);
685 else if (entry->widget_type == GTK_TYPE_COMBO_BOX_ENTRY)
686 handle_combo_box_entry(widget, entry, action);
687 else if (entry->widget_type == GTK_TYPE_ENTRY)
688 handle_entry(widget, entry, action);
689 else if (entry->widget_type == G_TYPE_PARAM)
690 handle_widget_property(widget, entry, action);
691 else
692 g_warning("Unhandled type for %s::%s in %s()!", group->name, entry->key_name,
693 G_STRFUNC);
698 /** Applies Stash settings to widgets, usually called before displaying the widgets.
699 * The @a owner argument depends on which type you use for @ref StashWidgetID.
700 * @param group .
701 * @param owner If non-NULL, used to lookup widgets by name, otherwise
702 * widget pointers are assumed.
703 * @see stash_group_update(). */
704 void stash_group_display(StashGroup *group, GtkWidget *owner)
706 pref_action(PREF_DISPLAY, group, owner);
710 /** Applies widget values to Stash settings, usually called after displaying the widgets.
711 * The @a owner argument depends on which type you use for @ref StashWidgetID.
712 * @param group .
713 * @param owner If non-NULL, used to lookup widgets by name, otherwise
714 * widget pointers are assumed.
715 * @see stash_group_display(). */
716 void stash_group_update(StashGroup *group, GtkWidget *owner)
718 pref_action(PREF_UPDATE, group, owner);
722 static StashPref *
723 add_widget_pref(StashGroup *group, GType setting_type, gpointer setting,
724 const gchar *key_name, gpointer default_value,
725 GType widget_type, StashWidgetID widget_id)
727 StashPref *entry =
728 add_pref(group, setting_type, setting, key_name, default_value);
730 entry->widget_type = widget_type;
731 entry->widget_id = widget_id;
732 return entry;
736 /** Adds a @c GtkToggleButton (or @c GtkCheckButton) widget pref.
737 * @param group .
738 * @param setting Address of setting variable.
739 * @param key_name Name for key in a @c GKeyFile.
740 * @param default_value Value to use if the key doesn't exist when loading.
741 * @param widget_id @c GtkWidget pointer or string to lookup widget later.
742 * @see stash_group_add_radio_buttons(). */
743 void stash_group_add_toggle_button(StashGroup *group, gboolean *setting,
744 const gchar *key_name, gboolean default_value, StashWidgetID widget_id)
746 add_widget_pref(group, G_TYPE_BOOLEAN, setting, key_name, GINT_TO_POINTER(default_value),
747 GTK_TYPE_TOGGLE_BUTTON, widget_id);
751 /** Adds a @c GtkRadioButton widget group pref.
752 * @param group .
753 * @param setting Address of setting variable.
754 * @param key_name Name for key in a @c GKeyFile.
755 * @param default_value Value to use if the key doesn't exist when loading.
756 * @param widget_id @c GtkWidget pointer or string to lookup widget later.
757 * @param enum_id Enum value for @a widget_id.
758 * @param ... pairs of @a widget_id, @a enum_id.
759 * Example (using widget lookup strings, but widget pointers can also work):
760 * @code
761 * enum {FOO, BAR};
762 * stash_group_add_radio_buttons(group, &which_one_setting, "which_one", BAR,
763 * "radio_foo", FOO, "radio_bar", BAR, NULL);
764 * @endcode */
765 void stash_group_add_radio_buttons(StashGroup *group, gint *setting,
766 const gchar *key_name, gint default_value,
767 StashWidgetID widget_id, gint enum_id, ...)
769 StashPref *entry =
770 add_widget_pref(group, G_TYPE_INT, setting, key_name, GINT_TO_POINTER(default_value),
771 GTK_TYPE_RADIO_BUTTON, NULL);
772 va_list args;
773 gsize count = 1;
774 EnumWidget *item, *array;
776 /* count pairs of args */
777 va_start(args, enum_id);
778 while (1)
780 gint dummy;
782 if (!va_arg(args, gpointer))
783 break;
784 dummy = va_arg(args, gint);
785 count++;
787 va_end(args);
789 array = g_new0(EnumWidget, count + 1);
790 entry->fields = array;
792 va_start(args, enum_id);
793 foreach_c_array(item, array, count)
795 if (item == array)
797 /* first element */
798 item->widget_id = widget_id;
799 item->enum_id = enum_id;
801 else
803 item->widget_id = va_arg(args, gpointer);
804 item->enum_id = va_arg(args, gint);
807 va_end(args);
811 /** Adds a @c GtkSpinButton widget pref.
812 * @param group .
813 * @param setting Address of setting variable.
814 * @param key_name Name for key in a @c GKeyFile.
815 * @param default_value Value to use if the key doesn't exist when loading.
816 * @param widget_id @c GtkWidget pointer or string to lookup widget later. */
817 void stash_group_add_spin_button_integer(StashGroup *group, gint *setting,
818 const gchar *key_name, gint default_value, StashWidgetID widget_id)
820 add_widget_pref(group, G_TYPE_INT, setting, key_name, GINT_TO_POINTER(default_value),
821 GTK_TYPE_SPIN_BUTTON, widget_id);
825 /** Adds a @c GtkComboBox widget pref.
826 * @param group .
827 * @param setting Address of setting variable.
828 * @param key_name Name for key in a @c GKeyFile.
829 * @param default_value Value to use if the key doesn't exist when loading.
830 * @param widget_id @c GtkWidget pointer or string to lookup widget later.
831 * @see stash_group_add_combo_box_entry(). */
832 void stash_group_add_combo_box(StashGroup *group, gint *setting,
833 const gchar *key_name, gint default_value, StashWidgetID widget_id)
835 add_widget_pref(group, G_TYPE_INT, setting, key_name, GINT_TO_POINTER(default_value),
836 GTK_TYPE_COMBO_BOX, widget_id);
840 /** Adds a @c GtkComboBoxEntry widget pref.
841 * @param group .
842 * @param setting Address of setting variable.
843 * @param key_name Name for key in a @c GKeyFile.
844 * @param default_value Value to use if the key doesn't exist when loading.
845 * @param widget_id @c GtkWidget pointer or string to lookup widget later. */
846 /* We could maybe also have something like stash_group_add_combo_box_entry_with_menu()
847 * for the history list - or should that be stored as a separate setting? */
848 void stash_group_add_combo_box_entry(StashGroup *group, gchar **setting,
849 const gchar *key_name, const gchar *default_value, StashWidgetID widget_id)
851 add_widget_pref(group, G_TYPE_STRING, setting, key_name, (gpointer)default_value,
852 GTK_TYPE_COMBO_BOX_ENTRY, widget_id);
856 /** Adds a @c GtkEntry widget pref.
857 * @param group .
858 * @param setting Address of setting variable.
859 * @param key_name Name for key in a @c GKeyFile.
860 * @param default_value Value to use if the key doesn't exist when loading.
861 * @param widget_id @c GtkWidget pointer or string to lookup widget later. */
862 void stash_group_add_entry(StashGroup *group, gchar **setting,
863 const gchar *key_name, const gchar *default_value, StashWidgetID widget_id)
865 add_widget_pref(group, G_TYPE_STRING, setting, key_name, (gpointer)default_value,
866 GTK_TYPE_ENTRY, widget_id);
870 static GType object_get_property_type(GObject *object, const gchar *property_name)
872 GObjectClass *klass = G_OBJECT_GET_CLASS(object);
873 GParamSpec *ps;
875 ps = g_object_class_find_property(klass, property_name);
876 return ps->value_type;
880 /** Adds a widget's read/write property to the stash group.
881 * The property will be set when calling
882 * stash_group_display(), and read when calling stash_group_update().
883 * @param group .
884 * @param setting Address of e.g. an integer if using an integer property.
885 * @param key_name Name for key in a @c GKeyFile.
886 * @param default_value Value to use if the key doesn't exist when loading.
887 * Should be cast into a pointer e.g. with @c GINT_TO_POINTER().
888 * @param widget_id @c GtkWidget pointer or string to lookup widget later.
889 * @param property_name .
890 * @param type can be @c 0 if passing a @c GtkWidget as the @a widget_id argument to look it up from the
891 * @c GObject data.
892 * @warning Currently only string GValue properties will be freed before setting; patch for
893 * other types - see @c handle_widget_property(). */
894 void stash_group_add_widget_property(StashGroup *group, gpointer setting,
895 const gchar *key_name, gpointer default_value, StashWidgetID widget_id,
896 const gchar *property_name, GType type)
898 if (!type)
899 type = object_get_property_type(G_OBJECT(widget_id), property_name);
901 add_widget_pref(group, type, setting, key_name, default_value,
902 G_TYPE_PARAM, widget_id)->fields = (gchar*)property_name;