Merge pull request #3909 from techee/infobar_fix
[geany-mirror.git] / src / stash.c
blob1a4331f530c6737728ab8026d26a0a9f206f06ad
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 a cup - whether it is made of porcelain, who made it,
50 * how many we have, and how much they cost. (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 #ifdef HAVE_CONFIG_H
79 # include "config.h"
80 #endif
82 #include "stash.h"
84 #include "support.h" /* only for _("text") */
85 #include "utils.h" /* only for foreach_*, utils_get_setting_*(). Stash should not depend on Geany. */
87 #include <stdlib.h> /* only for atoi() */
90 /* GTK3 removed ComboBoxEntry, but we need a value to differentiate combo box with and
91 * without entries, and it must not collide with other GTypes */
92 #define TYPE_COMBO_BOX_ENTRY get_combo_box_entry_type()
93 static GType get_combo_box_entry_type(void)
95 static gsize type = 0;
96 if (g_once_init_enter(&type))
98 GType g_type = g_type_register_static_simple(GTK_TYPE_COMBO_BOX, "dummy-combo-box-entry",
99 sizeof(GtkComboBoxClass), NULL, sizeof(GtkComboBox), NULL, G_TYPE_FLAG_ABSTRACT);
100 g_once_init_leave(&type, g_type);
102 return type;
105 /* storage for StashPref default values */
106 union Value
108 gboolean bool_val;
109 gint int_val;
110 gdouble double_val;
111 gchar *str_val;
112 gchar **strv_val;
113 gpointer *ptr_val;
114 GtkWidget *widget_val;
117 struct StashPref
119 GType setting_type; /* e.g. G_TYPE_INT */
120 gpointer setting; /* Address of a variable */
121 const gchar *key_name;
122 union Value default_value; /* Default value, as per setting_type above, e.g. .int_val */
123 GType widget_type; /* e.g. GTK_TYPE_TOGGLE_BUTTON */
124 StashWidgetID widget_id; /* (GtkWidget*) or (gchar*) */
125 union
127 struct EnumWidget *radio_buttons;
128 const gchar *property_name;
129 } extra; /* extra fields depending on widget_type */
132 typedef struct StashPref StashPref;
134 struct StashGroup
136 guint refcount; /* ref count for GBoxed implementation */
137 const gchar *name; /* group name to use in the keyfile */
138 GPtrArray *entries; /* array of (StashPref*) */
139 gboolean various; /* mark group for display/edit in stash treeview */
140 const gchar *prefix; /* text to display for Various UI instead of name */
141 gboolean use_defaults; /* use default values if there's no keyfile entry */
144 typedef struct EnumWidget
146 StashWidgetID widget_id;
147 gint enum_id;
149 EnumWidget;
152 typedef enum SettingAction
154 SETTING_READ,
155 SETTING_WRITE
157 SettingAction;
159 typedef enum PrefAction
161 PREF_DISPLAY,
162 PREF_UPDATE
164 PrefAction;
167 static void handle_boolean_setting(StashGroup *group, StashPref *se,
168 GKeyFile *config, SettingAction action)
170 gboolean *setting = se->setting;
172 switch (action)
174 case SETTING_READ:
175 *setting = utils_get_setting_boolean(config, group->name, se->key_name,
176 se->default_value.bool_val);
177 break;
178 case SETTING_WRITE:
179 g_key_file_set_boolean(config, group->name, se->key_name, *setting);
180 break;
185 static void handle_double_setting(StashGroup *group, StashPref *se,
186 GKeyFile *config, SettingAction action)
188 gdouble *setting = se->setting;
190 switch (action)
192 case SETTING_READ:
193 *setting = utils_get_setting_double(config, group->name, se->key_name,
194 se->default_value.double_val);
195 break;
196 case SETTING_WRITE:
197 g_key_file_set_double(config, group->name, se->key_name, *setting);
198 break;
203 static void handle_integer_setting(StashGroup *group, StashPref *se,
204 GKeyFile *config, SettingAction action)
206 gint *setting = se->setting;
208 switch (action)
210 case SETTING_READ:
211 *setting = utils_get_setting_integer(config, group->name, se->key_name,
212 se->default_value.int_val);
213 break;
214 case SETTING_WRITE:
215 g_key_file_set_integer(config, group->name, se->key_name, *setting);
216 break;
221 static void handle_string_setting(StashGroup *group, StashPref *se,
222 GKeyFile *config, SettingAction action)
224 gchararray *setting = se->setting;
226 switch (action)
228 case SETTING_READ:
229 g_free(*setting);
230 *setting = utils_get_setting_string(config, group->name, se->key_name,
231 se->default_value.str_val);
232 break;
233 case SETTING_WRITE:
234 g_key_file_set_string(config, group->name, se->key_name,
235 *setting ? *setting : "");
236 break;
241 static void handle_strv_setting(StashGroup *group, StashPref *se,
242 GKeyFile *config, SettingAction action)
244 gchararray **setting = se->setting;
246 switch (action)
248 case SETTING_READ:
249 g_strfreev(*setting);
250 *setting = g_key_file_get_string_list(config, group->name, se->key_name,
251 NULL, NULL);
252 if (*setting == NULL)
253 *setting = g_strdupv(se->default_value.strv_val);
254 break;
256 case SETTING_WRITE:
258 /* don't try to save a NULL string vector */
259 const gchar *dummy[] = { "", NULL };
260 const gchar **strv = *setting ? (const gchar **)*setting : dummy;
262 g_key_file_set_string_list(config, group->name, se->key_name,
263 strv, g_strv_length((gchar **)strv));
264 break;
270 static void keyfile_action(SettingAction action, StashGroup *group, GKeyFile *keyfile)
272 StashPref *entry;
273 guint i;
275 foreach_ptr_array(entry, i, group->entries)
277 /* don't override settings with default values */
278 if (!group->use_defaults && action == SETTING_READ &&
279 !g_key_file_has_key(keyfile, group->name, entry->key_name, NULL))
280 continue;
282 switch (entry->setting_type)
284 case G_TYPE_BOOLEAN:
285 handle_boolean_setting(group, entry, keyfile, action); break;
286 case G_TYPE_INT:
287 handle_integer_setting(group, entry, keyfile, action); break;
288 case G_TYPE_DOUBLE:
289 handle_double_setting(group, entry, keyfile, action); break;
290 case G_TYPE_STRING:
291 handle_string_setting(group, entry, keyfile, action); break;
292 default:
293 /* Note: G_TYPE_STRV is not a constant, can't use case label */
294 if (entry->setting_type == G_TYPE_STRV)
295 handle_strv_setting(group, entry, keyfile, action);
296 else
297 g_warning("Unhandled type for %s::%s in %s()!", group->name, entry->key_name,
298 G_STRFUNC);
304 /** Reads key values from @a keyfile into the group settings.
305 * @note You should still call this even if the keyfile couldn't be loaded from disk
306 * so that all Stash settings are initialized to defaults.
307 * @param group .
308 * @param keyfile Usually loaded from a configuration file first. */
309 GEANY_API_SYMBOL
310 void stash_group_load_from_key_file(StashGroup *group, GKeyFile *keyfile)
312 keyfile_action(SETTING_READ, group, keyfile);
316 /** Writes group settings into key values in @a keyfile.
317 * @a keyfile is usually written to a configuration file afterwards.
318 * @param group .
319 * @param keyfile . */
320 GEANY_API_SYMBOL
321 void stash_group_save_to_key_file(StashGroup *group, GKeyFile *keyfile)
323 keyfile_action(SETTING_WRITE, group, keyfile);
327 /** Reads group settings from a configuration file using @c GKeyFile.
328 * @note Stash settings will be initialized to defaults if the keyfile
329 * couldn't be loaded from disk.
330 * @param group .
331 * @param filename Filename of the file to read, in locale encoding.
332 * @return @c TRUE if a key file could be loaded.
333 * @see stash_group_load_from_key_file().
335 GEANY_API_SYMBOL
336 gboolean stash_group_load_from_file(StashGroup *group, const gchar *filename)
338 GKeyFile *keyfile;
339 gboolean ret;
341 keyfile = g_key_file_new();
342 ret = g_key_file_load_from_file(keyfile, filename, 0, NULL);
343 /* even on failure we load settings to apply defaults */
344 stash_group_load_from_key_file(group, keyfile);
346 g_key_file_free(keyfile);
347 return ret;
351 /** Writes group settings to a configuration file using @c GKeyFile.
353 * @param group .
354 * @param filename Filename of the file to write, in locale encoding.
355 * @param flags Keyfile options - @c G_KEY_FILE_NONE is the most efficient.
356 * @return 0 if the file was successfully written, otherwise the @c errno of the
357 * failed operation is returned.
358 * @see stash_group_save_to_key_file().
360 GEANY_API_SYMBOL
361 gint stash_group_save_to_file(StashGroup *group, const gchar *filename,
362 GKeyFileFlags flags)
364 GKeyFile *keyfile;
365 gchar *data;
366 gint ret;
368 keyfile = g_key_file_new();
369 /* if we need to keep comments or translations, try to load first */
370 if (flags)
371 g_key_file_load_from_file(keyfile, filename, flags, NULL);
373 stash_group_save_to_key_file(group, keyfile);
374 data = g_key_file_to_data(keyfile, NULL, NULL);
375 ret = utils_write_file(filename, data);
376 g_free(data);
377 g_key_file_free(keyfile);
378 return ret;
382 static void free_stash_pref(StashPref *pref)
384 if (pref->widget_type == GTK_TYPE_RADIO_BUTTON)
385 g_free(pref->extra.radio_buttons);
387 g_slice_free(StashPref, pref);
391 /** Creates a new group.
392 * @param name Name used for @c GKeyFile group.
393 * @return Group. */
394 GEANY_API_SYMBOL
395 StashGroup *stash_group_new(const gchar *name)
397 StashGroup *group = g_slice_new0(StashGroup);
399 group->name = name;
400 group->entries = g_ptr_array_new_with_free_func((GDestroyNotify) free_stash_pref);
401 group->use_defaults = TRUE;
402 group->refcount = 1;
403 return group;
407 /** Frees the memory allocated for setting values in a group.
408 * Useful e.g. to avoid freeing strings individually.
409 * @note This is *not* called by stash_group_free().
410 * @param group . */
411 GEANY_API_SYMBOL
412 void stash_group_free_settings(StashGroup *group)
414 StashPref *entry;
415 guint i;
417 foreach_ptr_array(entry, i, group->entries)
419 if (entry->setting_type == G_TYPE_STRING)
420 g_free(*(gchararray *) entry->setting);
421 else if (entry->setting_type == G_TYPE_STRV)
422 g_strfreev(*(gchararray **) entry->setting);
423 else
424 continue;
426 *(gpointer**) entry->setting = NULL;
431 static StashGroup *stash_group_dup(StashGroup *src)
433 g_atomic_int_inc(&src->refcount);
435 return src;
439 /** Frees a group.
440 * @param group . */
441 GEANY_API_SYMBOL
442 void stash_group_free(StashGroup *group)
444 if (g_atomic_int_dec_and_test(&group->refcount))
446 g_ptr_array_free(group->entries, TRUE);
447 g_slice_free(StashGroup, group);
452 /** Gets the GBoxed-derived GType for StashGroup
454 * @return StashGroup type . */
455 GEANY_API_SYMBOL
456 GType stash_group_get_type(void);
458 G_DEFINE_BOXED_TYPE(StashGroup, stash_group, stash_group_dup, stash_group_free);
461 /* Used for selecting groups passed to stash_tree_setup().
462 * @param various @c FALSE by default.
463 * @param prefix @nullable Group prefix or @c NULL to use @c group->name. */
464 void stash_group_set_various(StashGroup *group, gboolean various,
465 const gchar *prefix)
467 group->various = various;
468 group->prefix = prefix;
472 /* When @c FALSE, Stash doesn't change the setting if there is no keyfile entry, so it
473 * remains whatever it was initialized/set to by user code.
474 * @c TRUE by default. */
475 void stash_group_set_use_defaults(StashGroup *group, gboolean use_defaults)
477 group->use_defaults = use_defaults;
481 static StashPref *
482 add_pref(StashGroup *group, GType type, gpointer setting,
483 const gchar *key_name, union Value default_value)
485 StashPref *entry = g_slice_new(StashPref);
487 *entry = (StashPref) {type, setting, key_name, default_value, G_TYPE_NONE, NULL, {NULL}};
489 /* init any pointer settings to NULL so they can be freed later */
490 if (type == G_TYPE_STRING || type == G_TYPE_STRV) {
491 if (group->use_defaults)
492 *(gpointer**)setting = NULL;
495 g_ptr_array_add(group->entries, entry);
496 return entry;
500 /** Adds boolean setting.
501 * @param group .
502 * @param setting Address of setting variable.
503 * @param key_name Name for key in a @c GKeyFile.
504 * @param default_value Value to use if the key doesn't exist when loading. */
505 GEANY_API_SYMBOL
506 void stash_group_add_boolean(StashGroup *group, gboolean *setting,
507 const gchar *key_name, gboolean default_value)
509 add_pref(group, G_TYPE_BOOLEAN, setting, key_name, (union Value) {.bool_val = default_value});
513 /** Adds double setting.
514 * @param group .
515 * @param setting Address of setting variable.
516 * @param key_name Name for key in a @c GKeyFile.
517 * @param default_value Value to use if the key doesn't exist when loading. */
518 GEANY_API_SYMBOL
519 void stash_group_add_double(StashGroup *group, gdouble *setting,
520 const gchar *key_name, gdouble default_value)
522 add_pref(group, G_TYPE_DOUBLE, setting, key_name, (union Value) {.double_val = default_value});
526 /** Adds integer setting.
527 * @param group .
528 * @param setting Address of setting variable.
529 * @param key_name Name for key in a @c GKeyFile.
530 * @param default_value Value to use if the key doesn't exist when loading. */
531 GEANY_API_SYMBOL
532 void stash_group_add_integer(StashGroup *group, gint *setting,
533 const gchar *key_name, gint default_value)
535 add_pref(group, G_TYPE_INT, setting, key_name, (union Value) {.int_val = default_value});
539 /** Adds string setting.
540 * The contents of @a setting will be initialized to @c NULL.
541 * @param group .
542 * @param setting Address of setting variable.
543 * @param key_name Name for key in a @c GKeyFile.
544 * @param default_value @nullable String to copy if the key doesn't exist when loading, or @c NULL. */
545 GEANY_API_SYMBOL
546 void stash_group_add_string(StashGroup *group, gchar **setting,
547 const gchar *key_name, const gchar *default_value)
549 add_pref(group, G_TYPE_STRING, setting, key_name, (union Value) {.str_val = (gchar *) default_value});
553 /** Adds string vector setting (array of strings).
554 * The contents of @a setting will be initialized to @c NULL.
555 * @param group .
556 * @param setting Address of setting variable.
557 * @param key_name Name for key in a @c GKeyFile.
558 * @param default_value Vector to copy if the key doesn't exist when loading. Usually @c NULL. */
559 GEANY_API_SYMBOL
560 void stash_group_add_string_vector(StashGroup *group, gchar ***setting,
561 const gchar *key_name, const gchar **default_value)
563 add_pref(group, G_TYPE_STRV, setting, key_name, (union Value) {.strv_val = (gchar **) default_value});
567 /* *** GTK-related functions *** */
569 static void handle_toggle_button(GtkWidget *widget, gboolean *setting,
570 PrefAction action)
572 switch (action)
574 case PREF_DISPLAY:
575 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), *setting);
576 break;
577 case PREF_UPDATE:
578 *setting = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
579 break;
584 static void handle_spin_button(GtkWidget *widget, StashPref *entry,
585 PrefAction action)
587 gint *setting = entry->setting;
589 g_assert(entry->setting_type == G_TYPE_INT); /* only int spin prefs */
591 switch (action)
593 case PREF_DISPLAY:
594 gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget), *setting);
595 break;
596 case PREF_UPDATE:
597 /* if the widget is focussed, the value might not be updated */
598 gtk_spin_button_update(GTK_SPIN_BUTTON(widget));
599 *setting = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(widget));
600 break;
605 static void handle_combo_box(GtkWidget *widget, StashPref *entry,
606 PrefAction action)
608 gint *setting = entry->setting;
610 switch (action)
612 case PREF_DISPLAY:
613 gtk_combo_box_set_active(GTK_COMBO_BOX(widget), *setting);
614 break;
615 case PREF_UPDATE:
616 *setting = gtk_combo_box_get_active(GTK_COMBO_BOX(widget));
617 break;
622 static void handle_entry(GtkWidget *widget, StashPref *entry,
623 PrefAction action)
625 gchararray *setting = entry->setting;
627 switch (action)
629 case PREF_DISPLAY:
630 gtk_entry_set_text(GTK_ENTRY(widget), *setting);
631 break;
632 case PREF_UPDATE:
633 g_free(*setting);
634 *setting = g_strdup(gtk_entry_get_text(GTK_ENTRY(widget)));
635 break;
640 static void handle_combo_box_entry(GtkWidget *widget, StashPref *entry,
641 PrefAction action)
643 widget = gtk_bin_get_child(GTK_BIN(widget));
644 handle_entry(widget, entry, action);
648 /* taken from Glade 2.x generated support.c */
649 static GtkWidget*
650 lookup_widget(GtkWidget *widget, const gchar *widget_name)
652 GtkWidget *parent, *found_widget;
654 g_return_val_if_fail(widget != NULL, NULL);
655 g_return_val_if_fail(widget_name != NULL, NULL);
657 for (;;)
659 if (GTK_IS_MENU(widget))
660 parent = gtk_menu_get_attach_widget(GTK_MENU(widget));
661 else
662 parent = gtk_widget_get_parent(widget);
663 if (parent == NULL)
664 parent = (GtkWidget*) g_object_get_data(G_OBJECT(widget), "GladeParentKey");
665 if (parent == NULL)
666 break;
667 widget = parent;
670 found_widget = (GtkWidget*) g_object_get_data(G_OBJECT(widget), widget_name);
671 if (G_UNLIKELY(found_widget == NULL))
672 g_warning("Widget not found: %s", widget_name);
673 return found_widget;
677 static GtkWidget *
678 get_widget(GtkWidget *owner, StashWidgetID widget_id)
680 GtkWidget *widget;
682 if (owner)
683 widget = lookup_widget(owner, (const gchar *)widget_id);
684 else
685 widget = (GtkWidget *)widget_id;
687 if (!GTK_IS_WIDGET(widget))
689 g_warning("Unknown widget in %s()!", G_STRFUNC);
690 return NULL;
692 return widget;
696 static void handle_radio_button(GtkWidget *widget, gint enum_id, gboolean *setting,
697 PrefAction action)
699 switch (action)
701 case PREF_DISPLAY:
702 if (*setting == enum_id)
703 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), TRUE);
704 break;
705 case PREF_UPDATE:
706 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
707 *setting = enum_id;
708 break;
713 static void handle_radio_buttons(GtkWidget *owner, StashPref *entry,
714 PrefAction action)
716 EnumWidget *field = entry->extra.radio_buttons;
717 gsize count = 0;
718 GtkWidget *widget = NULL;
720 while (1)
722 widget = get_widget(owner, field->widget_id);
724 if (!widget)
725 continue;
727 count++;
728 handle_radio_button(widget, field->enum_id, entry->setting, action);
729 field++;
730 if (!field->widget_id)
731 break;
733 if (g_slist_length(gtk_radio_button_get_group(GTK_RADIO_BUTTON(widget))) != count)
734 g_warning("Missing/invalid radio button widget IDs found!");
738 static void handle_widget_property(GtkWidget *widget, StashPref *entry,
739 PrefAction action)
741 GObject *object = G_OBJECT(widget);
742 const gchar *name = entry->extra.property_name;
744 switch (action)
746 case PREF_DISPLAY:
747 if (entry->setting_type == G_TYPE_BOOLEAN)
748 g_object_set(object, name, *(gboolean*)entry->setting, NULL);
749 else if (entry->setting_type == G_TYPE_INT)
750 g_object_set(object, name, *(gint*)entry->setting, NULL);
751 else if (entry->setting_type == G_TYPE_STRING)
752 g_object_set(object, name, *(gchararray*)entry->setting, NULL);
753 else if (entry->setting_type == G_TYPE_STRV)
754 g_object_set(object, name, *(gchararray**)entry->setting, NULL);
755 else
757 g_warning("Unhandled type %s for %s in %s()!", g_type_name(entry->setting_type),
758 entry->key_name, G_STRFUNC);
760 break;
761 case PREF_UPDATE:
762 if (entry->setting_type == G_TYPE_STRING)
763 g_free(*(gchararray*)entry->setting);
764 else if (entry->setting_type == G_TYPE_STRV)
765 g_strfreev(*(gchararray**)entry->setting);
767 g_object_get(object, name, entry->setting, NULL);
768 break;
773 static void pref_action(PrefAction action, StashGroup *group, GtkWidget *owner)
775 StashPref *entry;
776 guint i;
778 foreach_ptr_array(entry, i, group->entries)
780 GtkWidget *widget;
782 /* ignore settings with no widgets */
783 if (entry->widget_type == G_TYPE_NONE)
784 continue;
786 /* radio buttons have several widgets */
787 if (entry->widget_type == GTK_TYPE_RADIO_BUTTON)
789 handle_radio_buttons(owner, entry, action);
790 continue;
793 widget = get_widget(owner, entry->widget_id);
794 if (!widget)
796 g_warning("Unknown widget for %s::%s in %s()!", group->name, entry->key_name,
797 G_STRFUNC);
798 continue;
801 /* note: can't use switch for GTK_TYPE macros */
802 if (entry->widget_type == GTK_TYPE_TOGGLE_BUTTON)
803 handle_toggle_button(widget, entry->setting, action);
804 else if (entry->widget_type == GTK_TYPE_SPIN_BUTTON)
805 handle_spin_button(widget, entry, action);
806 else if (entry->widget_type == GTK_TYPE_COMBO_BOX)
807 handle_combo_box(widget, entry, action);
808 else if (entry->widget_type == TYPE_COMBO_BOX_ENTRY)
809 handle_combo_box_entry(widget, entry, action);
810 else if (entry->widget_type == GTK_TYPE_ENTRY)
811 handle_entry(widget, entry, action);
812 else if (entry->widget_type == G_TYPE_PARAM)
813 handle_widget_property(widget, entry, action);
814 else
815 g_warning("Unhandled type for %s::%s in %s()!", group->name, entry->key_name,
816 G_STRFUNC);
821 /** Applies Stash settings to widgets, usually called before displaying the widgets.
822 * The @a owner argument depends on which type you use for @ref StashWidgetID.
823 * @param group .
824 * @param owner If non-NULL, used to lookup widgets by name, otherwise
825 * widget pointers are assumed.
826 * @see stash_group_update(). */
827 GEANY_API_SYMBOL
828 void stash_group_display(StashGroup *group, GtkWidget *owner)
830 pref_action(PREF_DISPLAY, group, owner);
834 /** Applies widget values to Stash settings, usually called after displaying the widgets.
835 * The @a owner argument depends on which type you use for @ref StashWidgetID.
836 * @param group .
837 * @param owner If non-NULL, used to lookup widgets by name, otherwise
838 * widget pointers are assumed.
839 * @see stash_group_display(). */
840 GEANY_API_SYMBOL
841 void stash_group_update(StashGroup *group, GtkWidget *owner)
843 pref_action(PREF_UPDATE, group, owner);
847 static StashPref *
848 add_widget_pref(StashGroup *group, GType setting_type, gpointer setting,
849 const gchar *key_name, union Value default_value,
850 GType widget_type, StashWidgetID widget_id)
852 StashPref *entry =
853 add_pref(group, setting_type, setting, key_name, default_value);
855 entry->widget_type = widget_type;
856 entry->widget_id = widget_id;
857 return entry;
861 /** Adds a @c GtkToggleButton (or @c GtkCheckButton) widget pref.
862 * @param group .
863 * @param setting Address of setting variable.
864 * @param key_name Name for key in a @c GKeyFile.
865 * @param default_value Value to use if the key doesn't exist when loading.
866 * @param widget_id @c GtkWidget pointer or string to lookup widget later.
867 * @see stash_group_add_radio_buttons(). */
868 GEANY_API_SYMBOL
869 void stash_group_add_toggle_button(StashGroup *group, gboolean *setting,
870 const gchar *key_name, gboolean default_value, StashWidgetID widget_id)
872 add_widget_pref(group, G_TYPE_BOOLEAN, setting, key_name, (union Value) {.bool_val = default_value},
873 GTK_TYPE_TOGGLE_BUTTON, widget_id);
877 /** Adds a @c GtkRadioButton widget group pref.
878 * @param group .
879 * @param setting Address of setting variable.
880 * @param key_name Name for key in a @c GKeyFile.
881 * @param default_value Value to use if the key doesn't exist when loading.
882 * @param widget_id @c GtkWidget pointer or string to lookup widget later.
883 * @param enum_id Enum value for @a widget_id.
884 * @param ... pairs of @a widget_id, @a enum_id.
885 * Example (using widget lookup strings, but widget pointers can also work):
886 * @code
887 * enum {FOO, BAR};
888 * stash_group_add_radio_buttons(group, &which_one_setting, "which_one", BAR,
889 * "radio_foo", FOO, "radio_bar", BAR, NULL);
890 * @endcode */
891 GEANY_API_SYMBOL
892 void stash_group_add_radio_buttons(StashGroup *group, gint *setting,
893 const gchar *key_name, gint default_value,
894 StashWidgetID widget_id, gint enum_id, ...)
896 StashPref *entry =
897 add_widget_pref(group, G_TYPE_INT, setting, key_name, (union Value) {.int_val = default_value},
898 GTK_TYPE_RADIO_BUTTON, NULL);
899 va_list args;
900 gsize count = 1;
901 EnumWidget *item, *array;
903 /* count pairs of args */
904 va_start(args, enum_id);
905 while (1)
907 if (!va_arg(args, gpointer))
908 break;
909 va_arg(args, gint);
910 count++;
912 va_end(args);
914 array = g_new0(EnumWidget, count + 1);
915 entry->extra.radio_buttons = array;
917 va_start(args, enum_id);
918 foreach_c_array(item, array, count)
920 if (item == array)
922 /* first element */
923 item->widget_id = widget_id;
924 item->enum_id = enum_id;
926 else
928 item->widget_id = va_arg(args, gpointer);
929 item->enum_id = va_arg(args, gint);
932 va_end(args);
936 /** Adds a @c GtkSpinButton widget pref.
937 * @param group .
938 * @param setting Address of setting variable.
939 * @param key_name Name for key in a @c GKeyFile.
940 * @param default_value Value to use if the key doesn't exist when loading.
941 * @param widget_id @c GtkWidget pointer or string to lookup widget later. */
942 GEANY_API_SYMBOL
943 void stash_group_add_spin_button_integer(StashGroup *group, gint *setting,
944 const gchar *key_name, gint default_value, StashWidgetID widget_id)
946 add_widget_pref(group, G_TYPE_INT, setting, key_name,
947 (union Value) {.int_val = default_value}, GTK_TYPE_SPIN_BUTTON, widget_id);
951 /** Adds a @c GtkComboBox widget pref.
952 * @param group .
953 * @param setting Address of setting variable.
954 * @param key_name Name for key in a @c GKeyFile.
955 * @param default_value Value to use if the key doesn't exist when loading.
956 * @param widget_id @c GtkWidget pointer or string to lookup widget later.
957 * @see stash_group_add_combo_box_entry(). */
958 GEANY_API_SYMBOL
959 void stash_group_add_combo_box(StashGroup *group, gint *setting,
960 const gchar *key_name, gint default_value, StashWidgetID widget_id)
962 add_widget_pref(group, G_TYPE_INT, setting, key_name,
963 (union Value) {.int_val = default_value}, GTK_TYPE_COMBO_BOX, widget_id);
967 /** Adds a @c GtkComboBoxEntry widget pref.
968 * @param group .
969 * @param setting Address of setting variable.
970 * @param key_name Name for key in a @c GKeyFile.
971 * @param default_value Value to use if the key doesn't exist when loading.
972 * @param widget_id @c GtkWidget pointer or string to lookup widget later. */
973 /* We could maybe also have something like stash_group_add_combo_box_entry_with_menu()
974 * for the history list - or should that be stored as a separate setting? */
975 GEANY_API_SYMBOL
976 void stash_group_add_combo_box_entry(StashGroup *group, gchar **setting,
977 const gchar *key_name, const gchar *default_value, StashWidgetID widget_id)
979 add_widget_pref(group, G_TYPE_STRING, setting, key_name,
980 (union Value) {.str_val = (gchar *) default_value}, TYPE_COMBO_BOX_ENTRY, widget_id);
984 /** Adds a @c GtkEntry widget pref.
985 * @param group .
986 * @param setting Address of setting variable.
987 * @param key_name Name for key in a @c GKeyFile.
988 * @param default_value Value to use if the key doesn't exist when loading.
989 * @param widget_id @c GtkWidget pointer or string to lookup widget later. */
990 GEANY_API_SYMBOL
991 void stash_group_add_entry(StashGroup *group, gchar **setting,
992 const gchar *key_name, const gchar *default_value, StashWidgetID widget_id)
994 add_widget_pref(group, G_TYPE_STRING, setting, key_name,
995 (union Value) {.str_val = (gchar *) default_value}, GTK_TYPE_ENTRY, widget_id);
999 static GType object_get_property_type(GObject *object, const gchar *property_name)
1001 GObjectClass *klass = G_OBJECT_GET_CLASS(object);
1002 GParamSpec *ps;
1004 ps = g_object_class_find_property(klass, property_name);
1005 return ps->value_type;
1009 /** Adds a widget's read/write property to the stash group.
1010 * The property will be set when calling
1011 * stash_group_display(), and read when calling stash_group_update().
1012 * @param group .
1013 * @param setting Address of e.g. an integer if using an integer property.
1014 * @param key_name Name for key in a @c GKeyFile.
1015 * @param default_value Value to use if the key doesn't exist when loading.
1016 * Should be cast into a pointer e.g. with @c GINT_TO_POINTER().
1017 * @param widget_id @c GtkWidget pointer or string to lookup widget later.
1018 * @param property_name .
1019 * @param type can be @c 0 if passing a @c GtkWidget as the @a widget_id argument to look it up from the
1020 * @c GObject data.
1021 * @warning Currently only string GValue properties will be freed before setting; patch for
1022 * other types - see @c handle_widget_property(). */
1023 GEANY_API_SYMBOL
1024 void stash_group_add_widget_property(StashGroup *group, gpointer setting,
1025 const gchar *key_name, gpointer default_value, StashWidgetID widget_id,
1026 const gchar *property_name, GType type)
1028 StashPref *entry;
1030 if (!type)
1031 type = object_get_property_type(G_OBJECT(widget_id), property_name);
1033 entry = add_widget_pref(group, type, setting, key_name,
1034 (union Value) {.ptr_val = default_value}, G_TYPE_PARAM, widget_id);
1035 entry->extra.property_name = property_name;
1039 enum
1041 STASH_TREE_NAME,
1042 STASH_TREE_VALUE,
1043 STASH_TREE_COUNT
1047 struct StashTreeValue
1049 const gchar *group_name;
1050 StashPref *pref;
1051 struct
1053 gchararray tree_string;
1054 gint tree_int;
1055 } data;
1058 typedef struct StashTreeValue StashTreeValue;
1061 static void stash_tree_renderer_set_data(GtkCellLayout *cell_layout, GtkCellRenderer *cell,
1062 GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
1064 GType cell_type = GPOINTER_TO_SIZE(user_data);
1065 StashTreeValue *value;
1066 StashPref *pref;
1067 gboolean matches_type;
1069 gtk_tree_model_get(model, iter, STASH_TREE_VALUE, &value, -1);
1070 pref = value->pref;
1071 matches_type = pref->setting_type == cell_type;
1072 g_object_set(cell, "visible", matches_type, "sensitive", matches_type,
1073 cell_type == G_TYPE_BOOLEAN ? "activatable" : "editable", matches_type, NULL);
1075 if (matches_type)
1077 switch (pref->setting_type)
1079 case G_TYPE_BOOLEAN:
1080 g_object_set(cell, "active", value->data.tree_int, NULL);
1081 break;
1082 case G_TYPE_INT:
1084 gchar *text = g_strdup_printf("%d", value->data.tree_int);
1085 g_object_set(cell, "text", text, NULL);
1086 g_free(text);
1087 break;
1089 case G_TYPE_STRING:
1090 g_object_set(cell, "text", value->data.tree_string, NULL);
1091 break;
1097 static void stash_tree_renderer_edited(gchar *path_str, gchar *new_text, GtkTreeModel *model)
1099 GtkTreePath *path;
1100 GtkTreeIter iter;
1101 StashTreeValue *value;
1102 StashPref *pref;
1104 path = gtk_tree_path_new_from_string(path_str);
1105 gtk_tree_model_get_iter(model, &iter, path);
1106 gtk_tree_model_get(model, &iter, STASH_TREE_VALUE, &value, -1);
1107 pref = value->pref;
1109 switch (pref->setting_type)
1111 case G_TYPE_BOOLEAN:
1112 value->data.tree_int = !value->data.tree_int;
1113 break;
1114 case G_TYPE_INT:
1115 value->data.tree_int = atoi(new_text);
1116 break;
1117 case G_TYPE_STRING:
1118 SETPTR(value->data.tree_string, g_strdup(new_text));
1119 break;
1122 gtk_tree_model_row_changed(model, path, &iter);
1123 gtk_tree_path_free(path);
1127 static void stash_tree_boolean_toggled(GtkCellRendererToggle *cell, gchar *path_str,
1128 GtkTreeModel *model)
1130 stash_tree_renderer_edited(path_str, NULL, model);
1134 static void stash_tree_string_edited(GtkCellRenderer *cell, gchar *path_str, gchar *new_text,
1135 GtkTreeModel *model)
1137 stash_tree_renderer_edited(path_str, new_text, model);
1141 static gboolean stash_tree_discard_value(GtkTreeModel *model, GtkTreePath *path,
1142 GtkTreeIter *iter, gpointer user_data)
1144 StashTreeValue *value;
1146 gtk_tree_model_get(model, iter, STASH_TREE_VALUE, &value, -1);
1147 /* don't access value->pref as it might already have been freed */
1148 g_free(value->data.tree_string);
1149 g_free(value);
1151 return FALSE;
1155 static void stash_tree_destroy_cb(GtkWidget *widget, gpointer user_data)
1157 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
1158 gtk_tree_model_foreach(model, stash_tree_discard_value, NULL);
1162 static void stash_tree_append_pref(StashGroup *group, StashPref *entry, GtkListStore *store,
1163 PrefAction action)
1165 GtkTreeIter iter;
1166 StashTreeValue *value;
1167 gchar *text = NULL;
1169 value = g_new0(StashTreeValue, 1);
1171 value->group_name = group->name;
1172 value->pref = entry;
1174 gtk_list_store_append(store, &iter);
1175 text = g_strconcat(group->prefix ? group->prefix : group->name,
1176 ".", entry->key_name, NULL);
1177 gtk_list_store_set(store, &iter, STASH_TREE_NAME, text,
1178 STASH_TREE_VALUE, value, -1);
1179 g_free(text);
1183 static void stash_tree_append_prefs(GPtrArray *group_array,
1184 GtkListStore *store, PrefAction action)
1186 StashGroup *group;
1187 guint i, j;
1188 StashPref *entry;
1190 foreach_ptr_array(group, i, group_array)
1192 if (group->various)
1194 foreach_ptr_array(entry, j, group->entries)
1195 stash_tree_append_pref(group, entry, store, action);
1201 /* Setups a simple editor for stash preferences based on the widget arguments.
1202 * group_array - Array of groups which's settings will be edited.
1203 * tree - GtkTreeView in which to edit the preferences. Must be empty. */
1204 void stash_tree_setup(GPtrArray *group_array, GtkTreeView *tree)
1206 GtkListStore *store;
1207 GtkTreeModel *model;
1208 GtkCellRenderer *cell;
1209 GtkTreeViewColumn *column;
1210 GtkAdjustment *adjustment;
1212 store = gtk_list_store_new(STASH_TREE_COUNT, G_TYPE_STRING, G_TYPE_POINTER);
1213 stash_tree_append_prefs(group_array, store, PREF_DISPLAY);
1214 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store), STASH_TREE_NAME,
1215 GTK_SORT_ASCENDING);
1217 model = GTK_TREE_MODEL(store);
1218 gtk_tree_view_set_model(tree, model);
1219 g_object_unref(G_OBJECT(store));
1220 g_signal_connect(tree, "destroy", G_CALLBACK(stash_tree_destroy_cb), NULL);
1222 cell = gtk_cell_renderer_text_new();
1223 column = gtk_tree_view_column_new_with_attributes(_("Name"), cell, "text",
1224 STASH_TREE_NAME, NULL);
1225 gtk_tree_view_column_set_sort_column_id(column, STASH_TREE_NAME);
1226 gtk_tree_view_column_set_sort_indicator(column, TRUE);
1227 gtk_tree_view_append_column(tree, column);
1229 column = gtk_tree_view_column_new();
1230 gtk_tree_view_column_set_title(column, _("Value"));
1231 gtk_tree_view_append_column(tree, column);
1232 /* boolean renderer */
1233 cell = gtk_cell_renderer_toggle_new();
1234 g_signal_connect(cell, "toggled", G_CALLBACK(stash_tree_boolean_toggled), model);
1235 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(column), cell, FALSE);
1236 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(column), cell,
1237 stash_tree_renderer_set_data, GSIZE_TO_POINTER(G_TYPE_BOOLEAN), NULL);
1238 /* string renderer */
1239 cell = gtk_cell_renderer_text_new();
1240 g_object_set(cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1241 g_signal_connect(cell, "edited", G_CALLBACK(stash_tree_string_edited), model);
1242 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(column), cell, TRUE);
1243 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(column), cell,
1244 stash_tree_renderer_set_data, GSIZE_TO_POINTER(G_TYPE_STRING), NULL);
1245 /* integer renderer */
1246 cell = gtk_cell_renderer_spin_new();
1247 adjustment = GTK_ADJUSTMENT(gtk_adjustment_new(0, G_MININT, G_MAXINT, 1, 10, 0));
1248 g_object_set(cell, "adjustment", adjustment, NULL);
1249 g_signal_connect(cell, "edited", G_CALLBACK(stash_tree_string_edited), model);
1250 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(column), cell, FALSE);
1251 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(column), cell,
1252 stash_tree_renderer_set_data, GSIZE_TO_POINTER(G_TYPE_INT), NULL);
1256 static void stash_tree_display_pref(StashTreeValue *value, StashPref *entry)
1258 switch (entry->setting_type)
1260 case G_TYPE_BOOLEAN:
1261 value->data.tree_int = *(gboolean *) entry->setting;
1262 break;
1263 case G_TYPE_INT:
1264 value->data.tree_int = *(gint *) entry->setting;
1265 break;
1266 case G_TYPE_STRING:
1267 SETPTR(value->data.tree_string, g_strdup(*(gchararray *) entry->setting));
1268 break;
1269 default:
1270 g_warning("Unhandled type for %s::%s in %s()!", value->group_name,
1271 entry->key_name, G_STRFUNC);
1276 static void stash_tree_update_pref(StashTreeValue *value, StashPref *entry)
1278 switch (entry->setting_type)
1280 case G_TYPE_BOOLEAN:
1281 *(gboolean *) entry->setting = value->data.tree_int;
1282 break;
1283 case G_TYPE_INT:
1284 *(gint *) entry->setting = value->data.tree_int;
1285 break;
1286 case G_TYPE_STRING:
1288 gchararray *text = entry->setting;
1289 SETPTR(*text, g_strdup(value->data.tree_string));
1290 break;
1292 default:
1293 g_warning("Unhandled type for %s::%s in %s()!", value->group_name,
1294 entry->key_name, G_STRFUNC);
1299 static void stash_tree_action(GtkTreeModel *model, PrefAction action)
1301 GtkTreeIter iter;
1302 gboolean valid = gtk_tree_model_get_iter_first(model, &iter);
1303 StashTreeValue *value;
1305 while (valid)
1307 gtk_tree_model_get(model, &iter, STASH_TREE_VALUE, &value, -1);
1309 switch (action)
1311 case PREF_DISPLAY:
1312 stash_tree_display_pref(value, value->pref);
1313 break;
1314 case PREF_UPDATE:
1315 stash_tree_update_pref(value, value->pref);
1316 break;
1318 valid = gtk_tree_model_iter_next(model, &iter);
1323 void stash_tree_display(GtkTreeView *tree)
1325 stash_tree_action(gtk_tree_view_get_model(tree), PREF_DISPLAY);
1329 void stash_tree_update(GtkTreeView *tree)
1331 stash_tree_action(gtk_tree_view_get_model(tree), PREF_UPDATE);