plugins: plugin loader redesign
[geany-mirror.git] / src / stash.c
blobd4a5b698d8750bd9a7f1e9cc7243a55fce62a9f5
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.
79 #include "stash.h"
81 #include "support.h" /* only for _("text") */
82 #include "utils.h" /* only for foreach_*, utils_get_setting_*(). Stash should not depend on Geany. */
84 #include <stdlib.h> /* only for atoi() */
87 /* GTK3 removed ComboBoxEntry, but we need a value to differentiate combo box with and
88 * without entries, and it must not collide with other GTypes */
89 #ifdef GTK_TYPE_COMBO_BOX_ENTRY
90 # define TYPE_COMBO_BOX_ENTRY GTK_TYPE_COMBO_BOX_ENTRY
91 #else /* !GTK_TYPE_COMBO_BOX_ENTRY */
92 # define TYPE_COMBO_BOX_ENTRY get_combo_box_entry_type()
93 static GType get_combo_box_entry_type(void)
95 static volatile 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;
104 #endif /* !GTK_TYPE_COMBO_BOX_ENTRY */
107 struct StashPref
109 GType setting_type; /* e.g. G_TYPE_INT */
110 gpointer setting; /* Address of a variable */
111 const gchar *key_name;
112 gpointer default_value; /* Default value, e.g. (gpointer)1 */
113 GType widget_type; /* e.g. GTK_TYPE_TOGGLE_BUTTON */
114 StashWidgetID widget_id; /* (GtkWidget*) or (gchar*) */
115 union
117 struct EnumWidget *radio_buttons;
118 const gchar *property_name;
119 } extra; /* extra fields depending on widget_type */
122 typedef struct StashPref StashPref;
124 struct StashGroup
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 gboolean use_defaults; /* use default values if there's no keyfile entry */
132 typedef struct EnumWidget
134 StashWidgetID widget_id;
135 gint enum_id;
137 EnumWidget;
140 typedef enum SettingAction
142 SETTING_READ,
143 SETTING_WRITE
145 SettingAction;
147 typedef enum PrefAction
149 PREF_DISPLAY,
150 PREF_UPDATE
152 PrefAction;
155 static void handle_boolean_setting(StashGroup *group, StashPref *se,
156 GKeyFile *config, SettingAction action)
158 gboolean *setting = se->setting;
160 switch (action)
162 case SETTING_READ:
163 *setting = utils_get_setting_boolean(config, group->name, se->key_name,
164 GPOINTER_TO_INT(se->default_value));
165 break;
166 case SETTING_WRITE:
167 g_key_file_set_boolean(config, group->name, se->key_name, *setting);
168 break;
173 static void handle_integer_setting(StashGroup *group, StashPref *se,
174 GKeyFile *config, SettingAction action)
176 gint *setting = se->setting;
178 switch (action)
180 case SETTING_READ:
181 *setting = utils_get_setting_integer(config, group->name, se->key_name,
182 GPOINTER_TO_INT(se->default_value));
183 break;
184 case SETTING_WRITE:
185 g_key_file_set_integer(config, group->name, se->key_name, *setting);
186 break;
191 static void handle_string_setting(StashGroup *group, StashPref *se,
192 GKeyFile *config, SettingAction action)
194 gchararray *setting = se->setting;
196 switch (action)
198 case SETTING_READ:
199 g_free(*setting);
200 *setting = utils_get_setting_string(config, group->name, se->key_name,
201 se->default_value);
202 break;
203 case SETTING_WRITE:
204 g_key_file_set_string(config, group->name, se->key_name,
205 *setting ? *setting : "");
206 break;
211 static void handle_strv_setting(StashGroup *group, StashPref *se,
212 GKeyFile *config, SettingAction action)
214 gchararray **setting = se->setting;
216 switch (action)
218 case SETTING_READ:
219 g_strfreev(*setting);
220 *setting = g_key_file_get_string_list(config, group->name, se->key_name,
221 NULL, NULL);
222 if (*setting == NULL)
223 *setting = g_strdupv(se->default_value);
224 break;
226 case SETTING_WRITE:
228 /* don't try to save a NULL string vector */
229 const gchar *dummy[] = { "", NULL };
230 const gchar **strv = *setting ? (const gchar **)*setting : dummy;
232 g_key_file_set_string_list(config, group->name, se->key_name,
233 strv, g_strv_length((gchar **)strv));
234 break;
240 static void keyfile_action(SettingAction action, StashGroup *group, GKeyFile *keyfile)
242 StashPref *entry;
243 guint i;
245 foreach_ptr_array(entry, i, group->entries)
247 /* don't override settings with default values */
248 if (!group->use_defaults && action == SETTING_READ &&
249 !g_key_file_has_key(keyfile, group->name, entry->key_name, NULL))
250 continue;
252 switch (entry->setting_type)
254 case G_TYPE_BOOLEAN:
255 handle_boolean_setting(group, entry, keyfile, action); break;
256 case G_TYPE_INT:
257 handle_integer_setting(group, entry, keyfile, action); break;
258 case G_TYPE_STRING:
259 handle_string_setting(group, entry, keyfile, action); break;
260 default:
261 /* Note: G_TYPE_STRV is not a constant, can't use case label */
262 if (entry->setting_type == G_TYPE_STRV)
263 handle_strv_setting(group, entry, keyfile, action);
264 else
265 g_warning("Unhandled type for %s::%s in %s()!", group->name, entry->key_name,
266 G_STRFUNC);
272 /** Reads key values from @a keyfile into the group settings.
273 * @note You should still call this even if the keyfile couldn't be loaded from disk
274 * so that all Stash settings are initialized to defaults.
275 * @param group .
276 * @param keyfile Usually loaded from a configuration file first. */
277 GEANY_API_SYMBOL
278 void stash_group_load_from_key_file(StashGroup *group, GKeyFile *keyfile)
280 keyfile_action(SETTING_READ, group, keyfile);
284 /** Writes group settings into key values in @a keyfile.
285 * @a keyfile is usually written to a configuration file afterwards.
286 * @param group .
287 * @param keyfile . */
288 GEANY_API_SYMBOL
289 void stash_group_save_to_key_file(StashGroup *group, GKeyFile *keyfile)
291 keyfile_action(SETTING_WRITE, group, keyfile);
295 /** Reads group settings from a configuration file using @c GKeyFile.
296 * @note Stash settings will be initialized to defaults if the keyfile
297 * couldn't be loaded from disk.
298 * @param group .
299 * @param filename Filename of the file to read, in locale encoding.
300 * @return @c TRUE if a key file could be loaded.
301 * @see stash_group_load_from_key_file().
303 GEANY_API_SYMBOL
304 gboolean stash_group_load_from_file(StashGroup *group, const gchar *filename)
306 GKeyFile *keyfile;
307 gboolean ret;
309 keyfile = g_key_file_new();
310 ret = g_key_file_load_from_file(keyfile, filename, 0, NULL);
311 /* even on failure we load settings to apply defaults */
312 stash_group_load_from_key_file(group, keyfile);
314 g_key_file_free(keyfile);
315 return ret;
319 /** Writes group settings to a configuration file using @c GKeyFile.
321 * @param group .
322 * @param filename Filename of the file to write, in locale encoding.
323 * @param flags Keyfile options - @c G_KEY_FILE_NONE is the most efficient.
324 * @return 0 if the file was successfully written, otherwise the @c errno of the
325 * failed operation is returned.
326 * @see stash_group_save_to_key_file().
328 GEANY_API_SYMBOL
329 gint stash_group_save_to_file(StashGroup *group, const gchar *filename,
330 GKeyFileFlags flags)
332 GKeyFile *keyfile;
333 gchar *data;
334 gint ret;
336 keyfile = g_key_file_new();
337 /* if we need to keep comments or translations, try to load first */
338 if (flags)
339 g_key_file_load_from_file(keyfile, filename, flags, NULL);
341 stash_group_save_to_key_file(group, keyfile);
342 data = g_key_file_to_data(keyfile, NULL, NULL);
343 ret = utils_write_file(filename, data);
344 g_free(data);
345 g_key_file_free(keyfile);
346 return ret;
350 /** Creates a new group.
351 * @param name Name used for @c GKeyFile group.
352 * @return Group. */
353 GEANY_API_SYMBOL
354 StashGroup *stash_group_new(const gchar *name)
356 StashGroup *group = g_new0(StashGroup, 1);
358 group->name = name;
359 group->entries = g_ptr_array_new();
360 group->use_defaults = TRUE;
361 return group;
365 /** Frees the memory allocated for setting values in a group.
366 * Useful e.g. to avoid freeing strings individually.
367 * @note This is *not* called by stash_group_free().
368 * @param group . */
369 GEANY_API_SYMBOL
370 void stash_group_free_settings(StashGroup *group)
372 StashPref *entry;
373 guint i;
375 foreach_ptr_array(entry, i, group->entries)
377 if (entry->setting_type == G_TYPE_STRING)
378 g_free(*(gchararray *) entry->setting);
379 else if (entry->setting_type == G_TYPE_STRV)
380 g_strfreev(*(gchararray **) entry->setting);
381 else
382 continue;
384 *(gpointer**) entry->setting = NULL;
389 /** Frees a group.
390 * @param group . */
391 GEANY_API_SYMBOL
392 void stash_group_free(StashGroup *group)
394 StashPref *entry;
395 guint i;
397 foreach_ptr_array(entry, i, group->entries)
399 if (entry->widget_type == GTK_TYPE_RADIO_BUTTON)
401 g_free(entry->extra.radio_buttons);
403 g_slice_free(StashPref, entry);
405 g_ptr_array_free(group->entries, TRUE);
406 g_free(group);
410 /* Used for selecting groups passed to stash_tree_setup().
411 * @c FALSE by default. */
412 void stash_group_set_various(StashGroup *group, gboolean various)
414 group->various = various;
418 /* When @c FALSE, Stash doesn't change the setting if there is no keyfile entry, so it
419 * remains whatever it was initialized/set to by user code.
420 * @c TRUE by default. */
421 void stash_group_set_use_defaults(StashGroup *group, gboolean use_defaults)
423 group->use_defaults = use_defaults;
427 static StashPref *
428 add_pref(StashGroup *group, GType type, gpointer setting,
429 const gchar *key_name, gpointer default_value)
431 StashPref init = {type, setting, key_name, default_value, G_TYPE_NONE, NULL, {NULL}};
432 StashPref *entry = g_slice_new(StashPref);
434 *entry = init;
436 /* init any pointer settings to NULL so they can be freed later */
437 if (type == G_TYPE_STRING ||
438 type == G_TYPE_STRV)
439 if (group->use_defaults)
440 *(gpointer**)setting = NULL;
442 g_ptr_array_add(group->entries, entry);
443 return entry;
447 /** Adds boolean setting.
448 * @param group .
449 * @param setting Address of setting variable.
450 * @param key_name Name for key in a @c GKeyFile.
451 * @param default_value Value to use if the key doesn't exist when loading. */
452 GEANY_API_SYMBOL
453 void stash_group_add_boolean(StashGroup *group, gboolean *setting,
454 const gchar *key_name, gboolean default_value)
456 add_pref(group, G_TYPE_BOOLEAN, setting, key_name, GINT_TO_POINTER(default_value));
460 /** Adds integer setting.
461 * @param group .
462 * @param setting Address of setting variable.
463 * @param key_name Name for key in a @c GKeyFile.
464 * @param default_value Value to use if the key doesn't exist when loading. */
465 GEANY_API_SYMBOL
466 void stash_group_add_integer(StashGroup *group, gint *setting,
467 const gchar *key_name, gint default_value)
469 add_pref(group, G_TYPE_INT, setting, key_name, GINT_TO_POINTER(default_value));
473 /** Adds string setting.
474 * The contents of @a setting will be initialized to @c NULL.
475 * @param group .
476 * @param setting Address of setting variable.
477 * @param key_name Name for key in a @c GKeyFile.
478 * @param default_value String to copy if the key doesn't exist when loading, or @c NULL. */
479 GEANY_API_SYMBOL
480 void stash_group_add_string(StashGroup *group, gchar **setting,
481 const gchar *key_name, const gchar *default_value)
483 add_pref(group, G_TYPE_STRING, setting, key_name, (gpointer)default_value);
487 /** Adds string vector setting (array of strings).
488 * The contents of @a setting will be initialized to @c NULL.
489 * @param group .
490 * @param setting Address of setting variable.
491 * @param key_name Name for key in a @c GKeyFile.
492 * @param default_value Vector to copy if the key doesn't exist when loading. Usually @c NULL. */
493 GEANY_API_SYMBOL
494 void stash_group_add_string_vector(StashGroup *group, gchar ***setting,
495 const gchar *key_name, const gchar **default_value)
497 add_pref(group, G_TYPE_STRV, setting, key_name, (gpointer)default_value);
501 /* *** GTK-related functions *** */
503 static void handle_toggle_button(GtkWidget *widget, gboolean *setting,
504 PrefAction action)
506 switch (action)
508 case PREF_DISPLAY:
509 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), *setting);
510 break;
511 case PREF_UPDATE:
512 *setting = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
513 break;
518 static void handle_spin_button(GtkWidget *widget, StashPref *entry,
519 PrefAction action)
521 gint *setting = entry->setting;
523 g_assert(entry->setting_type == G_TYPE_INT); /* only int spin prefs */
525 switch (action)
527 case PREF_DISPLAY:
528 gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget), *setting);
529 break;
530 case PREF_UPDATE:
531 /* if the widget is focussed, the value might not be updated */
532 gtk_spin_button_update(GTK_SPIN_BUTTON(widget));
533 *setting = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(widget));
534 break;
539 static void handle_combo_box(GtkWidget *widget, StashPref *entry,
540 PrefAction action)
542 gint *setting = entry->setting;
544 switch (action)
546 case PREF_DISPLAY:
547 gtk_combo_box_set_active(GTK_COMBO_BOX(widget), *setting);
548 break;
549 case PREF_UPDATE:
550 *setting = gtk_combo_box_get_active(GTK_COMBO_BOX(widget));
551 break;
556 static void handle_entry(GtkWidget *widget, StashPref *entry,
557 PrefAction action)
559 gchararray *setting = entry->setting;
561 switch (action)
563 case PREF_DISPLAY:
564 gtk_entry_set_text(GTK_ENTRY(widget), *setting);
565 break;
566 case PREF_UPDATE:
567 g_free(*setting);
568 *setting = g_strdup(gtk_entry_get_text(GTK_ENTRY(widget)));
569 break;
574 static void handle_combo_box_entry(GtkWidget *widget, StashPref *entry,
575 PrefAction action)
577 widget = gtk_bin_get_child(GTK_BIN(widget));
578 handle_entry(widget, entry, action);
582 /* taken from Glade 2.x generated support.c */
583 static GtkWidget*
584 lookup_widget(GtkWidget *widget, const gchar *widget_name)
586 GtkWidget *parent, *found_widget;
588 g_return_val_if_fail(widget != NULL, NULL);
589 g_return_val_if_fail(widget_name != NULL, NULL);
591 for (;;)
593 if (GTK_IS_MENU(widget))
594 parent = gtk_menu_get_attach_widget(GTK_MENU(widget));
595 else
596 parent = gtk_widget_get_parent(widget);
597 if (parent == NULL)
598 parent = (GtkWidget*) g_object_get_data(G_OBJECT(widget), "GladeParentKey");
599 if (parent == NULL)
600 break;
601 widget = parent;
604 found_widget = (GtkWidget*) g_object_get_data(G_OBJECT(widget), widget_name);
605 if (G_UNLIKELY(found_widget == NULL))
606 g_warning("Widget not found: %s", widget_name);
607 return found_widget;
611 static GtkWidget *
612 get_widget(GtkWidget *owner, StashWidgetID widget_id)
614 GtkWidget *widget;
616 if (owner)
617 widget = lookup_widget(owner, (const gchar *)widget_id);
618 else
619 widget = (GtkWidget *)widget_id;
621 if (!GTK_IS_WIDGET(widget))
623 g_warning("Unknown widget in %s()!", G_STRFUNC);
624 return NULL;
626 return widget;
630 static void handle_radio_button(GtkWidget *widget, gint enum_id, gboolean *setting,
631 PrefAction action)
633 switch (action)
635 case PREF_DISPLAY:
636 if (*setting == enum_id)
637 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), TRUE);
638 break;
639 case PREF_UPDATE:
640 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
641 *setting = enum_id;
642 break;
647 static void handle_radio_buttons(GtkWidget *owner, StashPref *entry,
648 PrefAction action)
650 EnumWidget *field = entry->extra.radio_buttons;
651 gsize count = 0;
652 GtkWidget *widget = NULL;
654 while (1)
656 widget = get_widget(owner, field->widget_id);
658 if (!widget)
659 continue;
661 count++;
662 handle_radio_button(widget, field->enum_id, entry->setting, action);
663 field++;
664 if (!field->widget_id)
665 break;
667 if (g_slist_length(gtk_radio_button_get_group(GTK_RADIO_BUTTON(widget))) != count)
668 g_warning("Missing/invalid radio button widget IDs found!");
672 static void handle_widget_property(GtkWidget *widget, StashPref *entry,
673 PrefAction action)
675 GObject *object = G_OBJECT(widget);
676 const gchar *name = entry->extra.property_name;
678 switch (action)
680 case PREF_DISPLAY:
681 if (entry->setting_type == G_TYPE_BOOLEAN)
682 g_object_set(object, name, *(gboolean*)entry->setting, NULL);
683 else if (entry->setting_type == G_TYPE_INT)
684 g_object_set(object, name, *(gint*)entry->setting, NULL);
685 else if (entry->setting_type == G_TYPE_STRING)
686 g_object_set(object, name, *(gchararray*)entry->setting, NULL);
687 else if (entry->setting_type == G_TYPE_STRV)
688 g_object_set(object, name, *(gchararray**)entry->setting, NULL);
689 else
691 g_warning("Unhandled type %s for %s in %s()!", g_type_name(entry->setting_type),
692 entry->key_name, G_STRFUNC);
694 break;
695 case PREF_UPDATE:
696 if (entry->setting_type == G_TYPE_STRING)
697 g_free(*(gchararray*)entry->setting);
698 else if (entry->setting_type == G_TYPE_STRV)
699 g_strfreev(*(gchararray**)entry->setting);
701 g_object_get(object, name, entry->setting, NULL);
702 break;
707 static void pref_action(PrefAction action, StashGroup *group, GtkWidget *owner)
709 StashPref *entry;
710 guint i;
712 foreach_ptr_array(entry, i, group->entries)
714 GtkWidget *widget;
716 /* ignore settings with no widgets */
717 if (entry->widget_type == G_TYPE_NONE)
718 continue;
720 /* radio buttons have several widgets */
721 if (entry->widget_type == GTK_TYPE_RADIO_BUTTON)
723 handle_radio_buttons(owner, entry, action);
724 continue;
727 widget = get_widget(owner, entry->widget_id);
728 if (!widget)
730 g_warning("Unknown widget for %s::%s in %s()!", group->name, entry->key_name,
731 G_STRFUNC);
732 continue;
735 /* note: can't use switch for GTK_TYPE macros */
736 if (entry->widget_type == GTK_TYPE_TOGGLE_BUTTON)
737 handle_toggle_button(widget, entry->setting, action);
738 else if (entry->widget_type == GTK_TYPE_SPIN_BUTTON)
739 handle_spin_button(widget, entry, action);
740 else if (entry->widget_type == GTK_TYPE_COMBO_BOX)
741 handle_combo_box(widget, entry, action);
742 else if (entry->widget_type == TYPE_COMBO_BOX_ENTRY)
743 handle_combo_box_entry(widget, entry, action);
744 else if (entry->widget_type == GTK_TYPE_ENTRY)
745 handle_entry(widget, entry, action);
746 else if (entry->widget_type == G_TYPE_PARAM)
747 handle_widget_property(widget, entry, action);
748 else
749 g_warning("Unhandled type for %s::%s in %s()!", group->name, entry->key_name,
750 G_STRFUNC);
755 /** Applies Stash settings to widgets, usually called before displaying the widgets.
756 * The @a owner argument depends on which type you use for @ref StashWidgetID.
757 * @param group .
758 * @param owner If non-NULL, used to lookup widgets by name, otherwise
759 * widget pointers are assumed.
760 * @see stash_group_update(). */
761 GEANY_API_SYMBOL
762 void stash_group_display(StashGroup *group, GtkWidget *owner)
764 pref_action(PREF_DISPLAY, group, owner);
768 /** Applies widget values to Stash settings, usually called after displaying the widgets.
769 * The @a owner argument depends on which type you use for @ref StashWidgetID.
770 * @param group .
771 * @param owner If non-NULL, used to lookup widgets by name, otherwise
772 * widget pointers are assumed.
773 * @see stash_group_display(). */
774 GEANY_API_SYMBOL
775 void stash_group_update(StashGroup *group, GtkWidget *owner)
777 pref_action(PREF_UPDATE, group, owner);
781 static StashPref *
782 add_widget_pref(StashGroup *group, GType setting_type, gpointer setting,
783 const gchar *key_name, gpointer default_value,
784 GType widget_type, StashWidgetID widget_id)
786 StashPref *entry =
787 add_pref(group, setting_type, setting, key_name, default_value);
789 entry->widget_type = widget_type;
790 entry->widget_id = widget_id;
791 return entry;
795 /** Adds a @c GtkToggleButton (or @c GtkCheckButton) widget pref.
796 * @param group .
797 * @param setting Address of setting variable.
798 * @param key_name Name for key in a @c GKeyFile.
799 * @param default_value Value to use if the key doesn't exist when loading.
800 * @param widget_id @c GtkWidget pointer or string to lookup widget later.
801 * @see stash_group_add_radio_buttons(). */
802 GEANY_API_SYMBOL
803 void stash_group_add_toggle_button(StashGroup *group, gboolean *setting,
804 const gchar *key_name, gboolean default_value, StashWidgetID widget_id)
806 add_widget_pref(group, G_TYPE_BOOLEAN, setting, key_name, GINT_TO_POINTER(default_value),
807 GTK_TYPE_TOGGLE_BUTTON, widget_id);
811 /** Adds a @c GtkRadioButton widget group pref.
812 * @param group .
813 * @param setting Address of setting variable.
814 * @param key_name Name for key in a @c GKeyFile.
815 * @param default_value Value to use if the key doesn't exist when loading.
816 * @param widget_id @c GtkWidget pointer or string to lookup widget later.
817 * @param enum_id Enum value for @a widget_id.
818 * @param ... pairs of @a widget_id, @a enum_id.
819 * Example (using widget lookup strings, but widget pointers can also work):
820 * @code
821 * enum {FOO, BAR};
822 * stash_group_add_radio_buttons(group, &which_one_setting, "which_one", BAR,
823 * "radio_foo", FOO, "radio_bar", BAR, NULL);
824 * @endcode */
825 GEANY_API_SYMBOL
826 void stash_group_add_radio_buttons(StashGroup *group, gint *setting,
827 const gchar *key_name, gint default_value,
828 StashWidgetID widget_id, gint enum_id, ...)
830 StashPref *entry =
831 add_widget_pref(group, G_TYPE_INT, setting, key_name, GINT_TO_POINTER(default_value),
832 GTK_TYPE_RADIO_BUTTON, NULL);
833 va_list args;
834 gsize count = 1;
835 EnumWidget *item, *array;
837 /* count pairs of args */
838 va_start(args, enum_id);
839 while (1)
841 if (!va_arg(args, gpointer))
842 break;
843 va_arg(args, gint);
844 count++;
846 va_end(args);
848 array = g_new0(EnumWidget, count + 1);
849 entry->extra.radio_buttons = array;
851 va_start(args, enum_id);
852 foreach_c_array(item, array, count)
854 if (item == array)
856 /* first element */
857 item->widget_id = widget_id;
858 item->enum_id = enum_id;
860 else
862 item->widget_id = va_arg(args, gpointer);
863 item->enum_id = va_arg(args, gint);
866 va_end(args);
870 /** Adds a @c GtkSpinButton widget pref.
871 * @param group .
872 * @param setting Address of setting variable.
873 * @param key_name Name for key in a @c GKeyFile.
874 * @param default_value Value to use if the key doesn't exist when loading.
875 * @param widget_id @c GtkWidget pointer or string to lookup widget later. */
876 GEANY_API_SYMBOL
877 void stash_group_add_spin_button_integer(StashGroup *group, gint *setting,
878 const gchar *key_name, gint default_value, StashWidgetID widget_id)
880 add_widget_pref(group, G_TYPE_INT, setting, key_name, GINT_TO_POINTER(default_value),
881 GTK_TYPE_SPIN_BUTTON, widget_id);
885 /** Adds a @c GtkComboBox widget pref.
886 * @param group .
887 * @param setting Address of setting variable.
888 * @param key_name Name for key in a @c GKeyFile.
889 * @param default_value Value to use if the key doesn't exist when loading.
890 * @param widget_id @c GtkWidget pointer or string to lookup widget later.
891 * @see stash_group_add_combo_box_entry(). */
892 GEANY_API_SYMBOL
893 void stash_group_add_combo_box(StashGroup *group, gint *setting,
894 const gchar *key_name, gint default_value, StashWidgetID widget_id)
896 add_widget_pref(group, G_TYPE_INT, setting, key_name, GINT_TO_POINTER(default_value),
897 GTK_TYPE_COMBO_BOX, widget_id);
901 /** Adds a @c GtkComboBoxEntry widget pref.
902 * @param group .
903 * @param setting Address of setting variable.
904 * @param key_name Name for key in a @c GKeyFile.
905 * @param default_value Value to use if the key doesn't exist when loading.
906 * @param widget_id @c GtkWidget pointer or string to lookup widget later. */
907 /* We could maybe also have something like stash_group_add_combo_box_entry_with_menu()
908 * for the history list - or should that be stored as a separate setting? */
909 GEANY_API_SYMBOL
910 void stash_group_add_combo_box_entry(StashGroup *group, gchar **setting,
911 const gchar *key_name, const gchar *default_value, StashWidgetID widget_id)
913 add_widget_pref(group, G_TYPE_STRING, setting, key_name, (gpointer)default_value,
914 TYPE_COMBO_BOX_ENTRY, widget_id);
918 /** Adds a @c GtkEntry widget pref.
919 * @param group .
920 * @param setting Address of setting variable.
921 * @param key_name Name for key in a @c GKeyFile.
922 * @param default_value Value to use if the key doesn't exist when loading.
923 * @param widget_id @c GtkWidget pointer or string to lookup widget later. */
924 GEANY_API_SYMBOL
925 void stash_group_add_entry(StashGroup *group, gchar **setting,
926 const gchar *key_name, const gchar *default_value, StashWidgetID widget_id)
928 add_widget_pref(group, G_TYPE_STRING, setting, key_name, (gpointer)default_value,
929 GTK_TYPE_ENTRY, widget_id);
933 static GType object_get_property_type(GObject *object, const gchar *property_name)
935 GObjectClass *klass = G_OBJECT_GET_CLASS(object);
936 GParamSpec *ps;
938 ps = g_object_class_find_property(klass, property_name);
939 return ps->value_type;
943 /** Adds a widget's read/write property to the stash group.
944 * The property will be set when calling
945 * stash_group_display(), and read when calling stash_group_update().
946 * @param group .
947 * @param setting Address of e.g. an integer if using an integer property.
948 * @param key_name Name for key in a @c GKeyFile.
949 * @param default_value Value to use if the key doesn't exist when loading.
950 * Should be cast into a pointer e.g. with @c GINT_TO_POINTER().
951 * @param widget_id @c GtkWidget pointer or string to lookup widget later.
952 * @param property_name .
953 * @param type can be @c 0 if passing a @c GtkWidget as the @a widget_id argument to look it up from the
954 * @c GObject data.
955 * @warning Currently only string GValue properties will be freed before setting; patch for
956 * other types - see @c handle_widget_property(). */
957 GEANY_API_SYMBOL
958 void stash_group_add_widget_property(StashGroup *group, gpointer setting,
959 const gchar *key_name, gpointer default_value, StashWidgetID widget_id,
960 const gchar *property_name, GType type)
962 if (!type)
963 type = object_get_property_type(G_OBJECT(widget_id), property_name);
965 add_widget_pref(group, type, setting, key_name, default_value,
966 G_TYPE_PARAM, widget_id)->extra.property_name = property_name;
970 enum
972 STASH_TREE_NAME,
973 STASH_TREE_VALUE,
974 STASH_TREE_COUNT
978 struct StashTreeValue
980 const gchar *group_name;
981 StashPref *pref;
982 struct
984 gchararray tree_string;
985 gint tree_int;
986 } data;
989 typedef struct StashTreeValue StashTreeValue;
992 static void stash_tree_renderer_set_data(GtkCellLayout *cell_layout, GtkCellRenderer *cell,
993 GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
995 GType cell_type = GPOINTER_TO_SIZE(user_data);
996 StashTreeValue *value;
997 StashPref *pref;
998 gboolean matches_type;
1000 gtk_tree_model_get(model, iter, STASH_TREE_VALUE, &value, -1);
1001 pref = value->pref;
1002 matches_type = pref->setting_type == cell_type;
1003 g_object_set(cell, "visible", matches_type, "sensitive", matches_type,
1004 cell_type == G_TYPE_BOOLEAN ? "activatable" : "editable", matches_type, NULL);
1006 if (matches_type)
1008 switch (pref->setting_type)
1010 case G_TYPE_BOOLEAN:
1011 g_object_set(cell, "active", value->data.tree_int, NULL);
1012 break;
1013 case G_TYPE_INT:
1015 gchar *text = g_strdup_printf("%d", value->data.tree_int);
1016 g_object_set(cell, "text", text, NULL);
1017 g_free(text);
1018 break;
1020 case G_TYPE_STRING:
1021 g_object_set(cell, "text", value->data.tree_string, NULL);
1022 break;
1028 static void stash_tree_renderer_edited(gchar *path_str, gchar *new_text, GtkTreeModel *model)
1030 GtkTreePath *path;
1031 GtkTreeIter iter;
1032 StashTreeValue *value;
1033 StashPref *pref;
1035 path = gtk_tree_path_new_from_string(path_str);
1036 gtk_tree_model_get_iter(model, &iter, path);
1037 gtk_tree_model_get(model, &iter, STASH_TREE_VALUE, &value, -1);
1038 pref = value->pref;
1040 switch (pref->setting_type)
1042 case G_TYPE_BOOLEAN:
1043 value->data.tree_int = !value->data.tree_int;
1044 break;
1045 case G_TYPE_INT:
1046 value->data.tree_int = atoi(new_text);
1047 break;
1048 case G_TYPE_STRING:
1049 SETPTR(value->data.tree_string, g_strdup(new_text));
1050 break;
1053 gtk_tree_model_row_changed(model, path, &iter);
1054 gtk_tree_path_free(path);
1058 static void stash_tree_boolean_toggled(GtkCellRendererToggle *cell, gchar *path_str,
1059 GtkTreeModel *model)
1061 stash_tree_renderer_edited(path_str, NULL, model);
1065 static void stash_tree_string_edited(GtkCellRenderer *cell, gchar *path_str, gchar *new_text,
1066 GtkTreeModel *model)
1068 stash_tree_renderer_edited(path_str, new_text, model);
1072 static gboolean stash_tree_discard_value(GtkTreeModel *model, GtkTreePath *path,
1073 GtkTreeIter *iter, gpointer user_data)
1075 StashTreeValue *value;
1077 gtk_tree_model_get(model, iter, STASH_TREE_VALUE, &value, -1);
1078 /* don't access value->pref as it might already have been freed */
1079 g_free(value->data.tree_string);
1080 g_free(value);
1082 return FALSE;
1086 static void stash_tree_destroy_cb(GtkWidget *widget, gpointer user_data)
1088 GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
1089 gtk_tree_model_foreach(model, stash_tree_discard_value, NULL);
1093 static void stash_tree_append_pref(StashGroup *group, StashPref *entry, GtkListStore *store,
1094 PrefAction action)
1096 GtkTreeIter iter;
1097 StashTreeValue *value;
1099 value = g_new0(StashTreeValue, 1);
1101 value->group_name = group->name;
1102 value->pref = entry;
1104 gtk_list_store_append(store, &iter);
1105 gtk_list_store_set(store, &iter, STASH_TREE_NAME, entry->key_name,
1106 STASH_TREE_VALUE, value, -1);
1110 static void stash_tree_append_prefs(GPtrArray *group_array,
1111 GtkListStore *store, PrefAction action)
1113 StashGroup *group;
1114 guint i, j;
1115 StashPref *entry;
1117 foreach_ptr_array(group, i, group_array)
1119 if (group->various)
1121 foreach_ptr_array(entry, j, group->entries)
1122 stash_tree_append_pref(group, entry, store, action);
1128 /* Setups a simple editor for stash preferences based on the widget arguments.
1129 * group_array - Array of groups which's settings will be edited.
1130 * tree - GtkTreeView in which to edit the preferences. Must be empty. */
1131 void stash_tree_setup(GPtrArray *group_array, GtkTreeView *tree)
1133 GtkListStore *store;
1134 GtkTreeModel *model;
1135 GtkCellRenderer *cell;
1136 GtkTreeViewColumn *column;
1137 GtkAdjustment *adjustment;
1139 store = gtk_list_store_new(STASH_TREE_COUNT, G_TYPE_STRING, G_TYPE_POINTER);
1140 stash_tree_append_prefs(group_array, store, PREF_DISPLAY);
1141 gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store), STASH_TREE_NAME,
1142 GTK_SORT_ASCENDING);
1144 model = GTK_TREE_MODEL(store);
1145 gtk_tree_view_set_model(tree, model);
1146 g_object_unref(G_OBJECT(store));
1147 g_signal_connect(tree, "destroy", G_CALLBACK(stash_tree_destroy_cb), NULL);
1149 cell = gtk_cell_renderer_text_new();
1150 column = gtk_tree_view_column_new_with_attributes(_("Name"), cell, "text",
1151 STASH_TREE_NAME, NULL);
1152 gtk_tree_view_column_set_sort_column_id(column, STASH_TREE_NAME);
1153 gtk_tree_view_column_set_sort_indicator(column, TRUE);
1154 gtk_tree_view_append_column(tree, column);
1156 column = gtk_tree_view_column_new();
1157 gtk_tree_view_column_set_title(column, _("Value"));
1158 gtk_tree_view_append_column(tree, column);
1159 /* boolean renderer */
1160 cell = gtk_cell_renderer_toggle_new();
1161 g_signal_connect(cell, "toggled", G_CALLBACK(stash_tree_boolean_toggled), model);
1162 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(column), cell, FALSE);
1163 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(column), cell,
1164 stash_tree_renderer_set_data, GSIZE_TO_POINTER(G_TYPE_BOOLEAN), NULL);
1165 /* string renderer */
1166 cell = gtk_cell_renderer_text_new();
1167 g_object_set(cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
1168 g_signal_connect(cell, "edited", G_CALLBACK(stash_tree_string_edited), model);
1169 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(column), cell, TRUE);
1170 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(column), cell,
1171 stash_tree_renderer_set_data, GSIZE_TO_POINTER(G_TYPE_STRING), NULL);
1172 /* integer renderer */
1173 cell = gtk_cell_renderer_spin_new();
1174 adjustment = GTK_ADJUSTMENT(gtk_adjustment_new(0, G_MININT, G_MAXINT, 1, 10, 0));
1175 g_object_set(cell, "adjustment", adjustment, NULL);
1176 g_signal_connect(cell, "edited", G_CALLBACK(stash_tree_string_edited), model);
1177 gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(column), cell, FALSE);
1178 gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(column), cell,
1179 stash_tree_renderer_set_data, GSIZE_TO_POINTER(G_TYPE_INT), NULL);
1183 static void stash_tree_display_pref(StashTreeValue *value, StashPref *entry)
1185 switch (entry->setting_type)
1187 case G_TYPE_BOOLEAN:
1188 value->data.tree_int = *(gboolean *) entry->setting;
1189 break;
1190 case G_TYPE_INT:
1191 value->data.tree_int = *(gint *) entry->setting;
1192 break;
1193 case G_TYPE_STRING:
1194 SETPTR(value->data.tree_string, g_strdup(*(gchararray *) entry->setting));
1195 break;
1196 default:
1197 g_warning("Unhandled type for %s::%s in %s()!", value->group_name,
1198 entry->key_name, G_STRFUNC);
1203 static void stash_tree_update_pref(StashTreeValue *value, StashPref *entry)
1205 switch (entry->setting_type)
1207 case G_TYPE_BOOLEAN:
1208 *(gboolean *) entry->setting = value->data.tree_int;
1209 break;
1210 case G_TYPE_INT:
1211 *(gint *) entry->setting = value->data.tree_int;
1212 break;
1213 case G_TYPE_STRING:
1215 gchararray *text = entry->setting;
1216 SETPTR(*text, g_strdup(value->data.tree_string));
1217 break;
1219 default:
1220 g_warning("Unhandled type for %s::%s in %s()!", value->group_name,
1221 entry->key_name, G_STRFUNC);
1226 static void stash_tree_action(GtkTreeModel *model, PrefAction action)
1228 GtkTreeIter iter;
1229 gboolean valid = gtk_tree_model_get_iter_first(model, &iter);
1230 StashTreeValue *value;
1232 while (valid)
1234 gtk_tree_model_get(model, &iter, STASH_TREE_VALUE, &value, -1);
1236 switch (action)
1238 case PREF_DISPLAY:
1239 stash_tree_display_pref(value, value->pref);
1240 break;
1241 case PREF_UPDATE:
1242 stash_tree_update_pref(value, value->pref);
1243 break;
1245 valid = gtk_tree_model_iter_next(model, &iter);
1250 void stash_tree_display(GtkTreeView *tree)
1252 stash_tree_action(gtk_tree_view_get_model(tree), PREF_DISPLAY);
1256 void stash_tree_update(GtkTreeView *tree)
1258 stash_tree_action(gtk_tree_view_get_model(tree), PREF_UPDATE);