Set release date
[geany-mirror.git] / src / stash.c
blobf056e7deb8414061812b5c58dd778c2d1a155711
1 /*
2 * stash.c - this file is part of Geany, a fast and lightweight IDE
4 * Copyright 2008 The Geany contributors
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 /**
22 * @file stash.h
23 * Lightweight library for reading/writing @c GKeyFile settings and synchronizing widgets with
24 * C variables.
26 * Note: Stash should only depend on GLib and GTK, but currently has some minor
27 * dependencies on Geany's utils.c.
29 * @section Terms
30 * 'Setting' is used only for data stored on disk or in memory.
31 * 'Pref' can also include visual widget information.
33 * @section Memory Usage
34 * Stash will not duplicate strings if they are normally static arrays, such as
35 * keyfile group names and key names, string default values, widget_id names, property names.
37 * @section String Settings
38 * String settings and other dynamically allocated settings will be initialized to NULL when
39 * added to a StashGroup (so they can safely be reassigned later).
41 * @section Widget Support
42 * Widgets very commonly used in configuration dialogs will be supported with their own function.
43 * Widgets less commonly used such as @c GtkExpander or widget settings that aren't commonly needed
44 * to be persistent won't be directly supported, to keep the library lightweight. However, you can
45 * use stash_group_add_widget_property() to also save these settings for any read/write widget
46 * property. Macros could be added for common widget properties such as @c GtkExpander:"expanded".
48 * @section settings-example Settings Example
49 * Here we have some settings for how to make a cup - whether it should be made of china
50 * and who's going to make it. (Yes, it's a stupid example).
51 * @include stash-example.c
52 * @note You might want to handle the warning/error conditions differently from above.
54 * @section prefs-example GUI Prefs Example
55 * For prefs, it's the same as the above example but you tell Stash to add widget prefs instead of
56 * just data settings.
58 * This example uses lookup strings for widgets as they are more flexible than widget pointers.
59 * Code to load and save the settings is omitted - see the first example instead.
61 * Here we show a dialog with a toggle button for whether the cup should have a handle.
62 * @include stash-gui-example.c
63 * @note This example should also work for other widget containers besides dialogs, e.g. popup menus.
66 /* Implementation Note
67 * We dynamically allocate prefs. It would be more efficient for user code to declare
68 * a static array of StashPref structs, but we don't do this because:
70 * * It would be more ugly (lots of casts and NULLs).
71 * * Less type checking.
72 * * The API & ABI would have to break when adding/changing fields.
74 * Usually the prefs code isn't what user code will spend most of its time doing, so this
75 * should be efficient enough.
78 #include "stash.h"
80 #include "support.h" /* only for _("text") */
81 #include "utils.h" /* only for foreach_*, utils_get_setting_*(). Stash should not depend on Geany. */
83 #include <stdlib.h> /* only for atoi() */
86 /* GTK3 removed ComboBoxEntry, but we need a value to differentiate combo box with and
87 * without entries, and it must not collide with other GTypes */
88 #ifdef GTK_TYPE_COMBO_BOX_ENTRY
89 # define TYPE_COMBO_BOX_ENTRY GTK_TYPE_COMBO_BOX_ENTRY
90 #else /* !GTK_TYPE_COMBO_BOX_ENTRY */
91 # define TYPE_COMBO_BOX_ENTRY get_combo_box_entry_type()
92 static GType get_combo_box_entry_type(void)
94 static volatile gsize type = 0;
95 if (g_once_init_enter(&type))
97 GType g_type = g_type_register_static_simple(GTK_TYPE_COMBO_BOX, "dummy-combo-box-entry",
98 sizeof(GtkComboBoxClass), NULL, sizeof(GtkComboBox), NULL, G_TYPE_FLAG_ABSTRACT);
99 g_once_init_leave(&type, g_type);
101 return type;
103 #endif /* !GTK_TYPE_COMBO_BOX_ENTRY */
106 struct StashPref
108 GType setting_type; /* e.g. G_TYPE_INT */
109 gpointer setting; /* Address of a variable */
110 const gchar *key_name;
111 gpointer default_value; /* Default value, e.g. (gpointer)1 */
112 GType widget_type; /* e.g. GTK_TYPE_TOGGLE_BUTTON */
113 StashWidgetID widget_id; /* (GtkWidget*) or (gchar*) */
114 union
116 struct EnumWidget *radio_buttons;
117 const gchar *property_name;
118 } extra; /* extra fields depending on widget_type */
121 typedef struct StashPref StashPref;
123 struct StashGroup
125 guint refcount; /* ref count for GBoxed implementation */
126 const gchar *name; /* group name to use in the keyfile */
127 GPtrArray *entries; /* array of (StashPref*) */
128 gboolean various; /* mark group for display/edit in stash treeview */
129 const gchar *prefix; /* text to display for Various UI instead of name */
130 gboolean use_defaults; /* use default values if there's no keyfile entry */
133 typedef struct EnumWidget
135 StashWidgetID widget_id;
136 gint enum_id;
138 EnumWidget;
141 typedef enum SettingAction
143 SETTING_READ,
144 SETTING_WRITE
146 SettingAction;
148 typedef enum PrefAction
150 PREF_DISPLAY,
151 PREF_UPDATE
153 PrefAction;
156 static void handle_boolean_setting(StashGroup *group, StashPref *se,
157 GKeyFile *config, SettingAction action)
159 gboolean *setting = se->setting;
161 switch (action)
163 case SETTING_READ:
164 *setting = utils_get_setting_boolean(config, group->name, se->key_name,
165 GPOINTER_TO_INT(se->default_value));
166 break;
167 case SETTING_WRITE:
168 g_key_file_set_boolean(config, group->name, se->key_name, *setting);
169 break;
174 static void handle_integer_setting(StashGroup *group, StashPref *se,
175 GKeyFile *config, SettingAction action)
177 gint *setting = se->setting;
179 switch (action)
181 case SETTING_READ:
182 *setting = utils_get_setting_integer(config, group->name, se->key_name,
183 GPOINTER_TO_INT(se->default_value));
184 break;
185 case SETTING_WRITE:
186 g_key_file_set_integer(config, group->name, se->key_name, *setting);
187 break;
192 static void handle_string_setting(StashGroup *group, StashPref *se,
193 GKeyFile *config, SettingAction action)
195 gchararray *setting = se->setting;
197 switch (action)
199 case SETTING_READ:
200 g_free(*setting);
201 *setting = utils_get_setting_string(config, group->name, se->key_name,
202 se->default_value);
203 break;
204 case SETTING_WRITE:
205 g_key_file_set_string(config, group->name, se->key_name,
206 *setting ? *setting : "");
207 break;
212 static void handle_strv_setting(StashGroup *group, StashPref *se,
213 GKeyFile *config, SettingAction action)
215 gchararray **setting = se->setting;
217 switch (action)
219 case SETTING_READ:
220 g_strfreev(*setting);
221 *setting = g_key_file_get_string_list(config, group->name, se->key_name,
222 NULL, NULL);
223 if (*setting == NULL)
224 *setting = g_strdupv(se->default_value);
225 break;
227 case SETTING_WRITE:
229 /* don't try to save a NULL string vector */
230 const gchar *dummy[] = { "", NULL };
231 const gchar **strv = *setting ? (const gchar **)*setting : dummy;
233 g_key_file_set_string_list(config, group->name, se->key_name,
234 strv, g_strv_length((gchar **)strv));
235 break;
241 static void keyfile_action(SettingAction action, StashGroup *group, GKeyFile *keyfile)
243 StashPref *entry;
244 guint i;
246 foreach_ptr_array(entry, i, group->entries)
248 /* don't override settings with default values */
249 if (!group->use_defaults && action == SETTING_READ &&
250 !g_key_file_has_key(keyfile, group->name, entry->key_name, NULL))
251 continue;
253 switch (entry->setting_type)
255 case G_TYPE_BOOLEAN:
256 handle_boolean_setting(group, entry, keyfile, action); break;
257 case G_TYPE_INT:
258 handle_integer_setting(group, entry, keyfile, action); break;
259 case G_TYPE_STRING:
260 handle_string_setting(group, entry, keyfile, action); break;
261 default:
262 /* Note: G_TYPE_STRV is not a constant, can't use case label */
263 if (entry->setting_type == G_TYPE_STRV)
264 handle_strv_setting(group, entry, keyfile, action);
265 else
266 g_warning("Unhandled type for %s::%s in %s()!", group->name, entry->key_name,
267 G_STRFUNC);
273 /** Reads key values from @a keyfile into the group settings.
274 * @note You should still call this even if the keyfile couldn't be loaded from disk
275 * so that all Stash settings are initialized to defaults.
276 * @param group .
277 * @param keyfile Usually loaded from a configuration file first. */
278 GEANY_API_SYMBOL
279 void stash_group_load_from_key_file(StashGroup *group, GKeyFile *keyfile)
281 keyfile_action(SETTING_READ, group, keyfile);
285 /** Writes group settings into key values in @a keyfile.
286 * @a keyfile is usually written to a configuration file afterwards.
287 * @param group .
288 * @param keyfile . */
289 GEANY_API_SYMBOL
290 void stash_group_save_to_key_file(StashGroup *group, GKeyFile *keyfile)
292 keyfile_action(SETTING_WRITE, group, keyfile);
296 /** Reads group settings from a configuration file using @c GKeyFile.
297 * @note Stash settings will be initialized to defaults if the keyfile
298 * couldn't be loaded from disk.
299 * @param group .
300 * @param filename Filename of the file to read, in locale encoding.
301 * @return @c TRUE if a key file could be loaded.
302 * @see stash_group_load_from_key_file().
304 GEANY_API_SYMBOL
305 gboolean stash_group_load_from_file(StashGroup *group, const gchar *filename)
307 GKeyFile *keyfile;
308 gboolean ret;
310 keyfile = g_key_file_new();
311 ret = g_key_file_load_from_file(keyfile, filename, 0, NULL);
312 /* even on failure we load settings to apply defaults */
313 stash_group_load_from_key_file(group, keyfile);
315 g_key_file_free(keyfile);
316 return ret;
320 /** Writes group settings to a configuration file using @c GKeyFile.
322 * @param group .
323 * @param filename Filename of the file to write, in locale encoding.
324 * @param flags Keyfile options - @c G_KEY_FILE_NONE is the most efficient.
325 * @return 0 if the file was successfully written, otherwise the @c errno of the
326 * failed operation is returned.
327 * @see stash_group_save_to_key_file().
329 GEANY_API_SYMBOL
330 gint stash_group_save_to_file(StashGroup *group, const gchar *filename,
331 GKeyFileFlags flags)
333 GKeyFile *keyfile;
334 gchar *data;
335 gint ret;
337 keyfile = g_key_file_new();
338 /* if we need to keep comments or translations, try to load first */
339 if (flags)
340 g_key_file_load_from_file(keyfile, filename, flags, NULL);
342 stash_group_save_to_key_file(group, keyfile);
343 data = g_key_file_to_data(keyfile, NULL, NULL);
344 ret = utils_write_file(filename, data);
345 g_free(data);
346 g_key_file_free(keyfile);
347 return ret;
351 static void free_stash_pref(StashPref *pref)
353 if (pref->widget_type == GTK_TYPE_RADIO_BUTTON)
354 g_free(pref->extra.radio_buttons);
356 g_slice_free(StashPref, pref);
360 /** Creates a new group.
361 * @param name Name used for @c GKeyFile group.
362 * @return Group. */
363 GEANY_API_SYMBOL
364 StashGroup *stash_group_new(const gchar *name)
366 StashGroup *group = g_slice_new0(StashGroup);
368 group->name = name;
369 group->entries = g_ptr_array_new_with_free_func((GDestroyNotify) free_stash_pref);
370 group->use_defaults = TRUE;
371 group->refcount = 1;
372 return group;
376 /** Frees the memory allocated for setting values in a group.
377 * Useful e.g. to avoid freeing strings individually.
378 * @note This is *not* called by stash_group_free().
379 * @param group . */
380 GEANY_API_SYMBOL
381 void stash_group_free_settings(StashGroup *group)
383 StashPref *entry;
384 guint i;
386 foreach_ptr_array(entry, i, group->entries)
388 if (entry->setting_type == G_TYPE_STRING)
389 g_free(*(gchararray *) entry->setting);
390 else if (entry->setting_type == G_TYPE_STRV)
391 g_strfreev(*(gchararray **) entry->setting);
392 else
393 continue;
395 *(gpointer**) entry->setting = NULL;
400 static StashGroup *stash_group_dup(StashGroup *src)
402 g_atomic_int_inc(&src->refcount);
404 return src;
408 /** Frees a group.
409 * @param group . */
410 GEANY_API_SYMBOL
411 void stash_group_free(StashGroup *group)
413 if (g_atomic_int_dec_and_test(&group->refcount))
415 g_ptr_array_free(group->entries, TRUE);
416 g_slice_free(StashGroup, group);
421 /** Gets the GBoxed-derived GType for StashGroup
423 * @return StashGroup type . */
424 GEANY_API_SYMBOL
425 GType stash_group_get_type(void);
427 G_DEFINE_BOXED_TYPE(StashGroup, stash_group, stash_group_dup, stash_group_free);
430 /* Used for selecting groups passed to stash_tree_setup().
431 * @param various @c FALSE by default.
432 * @param prefix @nullable Group prefix or @c NULL to use @c group->name. */
433 void stash_group_set_various(StashGroup *group, gboolean various,
434 const gchar *prefix)
436 group->various = various;
437 group->prefix = prefix;
441 /* When @c FALSE, Stash doesn't change the setting if there is no keyfile entry, so it
442 * remains whatever it was initialized/set to by user code.
443 * @c TRUE by default. */
444 void stash_group_set_use_defaults(StashGroup *group, gboolean use_defaults)
446 group->use_defaults = use_defaults;
450 static StashPref *
451 add_pref(StashGroup *group, GType type, gpointer setting,
452 const gchar *key_name, gpointer default_value)
454 StashPref init = {type, setting, key_name, default_value, G_TYPE_NONE, NULL, {NULL}};
455 StashPref *entry = g_slice_new(StashPref);
457 *entry = init;
459 /* init any pointer settings to NULL so they can be freed later */
460 if (type == G_TYPE_STRING ||
461 type == G_TYPE_STRV)
462 if (group->use_defaults)
463 *(gpointer**)setting = NULL;
465 g_ptr_array_add(group->entries, entry);
466 return entry;
470 /** Adds boolean setting.
471 * @param group .
472 * @param setting Address of setting variable.
473 * @param key_name Name for key in a @c GKeyFile.
474 * @param default_value Value to use if the key doesn't exist when loading. */
475 GEANY_API_SYMBOL
476 void stash_group_add_boolean(StashGroup *group, gboolean *setting,
477 const gchar *key_name, gboolean default_value)
479 add_pref(group, G_TYPE_BOOLEAN, setting, key_name, GINT_TO_POINTER(default_value));
483 /** Adds integer setting.
484 * @param group .
485 * @param setting Address of setting variable.
486 * @param key_name Name for key in a @c GKeyFile.
487 * @param default_value Value to use if the key doesn't exist when loading. */
488 GEANY_API_SYMBOL
489 void stash_group_add_integer(StashGroup *group, gint *setting,
490 const gchar *key_name, gint default_value)
492 add_pref(group, G_TYPE_INT, setting, key_name, GINT_TO_POINTER(default_value));
496 /** Adds string setting.
497 * The contents of @a setting will be initialized to @c NULL.
498 * @param group .
499 * @param setting Address of setting variable.
500 * @param key_name Name for key in a @c GKeyFile.
501 * @param default_value @nullable String to copy if the key doesn't exist when loading, or @c NULL. */
502 GEANY_API_SYMBOL
503 void stash_group_add_string(StashGroup *group, gchar **setting,
504 const gchar *key_name, const gchar *default_value)
506 add_pref(group, G_TYPE_STRING, setting, key_name, (gpointer)default_value);
510 /** Adds string vector setting (array of strings).
511 * The contents of @a setting will be initialized to @c NULL.
512 * @param group .
513 * @param setting Address of setting variable.
514 * @param key_name Name for key in a @c GKeyFile.
515 * @param default_value Vector to copy if the key doesn't exist when loading. Usually @c NULL. */
516 GEANY_API_SYMBOL
517 void stash_group_add_string_vector(StashGroup *group, gchar ***setting,
518 const gchar *key_name, const gchar **default_value)
520 add_pref(group, G_TYPE_STRV, setting, key_name, (gpointer)default_value);
524 /* *** GTK-related functions *** */
526 static void handle_toggle_button(GtkWidget *widget, gboolean *setting,
527 PrefAction action)
529 switch (action)
531 case PREF_DISPLAY:
532 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), *setting);
533 break;
534 case PREF_UPDATE:
535 *setting = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
536 break;
541 static void handle_spin_button(GtkWidget *widget, StashPref *entry,
542 PrefAction action)
544 gint *setting = entry->setting;
546 g_assert(entry->setting_type == G_TYPE_INT); /* only int spin prefs */
548 switch (action)
550 case PREF_DISPLAY:
551 gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget), *setting);
552 break;
553 case PREF_UPDATE:
554 /* if the widget is focussed, the value might not be updated */
555 gtk_spin_button_update(GTK_SPIN_BUTTON(widget));
556 *setting = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(widget));
557 break;
562 static void handle_combo_box(GtkWidget *widget, StashPref *entry,
563 PrefAction action)
565 gint *setting = entry->setting;
567 switch (action)
569 case PREF_DISPLAY:
570 gtk_combo_box_set_active(GTK_COMBO_BOX(widget), *setting);
571 break;
572 case PREF_UPDATE:
573 *setting = gtk_combo_box_get_active(GTK_COMBO_BOX(widget));
574 break;
579 static void handle_entry(GtkWidget *widget, StashPref *entry,
580 PrefAction action)
582 gchararray *setting = entry->setting;
584 switch (action)
586 case PREF_DISPLAY:
587 gtk_entry_set_text(GTK_ENTRY(widget), *setting);
588 break;
589 case PREF_UPDATE:
590 g_free(*setting);
591 *setting = g_strdup(gtk_entry_get_text(GTK_ENTRY(widget)));
592 break;
597 static void handle_combo_box_entry(GtkWidget *widget, StashPref *entry,
598 PrefAction action)
600 widget = gtk_bin_get_child(GTK_BIN(widget));
601 handle_entry(widget, entry, action);
605 /* taken from Glade 2.x generated support.c */
606 static GtkWidget*
607 lookup_widget(GtkWidget *widget, const gchar *widget_name)
609 GtkWidget *parent, *found_widget;
611 g_return_val_if_fail(widget != NULL, NULL);
612 g_return_val_if_fail(widget_name != NULL, NULL);
614 for (;;)
616 if (GTK_IS_MENU(widget))
617 parent = gtk_menu_get_attach_widget(GTK_MENU(widget));
618 else
619 parent = gtk_widget_get_parent(widget);
620 if (parent == NULL)
621 parent = (GtkWidget*) g_object_get_data(G_OBJECT(widget), "GladeParentKey");
622 if (parent == NULL)
623 break;
624 widget = parent;
627 found_widget = (GtkWidget*) g_object_get_data(G_OBJECT(widget), widget_name);
628 if (G_UNLIKELY(found_widget == NULL))
629 g_warning("Widget not found: %s", widget_name);
630 return found_widget;
634 static GtkWidget *
635 get_widget(GtkWidget *owner, StashWidgetID widget_id)
637 GtkWidget *widget;
639 if (owner)
640 widget = lookup_widget(owner, (const gchar *)widget_id);
641 else
642 widget = (GtkWidget *)widget_id;
644 if (!GTK_IS_WIDGET(widget))
646 g_warning("Unknown widget in %s()!", G_STRFUNC);
647 return NULL;
649 return widget;
653 static void handle_radio_button(GtkWidget *widget, gint enum_id, gboolean *setting,
654 PrefAction action)
656 switch (action)
658 case PREF_DISPLAY:
659 if (*setting == enum_id)
660 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), TRUE);
661 break;
662 case PREF_UPDATE:
663 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
664 *setting = enum_id;
665 break;
670 static void handle_radio_buttons(GtkWidget *owner, StashPref *entry,
671 PrefAction action)
673 EnumWidget *field = entry->extra.radio_buttons;
674 gsize count = 0;
675 GtkWidget *widget = NULL;
677 while (1)
679 widget = get_widget(owner, field->widget_id);
681 if (!widget)
682 continue;
684 count++;
685 handle_radio_button(widget, field->enum_id, entry->setting, action);
686 field++;
687 if (!field->widget_id)
688 break;
690 if (g_slist_length(gtk_radio_button_get_group(GTK_RADIO_BUTTON(widget))) != count)
691 g_warning("Missing/invalid radio button widget IDs found!");
695 static void handle_widget_property(GtkWidget *widget, StashPref *entry,
696 PrefAction action)
698 GObject *object = G_OBJECT(widget);
699 const gchar *name = entry->extra.property_name;
701 switch (action)
703 case PREF_DISPLAY:
704 if (entry->setting_type == G_TYPE_BOOLEAN)
705 g_object_set(object, name, *(gboolean*)entry->setting, NULL);
706 else if (entry->setting_type == G_TYPE_INT)
707 g_object_set(object, name, *(gint*)entry->setting, NULL);
708 else if (entry->setting_type == G_TYPE_STRING)
709 g_object_set(object, name, *(gchararray*)entry->setting, NULL);
710 else if (entry->setting_type == G_TYPE_STRV)
711 g_object_set(object, name, *(gchararray**)entry->setting, NULL);
712 else
714 g_warning("Unhandled type %s for %s in %s()!", g_type_name(entry->setting_type),
715 entry->key_name, G_STRFUNC);
717 break;
718 case PREF_UPDATE:
719 if (entry->setting_type == G_TYPE_STRING)
720 g_free(*(gchararray*)entry->setting);
721 else if (entry->setting_type == G_TYPE_STRV)
722 g_strfreev(*(gchararray**)entry->setting);
724 g_object_get(object, name, entry->setting, NULL);
725 break;
730 static void pref_action(PrefAction action, StashGroup *group, GtkWidget *owner)
732 StashPref *entry;
733 guint i;
735 foreach_ptr_array(entry, i, group->entries)
737 GtkWidget *widget;
739 /* ignore settings with no widgets */
740 if (entry->widget_type == G_TYPE_NONE)
741 continue;
743 /* radio buttons have several widgets */
744 if (entry->widget_type == GTK_TYPE_RADIO_BUTTON)
746 handle_radio_buttons(owner, entry, action);
747 continue;
750 widget = get_widget(owner, entry->widget_id);
751 if (!widget)
753 g_warning("Unknown widget for %s::%s in %s()!", group->name, entry->key_name,
754 G_STRFUNC);
755 continue;
758 /* note: can't use switch for GTK_TYPE macros */
759 if (entry->widget_type == GTK_TYPE_TOGGLE_BUTTON)
760 handle_toggle_button(widget, entry->setting, action);
761 else if (entry->widget_type == GTK_TYPE_SPIN_BUTTON)
762 handle_spin_button(widget, entry, action);
763 else if (entry->widget_type == GTK_TYPE_COMBO_BOX)
764 handle_combo_box(widget, entry, action);
765 else if (entry->widget_type == TYPE_COMBO_BOX_ENTRY)
766 handle_combo_box_entry(widget, entry, action);
767 else if (entry->widget_type == GTK_TYPE_ENTRY)
768 handle_entry(widget, entry, action);
769 else if (entry->widget_type == G_TYPE_PARAM)
770 handle_widget_property(widget, entry, action);
771 else
772 g_warning("Unhandled type for %s::%s in %s()!", group->name, entry->key_name,
773 G_STRFUNC);
778 /** Applies Stash settings to widgets, usually called before displaying the widgets.
779 * The @a owner argument depends on which type you use for @ref StashWidgetID.
780 * @param group .
781 * @param owner If non-NULL, used to lookup widgets by name, otherwise
782 * widget pointers are assumed.
783 * @see stash_group_update(). */
784 GEANY_API_SYMBOL
785 void stash_group_display(StashGroup *group, GtkWidget *owner)
787 pref_action(PREF_DISPLAY, group, owner);
791 /** Applies widget values to Stash settings, usually called after displaying the widgets.
792 * The @a owner argument depends on which type you use for @ref StashWidgetID.
793 * @param group .
794 * @param owner If non-NULL, used to lookup widgets by name, otherwise
795 * widget pointers are assumed.
796 * @see stash_group_display(). */
797 GEANY_API_SYMBOL
798 void stash_group_update(StashGroup *group, GtkWidget *owner)
800 pref_action(PREF_UPDATE, group, owner);
804 static StashPref *
805 add_widget_pref(StashGroup *group, GType setting_type, gpointer setting,
806 const gchar *key_name, gpointer default_value,
807 GType widget_type, StashWidgetID widget_id)
809 StashPref *entry =
810 add_pref(group, setting_type, setting, key_name, default_value);
812 entry->widget_type = widget_type;
813 entry->widget_id = widget_id;
814 return entry;
818 /** Adds a @c GtkToggleButton (or @c GtkCheckButton) widget pref.
819 * @param group .
820 * @param setting Address of setting variable.
821 * @param key_name Name for key in a @c GKeyFile.
822 * @param default_value Value to use if the key doesn't exist when loading.
823 * @param widget_id @c GtkWidget pointer or string to lookup widget later.
824 * @see stash_group_add_radio_buttons(). */
825 GEANY_API_SYMBOL
826 void stash_group_add_toggle_button(StashGroup *group, gboolean *setting,
827 const gchar *key_name, gboolean default_value, StashWidgetID widget_id)
829 add_widget_pref(group, G_TYPE_BOOLEAN, setting, key_name, GINT_TO_POINTER(default_value),
830 GTK_TYPE_TOGGLE_BUTTON, widget_id);
834 /** Adds a @c GtkRadioButton widget group pref.
835 * @param group .
836 * @param setting Address of setting variable.
837 * @param key_name Name for key in a @c GKeyFile.
838 * @param default_value Value to use if the key doesn't exist when loading.
839 * @param widget_id @c GtkWidget pointer or string to lookup widget later.
840 * @param enum_id Enum value for @a widget_id.
841 * @param ... pairs of @a widget_id, @a enum_id.
842 * Example (using widget lookup strings, but widget pointers can also work):
843 * @code
844 * enum {FOO, BAR};
845 * stash_group_add_radio_buttons(group, &which_one_setting, "which_one", BAR,
846 * "radio_foo", FOO, "radio_bar", BAR, NULL);
847 * @endcode */
848 GEANY_API_SYMBOL
849 void stash_group_add_radio_buttons(StashGroup *group, gint *setting,
850 const gchar *key_name, gint default_value,
851 StashWidgetID widget_id, gint enum_id, ...)
853 StashPref *entry =
854 add_widget_pref(group, G_TYPE_INT, setting, key_name, GINT_TO_POINTER(default_value),
855 GTK_TYPE_RADIO_BUTTON, NULL);
856 va_list args;
857 gsize count = 1;
858 EnumWidget *item, *array;
860 /* count pairs of args */
861 va_start(args, enum_id);
862 while (1)
864 if (!va_arg(args, gpointer))
865 break;
866 va_arg(args, gint);
867 count++;
869 va_end(args);
871 array = g_new0(EnumWidget, count + 1);
872 entry->extra.radio_buttons = array;
874 va_start(args, enum_id);
875 foreach_c_array(item, array, count)
877 if (item == array)
879 /* first element */
880 item->widget_id = widget_id;
881 item->enum_id = enum_id;
883 else
885 item->widget_id = va_arg(args, gpointer);
886 item->enum_id = va_arg(args, gint);
889 va_end(args);
893 /** Adds a @c GtkSpinButton widget pref.
894 * @param group .
895 * @param setting Address of setting variable.
896 * @param key_name Name for key in a @c GKeyFile.
897 * @param default_value Value to use if the key doesn't exist when loading.
898 * @param widget_id @c GtkWidget pointer or string to lookup widget later. */
899 GEANY_API_SYMBOL
900 void stash_group_add_spin_button_integer(StashGroup *group, gint *setting,
901 const gchar *key_name, gint default_value, StashWidgetID widget_id)
903 add_widget_pref(group, G_TYPE_INT, setting, key_name, GINT_TO_POINTER(default_value),
904 GTK_TYPE_SPIN_BUTTON, widget_id);
908 /** Adds a @c GtkComboBox widget pref.
909 * @param group .
910 * @param setting Address of setting variable.
911 * @param key_name Name for key in a @c GKeyFile.
912 * @param default_value Value to use if the key doesn't exist when loading.
913 * @param widget_id @c GtkWidget pointer or string to lookup widget later.
914 * @see stash_group_add_combo_box_entry(). */
915 GEANY_API_SYMBOL
916 void stash_group_add_combo_box(StashGroup *group, gint *setting,
917 const gchar *key_name, gint default_value, StashWidgetID widget_id)
919 add_widget_pref(group, G_TYPE_INT, setting, key_name, GINT_TO_POINTER(default_value),
920 GTK_TYPE_COMBO_BOX, widget_id);
924 /** Adds a @c GtkComboBoxEntry widget pref.
925 * @param group .
926 * @param setting Address of setting variable.
927 * @param key_name Name for key in a @c GKeyFile.
928 * @param default_value Value to use if the key doesn't exist when loading.
929 * @param widget_id @c GtkWidget pointer or string to lookup widget later. */
930 /* We could maybe also have something like stash_group_add_combo_box_entry_with_menu()
931 * for the history list - or should that be stored as a separate setting? */
932 GEANY_API_SYMBOL
933 void stash_group_add_combo_box_entry(StashGroup *group, gchar **setting,
934 const gchar *key_name, const gchar *default_value, StashWidgetID widget_id)
936 add_widget_pref(group, G_TYPE_STRING, setting, key_name, (gpointer)default_value,
937 TYPE_COMBO_BOX_ENTRY, widget_id);
941 /** Adds a @c GtkEntry widget pref.
942 * @param group .
943 * @param setting Address of setting variable.
944 * @param key_name Name for key in a @c GKeyFile.
945 * @param default_value Value to use if the key doesn't exist when loading.
946 * @param widget_id @c GtkWidget pointer or string to lookup widget later. */
947 GEANY_API_SYMBOL
948 void stash_group_add_entry(StashGroup *group, gchar **setting,
949 const gchar *key_name, const gchar *default_value, StashWidgetID widget_id)
951 add_widget_pref(group, G_TYPE_STRING, setting, key_name, (gpointer)default_value,
952 GTK_TYPE_ENTRY, widget_id);
956 static GType object_get_property_type(GObject *object, const gchar *property_name)
958 GObjectClass *klass = G_OBJECT_GET_CLASS(object);
959 GParamSpec *ps;
961 ps = g_object_class_find_property(klass, property_name);
962 return ps->value_type;
966 /** Adds a widget's read/write property to the stash group.
967 * The property will be set when calling
968 * stash_group_display(), and read when calling stash_group_update().
969 * @param group .
970 * @param setting Address of e.g. an integer if using an integer property.
971 * @param key_name Name for key in a @c GKeyFile.
972 * @param default_value Value to use if the key doesn't exist when loading.
973 * Should be cast into a pointer e.g. with @c GINT_TO_POINTER().
974 * @param widget_id @c GtkWidget pointer or string to lookup widget later.
975 * @param property_name .
976 * @param type can be @c 0 if passing a @c GtkWidget as the @a widget_id argument to look it up from the
977 * @c GObject data.
978 * @warning Currently only string GValue properties will be freed before setting; patch for
979 * other types - see @c handle_widget_property(). */
980 GEANY_API_SYMBOL
981 void stash_group_add_widget_property(StashGroup *group, gpointer setting,
982 const gchar *key_name, gpointer default_value, StashWidgetID widget_id,
983 const gchar *property_name, GType type)
985 if (!type)
986 type = object_get_property_type(G_OBJECT(widget_id), property_name);
988 add_widget_pref(group, type, setting, key_name, default_value,
989 G_TYPE_PARAM, widget_id)->extra.property_name = property_name;
993 enum
995 STASH_TREE_NAME,
996 STASH_TREE_VALUE,
997 STASH_TREE_COUNT
1001 struct StashTreeValue
1003 const gchar *group_name;
1004 StashPref *pref;
1005 struct
1007 gchararray tree_string;
1008 gint tree_int;
1009 } data;
1012 typedef struct StashTreeValue StashTreeValue;
1015 static void stash_tree_renderer_set_data(GtkCellLayout *cell_layout, GtkCellRenderer *cell,
1016 GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
1018 GType cell_type = GPOINTER_TO_SIZE(user_data);
1019 StashTreeValue *value;
1020 StashPref *pref;
1021 gboolean matches_type;
1023 gtk_tree_model_get(model, iter, STASH_TREE_VALUE, &value, -1);
1024 pref = value->pref;
1025 matches_type = pref->setting_type == cell_type;
1026 g_object_set(cell, "visible", matches_type, "sensitive", matches_type,
1027 cell_type == G_TYPE_BOOLEAN ? "activatable" : "editable", matches_type, NULL);
1029 if (matches_type)
1031 switch (pref->setting_type)
1033 case G_TYPE_BOOLEAN:
1034 g_object_set(cell, "active", value->data.tree_int, NULL);
1035 break;
1036 case G_TYPE_INT:
1038 gchar *text = g_strdup_printf("%d", value->data.tree_int);
1039 g_object_set(cell, "text", text, NULL);
1040 g_free(text);
1041 break;
1043 case G_TYPE_STRING:
1044 g_object_set(cell, "text", value->data.tree_string, NULL);
1045 break;
1051 static void stash_tree_renderer_edited(gchar *path_str, gchar *new_text, GtkTreeModel *model)
1053 GtkTreePath *path;
1054 GtkTreeIter iter;
1055 StashTreeValue *value;
1056 StashPref *pref;
1058 path = gtk_tree_path_new_from_string(path_str);
1059 gtk_tree_model_get_iter(model, &iter, path);
1060 gtk_tree_model_get(model, &iter, STASH_TREE_VALUE, &value, -1);
1061 pref = value->pref;
1063 switch (pref->setting_type)
1065 case G_TYPE_BOOLEAN:
1066 value->data.tree_int = !value->data.tree_int;
1067 break;
1068 case G_TYPE_INT:
1069 value->data.tree_int = atoi(new_text);
1070 break;
1071 case G_TYPE_STRING:
1072 SETPTR(value->data.tree_string, g_strdup(new_text));
1073 break;
1076 gtk_tree_model_row_changed(model, path, &iter);
1077 gtk_tree_path_free(path);
1081 static void stash_tree_boolean_toggled(GtkCellRendererToggle *cell, gchar *path_str,
1082 GtkTreeModel *model)
1084 stash_tree_renderer_edited(path_str, NULL, model);
1088 static void stash_tree_string_edited(GtkCellRenderer *cell, gchar *path_str, gchar *new_text,
1089 GtkTreeModel *model)
1091 stash_tree_renderer_edited(path_str, new_text, model);
1095 static gboolean stash_tree_discard_value(GtkTreeModel *model, GtkTreePath *path,
1096 GtkTreeIter *iter, gpointer user_data)
1098 StashTreeValue *value;
1100 gtk_tree_model_get(model, iter, STASH_TREE_VALUE, &value, -1);
1101 /* don't access value->pref as it might already have been freed */
1102 g_free(value->data.tree_string);
1103 g_free(value);
1105 return FALSE;
1109 static void stash_tree_destroy_cb(GtkWidget *widget, gpointer user_data)
1111 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
1112 gtk_tree_model_foreach(model, stash_tree_discard_value, NULL);
1116 static void stash_tree_append_pref(StashGroup *group, StashPref *entry, GtkListStore *store,
1117 PrefAction action)
1119 GtkTreeIter iter;
1120 StashTreeValue *value;
1121 gchar *text = NULL;
1123 value = g_new0(StashTreeValue, 1);
1125 value->group_name = group->name;
1126 value->pref = entry;
1128 gtk_list_store_append(store, &iter);
1129 text = g_strconcat(group->prefix ? group->prefix : group->name,
1130 ".", entry->key_name, NULL);
1131 gtk_list_store_set(store, &iter, STASH_TREE_NAME, text,
1132 STASH_TREE_VALUE, value, -1);
1133 g_free(text);
1137 static void stash_tree_append_prefs(GPtrArray *group_array,
1138 GtkListStore *store, PrefAction action)
1140 StashGroup *group;
1141 guint i, j;
1142 StashPref *entry;
1144 foreach_ptr_array(group, i, group_array)
1146 if (group->various)
1148 foreach_ptr_array(entry, j, group->entries)
1149 stash_tree_append_pref(group, entry, store, action);
1155 /* Setups a simple editor for stash preferences based on the widget arguments.
1156 * group_array - Array of groups which's settings will be edited.
1157 * tree - GtkTreeView in which to edit the preferences. Must be empty. */
1158 void stash_tree_setup(GPtrArray *group_array, GtkTreeView *tree)
1160 GtkListStore *store;
1161 GtkTreeModel *model;
1162 GtkCellRenderer *cell;
1163 GtkTreeViewColumn *column;
1164 GtkAdjustment *adjustment;
1166 store = gtk_list_store_new(STASH_TREE_COUNT, G_TYPE_STRING, G_TYPE_POINTER);
1167 stash_tree_append_prefs(group_array, store, PREF_DISPLAY);
1168 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store), STASH_TREE_NAME,
1169 GTK_SORT_ASCENDING);
1171 model = GTK_TREE_MODEL(store);
1172 gtk_tree_view_set_model(tree, model);
1173 g_object_unref(G_OBJECT(store));
1174 g_signal_connect(tree, "destroy", G_CALLBACK(stash_tree_destroy_cb), NULL);
1176 cell = gtk_cell_renderer_text_new();
1177 column = gtk_tree_view_column_new_with_attributes(_("Name"), cell, "text",
1178 STASH_TREE_NAME, NULL);
1179 gtk_tree_view_column_set_sort_column_id(column, STASH_TREE_NAME);
1180 gtk_tree_view_column_set_sort_indicator(column, TRUE);
1181 gtk_tree_view_append_column(tree, column);
1183 column = gtk_tree_view_column_new();
1184 gtk_tree_view_column_set_title(column, _("Value"));
1185 gtk_tree_view_append_column(tree, column);
1186 /* boolean renderer */
1187 cell = gtk_cell_renderer_toggle_new();
1188 g_signal_connect(cell, "toggled", G_CALLBACK(stash_tree_boolean_toggled), model);
1189 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(column), cell, FALSE);
1190 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(column), cell,
1191 stash_tree_renderer_set_data, GSIZE_TO_POINTER(G_TYPE_BOOLEAN), NULL);
1192 /* string renderer */
1193 cell = gtk_cell_renderer_text_new();
1194 g_object_set(cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1195 g_signal_connect(cell, "edited", G_CALLBACK(stash_tree_string_edited), model);
1196 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(column), cell, TRUE);
1197 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(column), cell,
1198 stash_tree_renderer_set_data, GSIZE_TO_POINTER(G_TYPE_STRING), NULL);
1199 /* integer renderer */
1200 cell = gtk_cell_renderer_spin_new();
1201 adjustment = GTK_ADJUSTMENT(gtk_adjustment_new(0, G_MININT, G_MAXINT, 1, 10, 0));
1202 g_object_set(cell, "adjustment", adjustment, NULL);
1203 g_signal_connect(cell, "edited", G_CALLBACK(stash_tree_string_edited), model);
1204 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(column), cell, FALSE);
1205 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(column), cell,
1206 stash_tree_renderer_set_data, GSIZE_TO_POINTER(G_TYPE_INT), NULL);
1210 static void stash_tree_display_pref(StashTreeValue *value, StashPref *entry)
1212 switch (entry->setting_type)
1214 case G_TYPE_BOOLEAN:
1215 value->data.tree_int = *(gboolean *) entry->setting;
1216 break;
1217 case G_TYPE_INT:
1218 value->data.tree_int = *(gint *) entry->setting;
1219 break;
1220 case G_TYPE_STRING:
1221 SETPTR(value->data.tree_string, g_strdup(*(gchararray *) entry->setting));
1222 break;
1223 default:
1224 g_warning("Unhandled type for %s::%s in %s()!", value->group_name,
1225 entry->key_name, G_STRFUNC);
1230 static void stash_tree_update_pref(StashTreeValue *value, StashPref *entry)
1232 switch (entry->setting_type)
1234 case G_TYPE_BOOLEAN:
1235 *(gboolean *) entry->setting = value->data.tree_int;
1236 break;
1237 case G_TYPE_INT:
1238 *(gint *) entry->setting = value->data.tree_int;
1239 break;
1240 case G_TYPE_STRING:
1242 gchararray *text = entry->setting;
1243 SETPTR(*text, g_strdup(value->data.tree_string));
1244 break;
1246 default:
1247 g_warning("Unhandled type for %s::%s in %s()!", value->group_name,
1248 entry->key_name, G_STRFUNC);
1253 static void stash_tree_action(GtkTreeModel *model, PrefAction action)
1255 GtkTreeIter iter;
1256 gboolean valid = gtk_tree_model_get_iter_first(model, &iter);
1257 StashTreeValue *value;
1259 while (valid)
1261 gtk_tree_model_get(model, &iter, STASH_TREE_VALUE, &value, -1);
1263 switch (action)
1265 case PREF_DISPLAY:
1266 stash_tree_display_pref(value, value->pref);
1267 break;
1268 case PREF_UPDATE:
1269 stash_tree_update_pref(value, value->pref);
1270 break;
1272 valid = gtk_tree_model_iter_next(model, &iter);
1277 void stash_tree_display(GtkTreeView *tree)
1279 stash_tree_action(gtk_tree_view_get_model(tree), PREF_DISPLAY);
1283 void stash_tree_update(GtkTreeView *tree)
1285 stash_tree_action(gtk_tree_view_get_model(tree), PREF_UPDATE);