Save encoding in session as text
[geany-mirror.git] / src / stash.c
blob75c23a79c3eefbec480124396a0f4dc72eafd88b
1 /*
2 * stash.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2008-2012 Nick Treleaven <nick(dot)treleaven(at)btinternet(dot)com>
5 * Copyright 2008-2012 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 along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 /**
23 * @file stash.h
24 * Lightweight library for reading/writing @c GKeyFile settings and synchronizing widgets with
25 * C variables.
27 * Note: Stash should only depend on GLib and GTK, but currently has some minor
28 * dependencies on Geany's utils.c.
30 * @section Terms
31 * 'Setting' is used only for data stored on disk or in memory.
32 * 'Pref' can also include visual widget information.
34 * @section Memory Usage
35 * Stash will not duplicate strings if they are normally static arrays, such as
36 * keyfile group names and key names, string default values, widget_id names, property names.
38 * @section String Settings
39 * String settings and other dynamically allocated settings will be initialized to NULL when
40 * added to a StashGroup (so they can safely be reassigned later).
42 * @section Widget Support
43 * Widgets very commonly used in configuration dialogs will be supported with their own function.
44 * Widgets less commonly used such as @c GtkExpander or widget settings that aren't commonly needed
45 * to be persistent won't be directly supported, to keep the library lightweight. However, you can
46 * use stash_group_add_widget_property() to also save these settings for any read/write widget
47 * property. Macros could be added for common widget properties such as @c GtkExpander:"expanded".
49 * @section settings-example Settings Example
50 * Here we have some settings for how to make a cup - whether it should be made of china
51 * and who's going to make it. (Yes, it's a stupid example).
52 * @include stash-example.c
53 * @note You might want to handle the warning/error conditions differently from above.
55 * @section prefs-example GUI Prefs Example
56 * For prefs, it's the same as the above example but you tell Stash to add widget prefs instead of
57 * just data settings.
59 * This example uses lookup strings for widgets as they are more flexible than widget pointers.
60 * Code to load and save the settings is omitted - see the first example instead.
62 * Here we show a dialog with a toggle button for whether the cup should have a handle.
63 * @include stash-gui-example.c
64 * @note This example should also work for other widget containers besides dialogs, e.g. popup menus.
67 /* Implementation Note
68 * We dynamically allocate prefs. It would be more efficient for user code to declare
69 * a static array of StashPref structs, but we don't do this because:
71 * * It would be more ugly (lots of casts and NULLs).
72 * * Less type checking.
73 * * The API & ABI would have to break when adding/changing fields.
75 * Usually the prefs code isn't what user code will spend most of its time doing, so this
76 * should be efficient enough.
80 #include "geany.h" /* necessary for utils.h, otherwise use gtk/gtk.h */
81 #include <stdlib.h> /* only for atoi() */
82 #include "support.h" /* only for _("text") */
83 #include "utils.h" /* only for foreach_*, utils_get_setting_*(). Stash should not depend on Geany. */
85 #include "stash.h"
88 struct StashPref
90 GType setting_type; /* e.g. G_TYPE_INT */
91 gpointer setting; /* Address of a variable */
92 const gchar *key_name;
93 gpointer default_value; /* Default value, e.g. (gpointer)1 */
94 GType widget_type; /* e.g. GTK_TYPE_TOGGLE_BUTTON */
95 StashWidgetID widget_id; /* (GtkWidget*) or (gchar*) */
96 union
98 struct EnumWidget *radio_buttons;
99 const gchar *property_name;
100 } extra; /* extra fields depending on widget_type */
103 typedef struct StashPref StashPref;
105 struct StashGroup
107 const gchar *name; /* group name to use in the keyfile */
108 GPtrArray *entries; /* array of (StashPref*) */
109 gboolean various; /* mark group for display/edit in stash treeview */
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 const gchar *dummy[] = { "", NULL };
211 const gchar **strv = *setting ? (const gchar **)*setting : dummy;
213 g_key_file_set_string_list(config, group->name, se->key_name,
214 strv, g_strv_length((gchar **)strv));
215 break;
221 static void keyfile_action(SettingAction action, StashGroup *group, GKeyFile *keyfile)
223 StashPref *entry;
224 guint i;
226 foreach_ptr_array(entry, i, group->entries)
228 /* don't override settings with default values */
229 if (!group->use_defaults && action == SETTING_READ &&
230 !g_key_file_has_key(keyfile, group->name, entry->key_name, NULL))
231 continue;
233 switch (entry->setting_type)
235 case G_TYPE_BOOLEAN:
236 handle_boolean_setting(group, entry, keyfile, action); break;
237 case G_TYPE_INT:
238 handle_integer_setting(group, entry, keyfile, action); break;
239 case G_TYPE_STRING:
240 handle_string_setting(group, entry, keyfile, action); break;
241 default:
242 /* Note: G_TYPE_STRV is not a constant, can't use case label */
243 if (entry->setting_type == G_TYPE_STRV)
244 handle_strv_setting(group, entry, keyfile, action);
245 else
246 g_warning("Unhandled type for %s::%s in %s()!", group->name, entry->key_name,
247 G_STRFUNC);
253 /** Reads key values from @a keyfile into the group settings.
254 * @note You should still call this even if the keyfile couldn't be loaded from disk
255 * so that all Stash settings are initialized to defaults.
256 * @param group .
257 * @param keyfile Usually loaded from a configuration file first. */
258 void stash_group_load_from_key_file(StashGroup *group, GKeyFile *keyfile)
260 keyfile_action(SETTING_READ, group, keyfile);
264 /** Writes group settings into key values in @a keyfile.
265 * @a keyfile is usually written to a configuration file afterwards.
266 * @param group .
267 * @param keyfile . */
268 void stash_group_save_to_key_file(StashGroup *group, GKeyFile *keyfile)
270 keyfile_action(SETTING_WRITE, group, keyfile);
274 /** Reads group settings from a configuration file using @c GKeyFile.
275 * @note Stash settings will be initialized to defaults if the keyfile
276 * couldn't be loaded from disk.
277 * @param group .
278 * @param filename Filename of the file to read, in locale encoding.
279 * @return @c TRUE if a key file could be loaded.
280 * @see stash_group_load_from_key_file().
282 gboolean stash_group_load_from_file(StashGroup *group, const gchar *filename)
284 GKeyFile *keyfile;
285 gboolean ret;
287 keyfile = g_key_file_new();
288 ret = g_key_file_load_from_file(keyfile, filename, 0, NULL);
289 /* even on failure we load settings to apply defaults */
290 stash_group_load_from_key_file(group, keyfile);
292 g_key_file_free(keyfile);
293 return ret;
297 /** Writes group settings to a configuration file using @c GKeyFile.
299 * @param group .
300 * @param filename Filename of the file to write, in locale encoding.
301 * @param flags Keyfile options - @c G_KEY_FILE_NONE is the most efficient.
302 * @return 0 if the file was successfully written, otherwise the @c errno of the
303 * failed operation is returned.
304 * @see stash_group_save_to_key_file().
306 gint stash_group_save_to_file(StashGroup *group, const gchar *filename,
307 GKeyFileFlags flags)
309 GKeyFile *keyfile;
310 gchar *data;
311 gint ret;
313 keyfile = g_key_file_new();
314 /* if we need to keep comments or translations, try to load first */
315 if (flags)
316 g_key_file_load_from_file(keyfile, filename, flags, NULL);
318 stash_group_save_to_key_file(group, keyfile);
319 data = g_key_file_to_data(keyfile, NULL, NULL);
320 ret = utils_write_file(filename, data);
321 g_free(data);
322 g_key_file_free(keyfile);
323 return ret;
327 /** Creates a new group.
328 * @param name Name used for @c GKeyFile group.
329 * @return Group. */
330 StashGroup *stash_group_new(const gchar *name)
332 StashGroup *group = g_new0(StashGroup, 1);
334 group->name = name;
335 group->entries = g_ptr_array_new();
336 group->use_defaults = TRUE;
337 return group;
341 /** Frees the memory allocated for setting values in a group.
342 * Useful e.g. to avoid freeing strings individually.
343 * @note This is *not* called by stash_group_free().
344 * @param group . */
345 void stash_group_free_settings(StashGroup *group)
347 StashPref *entry;
348 guint i;
350 foreach_ptr_array(entry, i, group->entries)
352 if (entry->setting_type == G_TYPE_STRING)
353 g_free(*(gchararray *) entry->setting);
354 else if (entry->setting_type == G_TYPE_STRV)
355 g_strfreev(*(gchararray **) entry->setting);
356 else
357 continue;
359 *(gpointer**) entry->setting = NULL;
364 /** Frees a group.
365 * @param group . */
366 void stash_group_free(StashGroup *group)
368 StashPref *entry;
369 guint i;
371 foreach_ptr_array(entry, i, group->entries)
373 if (entry->widget_type == GTK_TYPE_RADIO_BUTTON)
375 g_free(entry->extra.radio_buttons);
377 g_slice_free(StashPref, entry);
379 g_ptr_array_free(group->entries, TRUE);
380 g_free(group);
384 /* Used for selecting groups passed to stash_tree_setup().
385 * @c FALSE by default. */
386 void stash_group_set_various(StashGroup *group, gboolean various)
388 group->various = various;
392 /* When @c FALSE, Stash doesn't change the setting if there is no keyfile entry, so it
393 * remains whatever it was initialized/set to by user code.
394 * @c TRUE by default. */
395 void stash_group_set_use_defaults(StashGroup *group, gboolean use_defaults)
397 group->use_defaults = use_defaults;
401 static StashPref *
402 add_pref(StashGroup *group, GType type, gpointer setting,
403 const gchar *key_name, gpointer default_value)
405 StashPref init = {type, setting, key_name, default_value, G_TYPE_NONE, NULL, {NULL}};
406 StashPref *entry = g_slice_new(StashPref);
408 *entry = init;
410 /* init any pointer settings to NULL so they can be freed later */
411 if (type == G_TYPE_STRING ||
412 type == G_TYPE_STRV)
413 if (group->use_defaults)
414 *(gpointer**)setting = NULL;
416 g_ptr_array_add(group->entries, entry);
417 return entry;
421 /** Adds boolean 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_boolean(StashGroup *group, gboolean *setting,
427 const gchar *key_name, gboolean default_value)
429 add_pref(group, G_TYPE_BOOLEAN, setting, key_name, GINT_TO_POINTER(default_value));
433 /** Adds integer setting.
434 * @param group .
435 * @param setting Address of setting variable.
436 * @param key_name Name for key in a @c GKeyFile.
437 * @param default_value Value to use if the key doesn't exist when loading. */
438 void stash_group_add_integer(StashGroup *group, gint *setting,
439 const gchar *key_name, gint default_value)
441 add_pref(group, G_TYPE_INT, setting, key_name, GINT_TO_POINTER(default_value));
445 /** Adds string setting.
446 * The contents of @a setting will be initialized to @c NULL.
447 * @param group .
448 * @param setting Address of setting variable.
449 * @param key_name Name for key in a @c GKeyFile.
450 * @param default_value String to copy if the key doesn't exist when loading, or @c NULL. */
451 void stash_group_add_string(StashGroup *group, gchar **setting,
452 const gchar *key_name, const gchar *default_value)
454 add_pref(group, G_TYPE_STRING, setting, key_name, (gpointer)default_value);
458 /** Adds string vector setting (array of strings).
459 * The contents of @a setting will be initialized to @c NULL.
460 * @param group .
461 * @param setting Address of setting variable.
462 * @param key_name Name for key in a @c GKeyFile.
463 * @param default_value Vector to copy if the key doesn't exist when loading. Usually @c NULL. */
464 void stash_group_add_string_vector(StashGroup *group, gchar ***setting,
465 const gchar *key_name, const gchar **default_value)
467 add_pref(group, G_TYPE_STRV, setting, key_name, (gpointer)default_value);
471 /* *** GTK-related functions *** */
473 static void handle_toggle_button(GtkWidget *widget, gboolean *setting,
474 PrefAction action)
476 switch (action)
478 case PREF_DISPLAY:
479 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), *setting);
480 break;
481 case PREF_UPDATE:
482 *setting = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
483 break;
488 static void handle_spin_button(GtkWidget *widget, StashPref *entry,
489 PrefAction action)
491 gint *setting = entry->setting;
493 g_assert(entry->setting_type == G_TYPE_INT); /* only int spin prefs */
495 switch (action)
497 case PREF_DISPLAY:
498 gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget), *setting);
499 break;
500 case PREF_UPDATE:
501 /* if the widget is focussed, the value might not be updated */
502 gtk_spin_button_update(GTK_SPIN_BUTTON(widget));
503 *setting = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(widget));
504 break;
509 static void handle_combo_box(GtkWidget *widget, StashPref *entry,
510 PrefAction action)
512 gint *setting = entry->setting;
514 switch (action)
516 case PREF_DISPLAY:
517 gtk_combo_box_set_active(GTK_COMBO_BOX(widget), *setting);
518 break;
519 case PREF_UPDATE:
520 *setting = gtk_combo_box_get_active(GTK_COMBO_BOX(widget));
521 break;
526 static void handle_entry(GtkWidget *widget, StashPref *entry,
527 PrefAction action)
529 gchararray *setting = entry->setting;
531 switch (action)
533 case PREF_DISPLAY:
534 gtk_entry_set_text(GTK_ENTRY(widget), *setting);
535 break;
536 case PREF_UPDATE:
537 g_free(*setting);
538 *setting = g_strdup(gtk_entry_get_text(GTK_ENTRY(widget)));
539 break;
544 static void handle_combo_box_entry(GtkWidget *widget, StashPref *entry,
545 PrefAction action)
547 widget = gtk_bin_get_child(GTK_BIN(widget));
548 handle_entry(widget, entry, action);
552 /* taken from Glade 2.x generated support.c */
553 static GtkWidget*
554 lookup_widget(GtkWidget *widget, const gchar *widget_name)
556 GtkWidget *parent, *found_widget;
558 g_return_val_if_fail(widget != NULL, NULL);
559 g_return_val_if_fail(widget_name != NULL, NULL);
561 for (;;)
563 if (GTK_IS_MENU(widget))
564 parent = gtk_menu_get_attach_widget(GTK_MENU(widget));
565 else
566 parent = widget->parent;
567 if (parent == NULL)
568 parent = (GtkWidget*) g_object_get_data(G_OBJECT(widget), "GladeParentKey");
569 if (parent == NULL)
570 break;
571 widget = parent;
574 found_widget = (GtkWidget*) g_object_get_data(G_OBJECT(widget), widget_name);
575 if (G_UNLIKELY(found_widget == NULL))
576 g_warning("Widget not found: %s", widget_name);
577 return found_widget;
581 static GtkWidget *
582 get_widget(GtkWidget *owner, StashWidgetID widget_id)
584 GtkWidget *widget;
586 if (owner)
587 widget = lookup_widget(owner, (const gchar *)widget_id);
588 else
589 widget = (GtkWidget *)widget_id;
591 if (!GTK_IS_WIDGET(widget))
593 g_warning("Unknown widget in %s()!", G_STRFUNC);
594 return NULL;
596 return widget;
600 static void handle_radio_button(GtkWidget *widget, gint enum_id, gboolean *setting,
601 PrefAction action)
603 switch (action)
605 case PREF_DISPLAY:
606 if (*setting == enum_id)
607 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), TRUE);
608 break;
609 case PREF_UPDATE:
610 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
611 *setting = enum_id;
612 break;
617 static void handle_radio_buttons(GtkWidget *owner, StashPref *entry,
618 PrefAction action)
620 EnumWidget *field = entry->extra.radio_buttons;
621 gsize count = 0;
622 GtkWidget *widget = NULL;
624 while (1)
626 widget = get_widget(owner, field->widget_id);
628 if (!widget)
629 continue;
631 count++;
632 handle_radio_button(widget, field->enum_id, entry->setting, action);
633 field++;
634 if (!field->widget_id)
635 break;
637 if (g_slist_length(gtk_radio_button_get_group(GTK_RADIO_BUTTON(widget))) != count)
638 g_warning("Missing/invalid radio button widget IDs found!");
642 static void handle_widget_property(GtkWidget *widget, StashPref *entry,
643 PrefAction action)
645 GObject *object = G_OBJECT(widget);
646 const gchar *name = entry->extra.property_name;
648 switch (action)
650 case PREF_DISPLAY:
651 g_object_set(object, name, entry->setting, NULL);
652 break;
653 case PREF_UPDATE:
654 if (entry->setting_type == G_TYPE_STRING)
655 g_free(entry->setting);
656 /* TODO: Which other types need freeing here? */
658 g_object_get(object, name, entry->setting, NULL);
659 break;
664 static void pref_action(PrefAction action, StashGroup *group, GtkWidget *owner)
666 StashPref *entry;
667 guint i;
669 foreach_ptr_array(entry, i, group->entries)
671 GtkWidget *widget;
673 /* ignore settings with no widgets */
674 if (entry->widget_type == G_TYPE_NONE)
675 continue;
677 /* radio buttons have several widgets */
678 if (entry->widget_type == GTK_TYPE_RADIO_BUTTON)
680 handle_radio_buttons(owner, entry, action);
681 continue;
684 widget = get_widget(owner, entry->widget_id);
685 if (!widget)
687 g_warning("Unknown widget for %s::%s in %s()!", group->name, entry->key_name,
688 G_STRFUNC);
689 continue;
692 /* note: can't use switch for GTK_TYPE macros */
693 if (entry->widget_type == GTK_TYPE_TOGGLE_BUTTON)
694 handle_toggle_button(widget, entry->setting, action);
695 else if (entry->widget_type == GTK_TYPE_SPIN_BUTTON)
696 handle_spin_button(widget, entry, action);
697 else if (entry->widget_type == GTK_TYPE_COMBO_BOX)
698 handle_combo_box(widget, entry, action);
699 else if (entry->widget_type == GTK_TYPE_COMBO_BOX_ENTRY)
700 handle_combo_box_entry(widget, entry, action);
701 else if (entry->widget_type == GTK_TYPE_ENTRY)
702 handle_entry(widget, entry, action);
703 else if (entry->widget_type == G_TYPE_PARAM)
704 handle_widget_property(widget, entry, action);
705 else
706 g_warning("Unhandled type for %s::%s in %s()!", group->name, entry->key_name,
707 G_STRFUNC);
712 /** Applies Stash settings to widgets, usually called before displaying the widgets.
713 * The @a owner argument depends on which type you use for @ref StashWidgetID.
714 * @param group .
715 * @param owner If non-NULL, used to lookup widgets by name, otherwise
716 * widget pointers are assumed.
717 * @see stash_group_update(). */
718 void stash_group_display(StashGroup *group, GtkWidget *owner)
720 pref_action(PREF_DISPLAY, group, owner);
724 /** Applies widget values to Stash settings, usually called after displaying the widgets.
725 * The @a owner argument depends on which type you use for @ref StashWidgetID.
726 * @param group .
727 * @param owner If non-NULL, used to lookup widgets by name, otherwise
728 * widget pointers are assumed.
729 * @see stash_group_display(). */
730 void stash_group_update(StashGroup *group, GtkWidget *owner)
732 pref_action(PREF_UPDATE, group, owner);
736 static StashPref *
737 add_widget_pref(StashGroup *group, GType setting_type, gpointer setting,
738 const gchar *key_name, gpointer default_value,
739 GType widget_type, StashWidgetID widget_id)
741 StashPref *entry =
742 add_pref(group, setting_type, setting, key_name, default_value);
744 entry->widget_type = widget_type;
745 entry->widget_id = widget_id;
746 return entry;
750 /** Adds a @c GtkToggleButton (or @c GtkCheckButton) widget pref.
751 * @param group .
752 * @param setting Address of setting variable.
753 * @param key_name Name for key in a @c GKeyFile.
754 * @param default_value Value to use if the key doesn't exist when loading.
755 * @param widget_id @c GtkWidget pointer or string to lookup widget later.
756 * @see stash_group_add_radio_buttons(). */
757 void stash_group_add_toggle_button(StashGroup *group, gboolean *setting,
758 const gchar *key_name, gboolean default_value, StashWidgetID widget_id)
760 add_widget_pref(group, G_TYPE_BOOLEAN, setting, key_name, GINT_TO_POINTER(default_value),
761 GTK_TYPE_TOGGLE_BUTTON, widget_id);
765 /** Adds a @c GtkRadioButton widget group pref.
766 * @param group .
767 * @param setting Address of setting variable.
768 * @param key_name Name for key in a @c GKeyFile.
769 * @param default_value Value to use if the key doesn't exist when loading.
770 * @param widget_id @c GtkWidget pointer or string to lookup widget later.
771 * @param enum_id Enum value for @a widget_id.
772 * @param ... pairs of @a widget_id, @a enum_id.
773 * Example (using widget lookup strings, but widget pointers can also work):
774 * @code
775 * enum {FOO, BAR};
776 * stash_group_add_radio_buttons(group, &which_one_setting, "which_one", BAR,
777 * "radio_foo", FOO, "radio_bar", BAR, NULL);
778 * @endcode */
779 void stash_group_add_radio_buttons(StashGroup *group, gint *setting,
780 const gchar *key_name, gint default_value,
781 StashWidgetID widget_id, gint enum_id, ...)
783 StashPref *entry =
784 add_widget_pref(group, G_TYPE_INT, setting, key_name, GINT_TO_POINTER(default_value),
785 GTK_TYPE_RADIO_BUTTON, NULL);
786 va_list args;
787 gsize count = 1;
788 EnumWidget *item, *array;
790 /* count pairs of args */
791 va_start(args, enum_id);
792 while (1)
794 if (!va_arg(args, gpointer))
795 break;
796 va_arg(args, gint);
797 count++;
799 va_end(args);
801 array = g_new0(EnumWidget, count + 1);
802 entry->extra.radio_buttons = array;
804 va_start(args, enum_id);
805 foreach_c_array(item, array, count)
807 if (item == array)
809 /* first element */
810 item->widget_id = widget_id;
811 item->enum_id = enum_id;
813 else
815 item->widget_id = va_arg(args, gpointer);
816 item->enum_id = va_arg(args, gint);
819 va_end(args);
823 /** Adds a @c GtkSpinButton widget pref.
824 * @param group .
825 * @param setting Address of setting variable.
826 * @param key_name Name for key in a @c GKeyFile.
827 * @param default_value Value to use if the key doesn't exist when loading.
828 * @param widget_id @c GtkWidget pointer or string to lookup widget later. */
829 void stash_group_add_spin_button_integer(StashGroup *group, gint *setting,
830 const gchar *key_name, gint default_value, StashWidgetID widget_id)
832 add_widget_pref(group, G_TYPE_INT, setting, key_name, GINT_TO_POINTER(default_value),
833 GTK_TYPE_SPIN_BUTTON, widget_id);
837 /** Adds a @c GtkComboBox widget pref.
838 * @param group .
839 * @param setting Address of setting variable.
840 * @param key_name Name for key in a @c GKeyFile.
841 * @param default_value Value to use if the key doesn't exist when loading.
842 * @param widget_id @c GtkWidget pointer or string to lookup widget later.
843 * @see stash_group_add_combo_box_entry(). */
844 void stash_group_add_combo_box(StashGroup *group, gint *setting,
845 const gchar *key_name, gint default_value, StashWidgetID widget_id)
847 add_widget_pref(group, G_TYPE_INT, setting, key_name, GINT_TO_POINTER(default_value),
848 GTK_TYPE_COMBO_BOX, widget_id);
852 /** Adds a @c GtkComboBoxEntry widget pref.
853 * @param group .
854 * @param setting Address of setting variable.
855 * @param key_name Name for key in a @c GKeyFile.
856 * @param default_value Value to use if the key doesn't exist when loading.
857 * @param widget_id @c GtkWidget pointer or string to lookup widget later. */
858 /* We could maybe also have something like stash_group_add_combo_box_entry_with_menu()
859 * for the history list - or should that be stored as a separate setting? */
860 void stash_group_add_combo_box_entry(StashGroup *group, gchar **setting,
861 const gchar *key_name, const gchar *default_value, StashWidgetID widget_id)
863 add_widget_pref(group, G_TYPE_STRING, setting, key_name, (gpointer)default_value,
864 GTK_TYPE_COMBO_BOX_ENTRY, widget_id);
868 /** Adds a @c GtkEntry widget pref.
869 * @param group .
870 * @param setting Address of setting variable.
871 * @param key_name Name for key in a @c GKeyFile.
872 * @param default_value Value to use if the key doesn't exist when loading.
873 * @param widget_id @c GtkWidget pointer or string to lookup widget later. */
874 void stash_group_add_entry(StashGroup *group, gchar **setting,
875 const gchar *key_name, const gchar *default_value, StashWidgetID widget_id)
877 add_widget_pref(group, G_TYPE_STRING, setting, key_name, (gpointer)default_value,
878 GTK_TYPE_ENTRY, widget_id);
882 static GType object_get_property_type(GObject *object, const gchar *property_name)
884 GObjectClass *klass = G_OBJECT_GET_CLASS(object);
885 GParamSpec *ps;
887 ps = g_object_class_find_property(klass, property_name);
888 return ps->value_type;
892 /** Adds a widget's read/write property to the stash group.
893 * The property will be set when calling
894 * stash_group_display(), and read when calling stash_group_update().
895 * @param group .
896 * @param setting Address of e.g. an integer if using an integer property.
897 * @param key_name Name for key in a @c GKeyFile.
898 * @param default_value Value to use if the key doesn't exist when loading.
899 * Should be cast into a pointer e.g. with @c GINT_TO_POINTER().
900 * @param widget_id @c GtkWidget pointer or string to lookup widget later.
901 * @param property_name .
902 * @param type can be @c 0 if passing a @c GtkWidget as the @a widget_id argument to look it up from the
903 * @c GObject data.
904 * @warning Currently only string GValue properties will be freed before setting; patch for
905 * other types - see @c handle_widget_property(). */
906 void stash_group_add_widget_property(StashGroup *group, gpointer setting,
907 const gchar *key_name, gpointer default_value, StashWidgetID widget_id,
908 const gchar *property_name, GType type)
910 if (!type)
911 type = object_get_property_type(G_OBJECT(widget_id), property_name);
913 add_widget_pref(group, type, setting, key_name, default_value,
914 G_TYPE_PARAM, widget_id)->extra.property_name = property_name;
918 enum
920 STASH_TREE_NAME,
921 STASH_TREE_VALUE,
922 STASH_TREE_COUNT
926 struct StashTreeValue
928 const gchar *group_name;
929 StashPref *pref;
930 union
932 gchararray tree_string;
933 gint tree_int;
934 } data;
937 typedef struct StashTreeValue StashTreeValue;
940 static void stash_tree_renderer_set_data(GtkCellLayout *cell_layout, GtkCellRenderer *cell,
941 GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
943 GType cell_type = GPOINTER_TO_SIZE(user_data);
944 StashTreeValue *value;
945 StashPref *pref;
946 gboolean matches_type;
948 gtk_tree_model_get(model, iter, STASH_TREE_VALUE, &value, -1);
949 pref = value->pref;
950 matches_type = pref->setting_type == cell_type;
951 g_object_set(cell, "visible", matches_type, "sensitive", matches_type,
952 cell_type == G_TYPE_BOOLEAN ? "activatable" : "editable", matches_type, NULL);
954 if (matches_type)
956 switch (pref->setting_type)
958 case G_TYPE_BOOLEAN:
959 g_object_set(cell, "active", value->data.tree_int, NULL);
960 break;
961 case G_TYPE_INT:
963 gchar *text = g_strdup_printf("%d", value->data.tree_int);
964 g_object_set(cell, "text", text, NULL);
965 g_free(text);
966 break;
968 case G_TYPE_STRING:
969 g_object_set(cell, "text", value->data.tree_string, NULL);
970 break;
976 static void stash_tree_renderer_edited(gchar *path_str, gchar *new_text, GtkTreeModel *model)
978 GtkTreePath *path;
979 GtkTreeIter iter;
980 StashTreeValue *value;
981 StashPref *pref;
983 path = gtk_tree_path_new_from_string(path_str);
984 gtk_tree_model_get_iter(model, &iter, path);
985 gtk_tree_model_get(model, &iter, STASH_TREE_VALUE, &value, -1);
986 pref = value->pref;
988 switch (pref->setting_type)
990 case G_TYPE_BOOLEAN:
991 value->data.tree_int = !value->data.tree_int;
992 break;
993 case G_TYPE_INT:
994 value->data.tree_int = atoi(new_text);
995 break;
996 case G_TYPE_STRING:
997 SETPTR(value->data.tree_string, g_strdup(new_text));
998 break;
1001 gtk_tree_model_row_changed(model, path, &iter);
1002 gtk_tree_path_free(path);
1006 static void stash_tree_boolean_toggled(GtkCellRendererToggle *cell, gchar *path_str,
1007 GtkTreeModel *model)
1009 stash_tree_renderer_edited(path_str, NULL, model);
1013 static void stash_tree_string_edited(GtkCellRenderer *cell, gchar *path_str, gchar *new_text,
1014 GtkTreeModel *model)
1016 stash_tree_renderer_edited(path_str, new_text, model);
1020 static gboolean stash_tree_discard_value(GtkTreeModel *model, GtkTreePath *path,
1021 GtkTreeIter *iter, gpointer user_data)
1023 StashTreeValue *value;
1025 gtk_tree_model_get(model, iter, STASH_TREE_VALUE, &value, -1);
1026 if (value->pref->setting_type == G_TYPE_STRING)
1027 g_free(value->data.tree_string);
1028 g_free(value);
1030 return FALSE;
1034 static void stash_tree_destroy_cb(GtkWidget *widget, gpointer user_data)
1036 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
1037 gtk_tree_model_foreach(model, stash_tree_discard_value, NULL);
1041 static void stash_tree_append_pref(StashGroup *group, StashPref *entry, GtkListStore *store,
1042 PrefAction action)
1044 GtkTreeIter iter;
1045 StashTreeValue *value;
1047 value = g_new0(StashTreeValue, 1);
1049 value->group_name = group->name;
1050 value->pref = entry;
1052 gtk_list_store_append(store, &iter);
1053 gtk_list_store_set(store, &iter, STASH_TREE_NAME, entry->key_name,
1054 STASH_TREE_VALUE, value, -1);
1058 static void stash_tree_append_prefs(GPtrArray *group_array,
1059 GtkListStore *store, PrefAction action)
1061 StashGroup *group;
1062 guint i, j;
1063 StashPref *entry;
1065 foreach_ptr_array(group, i, group_array)
1067 if (group->various)
1069 foreach_ptr_array(entry, j, group->entries)
1070 stash_tree_append_pref(group, entry, store, action);
1076 /* Setups a simple editor for stash preferences based on the widget arguments.
1077 * group_array - Array of groups which's settings will be edited.
1078 * tree - GtkTreeView in which to edit the preferences. Must be empty. */
1079 void stash_tree_setup(GPtrArray *group_array, GtkTreeView *tree)
1081 GtkListStore *store;
1082 GtkTreeModel *model;
1083 GtkCellRenderer *cell;
1084 GtkTreeViewColumn *column;
1085 GtkObject *adjustment;
1087 store = gtk_list_store_new(STASH_TREE_COUNT, G_TYPE_STRING, G_TYPE_POINTER);
1088 stash_tree_append_prefs(group_array, store, PREF_DISPLAY);
1089 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store), STASH_TREE_NAME,
1090 GTK_SORT_ASCENDING);
1092 model = GTK_TREE_MODEL(store);
1093 gtk_tree_view_set_model(tree, model);
1094 g_object_unref(G_OBJECT(store));
1095 g_signal_connect(tree, "destroy", G_CALLBACK(stash_tree_destroy_cb), NULL);
1097 cell = gtk_cell_renderer_text_new();
1098 column = gtk_tree_view_column_new_with_attributes(_("Name"), cell, "text",
1099 STASH_TREE_NAME, NULL);
1100 gtk_tree_view_column_set_sort_column_id(column, STASH_TREE_NAME);
1101 gtk_tree_view_column_set_sort_indicator(column, TRUE);
1102 gtk_tree_view_append_column(tree, column);
1104 column = gtk_tree_view_column_new();
1105 gtk_tree_view_column_set_title(column, _("Value"));
1106 gtk_tree_view_append_column(tree, column);
1107 /* boolean renderer */
1108 cell = gtk_cell_renderer_toggle_new();
1109 g_signal_connect(cell, "toggled", G_CALLBACK(stash_tree_boolean_toggled), model);
1110 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(column), cell, FALSE);
1111 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(column), cell,
1112 stash_tree_renderer_set_data, GSIZE_TO_POINTER(G_TYPE_BOOLEAN), NULL);
1113 /* string renderer */
1114 cell = gtk_cell_renderer_text_new();
1115 g_object_set(cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1116 g_signal_connect(cell, "edited", G_CALLBACK(stash_tree_string_edited), model);
1117 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(column), cell, TRUE);
1118 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(column), cell,
1119 stash_tree_renderer_set_data, GSIZE_TO_POINTER(G_TYPE_STRING), NULL);
1120 /* integer renderer */
1121 cell = gtk_cell_renderer_spin_new();
1122 adjustment = gtk_adjustment_new(0, G_MININT, G_MAXINT, 1, 10, 0);
1123 g_object_set(cell, "adjustment", adjustment, NULL);
1124 g_signal_connect(cell, "edited", G_CALLBACK(stash_tree_string_edited), model);
1125 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(column), cell, FALSE);
1126 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(column), cell,
1127 stash_tree_renderer_set_data, GSIZE_TO_POINTER(G_TYPE_INT), NULL);
1131 static void stash_tree_display_pref(StashTreeValue *value, StashPref *entry)
1133 switch (entry->setting_type)
1135 case G_TYPE_BOOLEAN:
1136 value->data.tree_int = *(gboolean *) entry->setting;
1137 break;
1138 case G_TYPE_INT:
1139 value->data.tree_int = *(gint *) entry->setting;
1140 break;
1141 case G_TYPE_STRING:
1142 SETPTR(value->data.tree_string, g_strdup(*(gchararray *) entry->setting));
1143 break;
1144 default:
1145 g_warning("Unhandled type for %s::%s in %s()!", value->group_name,
1146 entry->key_name, G_STRFUNC);
1151 static void stash_tree_update_pref(StashTreeValue *value, StashPref *entry)
1153 switch (entry->setting_type)
1155 case G_TYPE_BOOLEAN:
1156 *(gboolean *) entry->setting = value->data.tree_int;
1157 break;
1158 case G_TYPE_INT:
1159 *(gint *) entry->setting = value->data.tree_int;
1160 break;
1161 case G_TYPE_STRING:
1163 gchararray *text = entry->setting;
1164 SETPTR(*text, g_strdup(value->data.tree_string));
1165 break;
1167 default:
1168 g_warning("Unhandled type for %s::%s in %s()!", value->group_name,
1169 entry->key_name, G_STRFUNC);
1174 static void stash_tree_action(GtkTreeModel *model, PrefAction action)
1176 GtkTreeIter iter;
1177 gboolean valid = gtk_tree_model_get_iter_first(model, &iter);
1178 StashTreeValue *value;
1180 while (valid)
1182 gtk_tree_model_get(model, &iter, STASH_TREE_VALUE, &value, -1);
1184 switch (action)
1186 case PREF_DISPLAY:
1187 stash_tree_display_pref(value, value->pref);
1188 break;
1189 case PREF_UPDATE:
1190 stash_tree_update_pref(value, value->pref);
1191 break;
1193 valid = gtk_tree_model_iter_next(model, &iter);
1198 void stash_tree_display(GtkTreeView *tree)
1200 stash_tree_action(gtk_tree_view_get_model(tree), PREF_DISPLAY);
1204 void stash_tree_update(GtkTreeView *tree)
1206 stash_tree_action(gtk_tree_view_get_model(tree), PREF_UPDATE);