2008-11-28 12:52:19 +00:00
|
|
|
/*
|
|
|
|
* stash.c - this file is part of Geany, a fast and lightweight IDE
|
|
|
|
*
|
2011-01-19 19:39:09 +00:00
|
|
|
* Copyright 2008-2011 Nick Treleaven <nick(dot)treleaven(at)btinternet(dot)com>
|
|
|
|
* Copyright 2008-2011 Enrico Tröger <enrico(dot)troeger(at)uvena(dot)de>
|
2008-11-28 12:52:19 +00:00
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, write to the Free Software
|
|
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
|
|
* MA 02110-1301, USA.
|
|
|
|
*
|
|
|
|
* $Id$
|
|
|
|
*/
|
|
|
|
|
2010-03-30 16:29:38 +00:00
|
|
|
/**
|
2010-03-17 17:15:47 +00:00
|
|
|
* @file stash.h
|
|
|
|
* Lightweight library for reading/writing @c GKeyFile settings and synchronizing widgets with
|
|
|
|
* C variables.
|
|
|
|
*
|
|
|
|
* Note: Stash should only depend on GLib and GTK, but currently has some minor
|
|
|
|
* dependencies on Geany's utils.c.
|
|
|
|
*
|
|
|
|
* @section Terms
|
2008-12-22 17:13:37 +00:00
|
|
|
* 'Setting' is used only for data stored on disk or in memory.
|
|
|
|
* 'Pref' can also include visual widget information.
|
2008-12-22 16:14:48 +00:00
|
|
|
*
|
2010-03-17 17:15:47 +00:00
|
|
|
* @section Memory Usage
|
2008-12-22 17:13:37 +00:00
|
|
|
* Stash will not duplicate strings if they are normally static arrays, such as
|
2009-07-20 12:00:06 +00:00
|
|
|
* keyfile group names and key names, string default values, widget_id names, property names.
|
|
|
|
*
|
2010-03-17 17:15:47 +00:00
|
|
|
* @section String Settings
|
2010-03-19 12:51:08 +00:00
|
|
|
* String settings and other dynamically allocated settings will be initialized to NULL when
|
2010-03-30 16:29:38 +00:00
|
|
|
* added to a StashGroup (so they can safely be reassigned later).
|
2009-07-20 12:00:06 +00:00
|
|
|
*
|
2010-03-17 17:15:47 +00:00
|
|
|
* @section Widget Support
|
2009-07-20 12:00:06 +00:00
|
|
|
* Widgets very commonly used in configuration dialogs will be supported with their own function.
|
2010-03-31 16:21:28 +00:00
|
|
|
* Widgets less commonly used such as @c GtkExpander or widget settings that aren't commonly needed
|
2009-07-20 12:00:06 +00:00
|
|
|
* to be persistent won't be directly supported, to keep the library lightweight. However, you can
|
|
|
|
* use stash_group_add_widget_property() to also save these settings for any read/write widget
|
2010-03-31 16:21:28 +00:00
|
|
|
* property. Macros could be added for common widget properties such as @c GtkExpander:"expanded".
|
2010-03-17 17:15:47 +00:00
|
|
|
*
|
2010-03-31 16:21:28 +00:00
|
|
|
* @section settings-example Settings Example
|
2010-09-22 16:33:38 +00:00
|
|
|
* Here we have some settings for how to make a cup - whether it should be made of china
|
|
|
|
* and who's going to make it. (Yes, it's a stupid example).
|
2010-03-17 17:15:47 +00:00
|
|
|
* @include stash-example.c
|
2010-03-19 17:38:27 +00:00
|
|
|
* @note You might want to handle the warning/error conditions differently from above.
|
2010-03-31 16:21:28 +00:00
|
|
|
*
|
|
|
|
* @section prefs-example GUI Prefs Example
|
2010-09-22 16:33:38 +00:00
|
|
|
* For prefs, it's the same as the above example but you tell Stash to add widget prefs instead of
|
|
|
|
* just data settings.
|
2010-03-31 16:21:28 +00:00
|
|
|
*
|
|
|
|
* This example uses lookup strings for widgets as they are more flexible than widget pointers.
|
2010-09-22 16:33:38 +00:00
|
|
|
* Code to load and save the settings is omitted - see the first example instead.
|
|
|
|
*
|
|
|
|
* Here we show a dialog with a toggle button for whether the cup should have a handle.
|
|
|
|
* @include stash-gui-example.c
|
|
|
|
* @note This example should also work for other widget containers besides dialogs, e.g. popup menus.
|
2010-03-17 17:15:47 +00:00
|
|
|
*/
|
2008-12-11 13:25:08 +00:00
|
|
|
|
2008-12-22 18:18:14 +00:00
|
|
|
/* Implementation Note
|
|
|
|
* We use a GArray to hold prefs. It would be more efficient for user code to declare
|
2010-03-18 17:04:17 +00:00
|
|
|
* a static array of StashPref structs, but we don't do this because:
|
2008-12-22 18:18:14 +00:00
|
|
|
*
|
|
|
|
* * It would be more ugly (lots of casts and NULLs).
|
|
|
|
* * Less type checking.
|
|
|
|
* * The API would have to break when adding/changing fields.
|
|
|
|
*
|
|
|
|
* Usually the prefs code isn't what user code will spend most of its time doing, so this
|
|
|
|
* should be efficient enough. But, if desired we could add a stash_group_set_size() function
|
2010-09-22 16:33:38 +00:00
|
|
|
* to reduce reallocation (or perhaps use a different container).
|
2010-03-17 17:15:47 +00:00
|
|
|
*
|
2010-09-22 16:33:38 +00:00
|
|
|
* Note: Maybe using GSlice chunks with an extra 'next' pointer would be more efficient.
|
2010-03-17 17:15:47 +00:00
|
|
|
*/
|
2008-12-22 18:18:14 +00:00
|
|
|
|
2008-11-28 12:52:19 +00:00
|
|
|
|
2010-10-08 14:42:41 +00:00
|
|
|
#include "geany.h" /* necessary for utils.h, otherwise use gtk/gtk.h */
|
2011-07-28 17:59:22 +00:00
|
|
|
#include <stdlib.h> /* only for atoi() */
|
2011-08-03 15:20:43 +00:00
|
|
|
#include <string.h> /* only for strcmp() */
|
2011-07-28 17:59:22 +00:00
|
|
|
#include "support.h" /* only for _("text") */
|
2010-10-08 14:42:41 +00:00
|
|
|
#include "utils.h" /* only for foreach_*, utils_get_setting_*(). Stash should not depend on Geany. */
|
2008-11-28 12:52:19 +00:00
|
|
|
|
|
|
|
#include "stash.h"
|
2008-12-09 13:16:52 +00:00
|
|
|
|
|
|
|
|
2010-03-18 17:04:17 +00:00
|
|
|
struct StashPref
|
2008-12-09 13:16:52 +00:00
|
|
|
{
|
2008-12-22 16:14:48 +00:00
|
|
|
GType setting_type; /* e.g. G_TYPE_INT */
|
2010-08-10 12:46:30 +00:00
|
|
|
gpointer setting; /* Address of a variable */
|
2008-12-09 13:16:52 +00:00
|
|
|
const gchar *key_name;
|
2010-08-10 12:46:30 +00:00
|
|
|
gpointer default_value; /* Default value, e.g. (gpointer)1 */
|
2008-12-11 12:49:26 +00:00
|
|
|
GType widget_type; /* e.g. GTK_TYPE_TOGGLE_BUTTON */
|
2010-03-18 17:04:17 +00:00
|
|
|
StashWidgetID widget_id; /* (GtkWidget*) or (gchar*) */
|
2008-12-15 13:02:29 +00:00
|
|
|
gpointer fields; /* extra fields */
|
2008-12-09 13:16:52 +00:00
|
|
|
};
|
|
|
|
|
2010-03-18 17:04:17 +00:00
|
|
|
typedef struct StashPref StashPref;
|
2010-03-17 17:15:47 +00:00
|
|
|
|
2010-03-18 17:04:17 +00:00
|
|
|
struct StashGroup
|
2008-12-09 13:16:52 +00:00
|
|
|
{
|
|
|
|
const gchar *name; /* group name to use in the keyfile */
|
2010-03-18 17:04:17 +00:00
|
|
|
GArray *entries; /* array of StashPref */
|
2011-07-28 17:59:22 +00:00
|
|
|
gboolean various; /* mark group for display/edit in stash treeview */
|
2008-12-27 12:55:04 +00:00
|
|
|
gboolean use_defaults; /* use default values if there's no keyfile entry */
|
2008-12-09 13:16:52 +00:00
|
|
|
};
|
|
|
|
|
2008-12-15 13:02:29 +00:00
|
|
|
typedef struct EnumWidget
|
|
|
|
{
|
2010-03-18 17:04:17 +00:00
|
|
|
StashWidgetID widget_id;
|
2008-12-15 13:02:29 +00:00
|
|
|
gint enum_id;
|
|
|
|
}
|
|
|
|
EnumWidget;
|
|
|
|
|
2008-11-28 12:52:19 +00:00
|
|
|
|
|
|
|
typedef enum SettingAction
|
|
|
|
{
|
|
|
|
SETTING_READ,
|
|
|
|
SETTING_WRITE
|
|
|
|
}
|
|
|
|
SettingAction;
|
|
|
|
|
2008-12-11 12:49:26 +00:00
|
|
|
typedef enum PrefAction
|
|
|
|
{
|
|
|
|
PREF_DISPLAY,
|
|
|
|
PREF_UPDATE
|
|
|
|
}
|
|
|
|
PrefAction;
|
2008-11-28 12:52:19 +00:00
|
|
|
|
|
|
|
|
2010-03-18 17:04:17 +00:00
|
|
|
static void handle_boolean_setting(StashGroup *group, StashPref *se,
|
2008-11-28 12:52:19 +00:00
|
|
|
GKeyFile *config, SettingAction action)
|
|
|
|
{
|
|
|
|
gboolean *setting = se->setting;
|
|
|
|
|
|
|
|
switch (action)
|
|
|
|
{
|
|
|
|
case SETTING_READ:
|
|
|
|
*setting = utils_get_setting_boolean(config, group->name, se->key_name,
|
|
|
|
GPOINTER_TO_INT(se->default_value));
|
|
|
|
break;
|
|
|
|
case SETTING_WRITE:
|
|
|
|
g_key_file_set_boolean(config, group->name, se->key_name, *setting);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-18 17:04:17 +00:00
|
|
|
static void handle_integer_setting(StashGroup *group, StashPref *se,
|
2008-11-28 12:52:19 +00:00
|
|
|
GKeyFile *config, SettingAction action)
|
|
|
|
{
|
2010-10-28 11:45:14 +00:00
|
|
|
gint *setting = se->setting;
|
2008-11-28 12:52:19 +00:00
|
|
|
|
|
|
|
switch (action)
|
|
|
|
{
|
|
|
|
case SETTING_READ:
|
|
|
|
*setting = utils_get_setting_integer(config, group->name, se->key_name,
|
|
|
|
GPOINTER_TO_INT(se->default_value));
|
|
|
|
break;
|
|
|
|
case SETTING_WRITE:
|
|
|
|
g_key_file_set_integer(config, group->name, se->key_name, *setting);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-18 17:04:17 +00:00
|
|
|
static void handle_string_setting(StashGroup *group, StashPref *se,
|
2008-12-01 14:54:47 +00:00
|
|
|
GKeyFile *config, SettingAction action)
|
|
|
|
{
|
|
|
|
gchararray *setting = se->setting;
|
|
|
|
|
|
|
|
switch (action)
|
|
|
|
{
|
|
|
|
case SETTING_READ:
|
|
|
|
g_free(*setting);
|
|
|
|
*setting = utils_get_setting_string(config, group->name, se->key_name,
|
|
|
|
se->default_value);
|
|
|
|
break;
|
|
|
|
case SETTING_WRITE:
|
2008-12-28 17:25:09 +00:00
|
|
|
g_key_file_set_string(config, group->name, se->key_name,
|
|
|
|
*setting ? *setting : "");
|
2008-12-01 14:54:47 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-18 17:04:17 +00:00
|
|
|
static void handle_strv_setting(StashGroup *group, StashPref *se,
|
2008-12-28 13:21:35 +00:00
|
|
|
GKeyFile *config, SettingAction action)
|
|
|
|
{
|
|
|
|
gchararray **setting = se->setting;
|
|
|
|
|
|
|
|
switch (action)
|
|
|
|
{
|
|
|
|
case SETTING_READ:
|
|
|
|
g_strfreev(*setting);
|
|
|
|
*setting = g_key_file_get_string_list(config, group->name, se->key_name,
|
|
|
|
NULL, NULL);
|
|
|
|
if (*setting == NULL)
|
|
|
|
*setting = g_strdupv(se->default_value);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SETTING_WRITE:
|
|
|
|
{
|
|
|
|
/* don't try to save a NULL string vector */
|
|
|
|
gchar *dummy[] = { "", NULL };
|
|
|
|
gchar **strv = *setting ? *setting : dummy;
|
|
|
|
|
|
|
|
g_key_file_set_string_list(config, group->name, se->key_name,
|
|
|
|
(const gchar**)strv, g_strv_length(strv));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-18 17:04:17 +00:00
|
|
|
static void keyfile_action(SettingAction action, StashGroup *group, GKeyFile *keyfile)
|
2008-11-28 12:52:19 +00:00
|
|
|
{
|
2010-03-18 17:04:17 +00:00
|
|
|
StashPref *entry;
|
2008-11-28 12:52:19 +00:00
|
|
|
|
2010-03-18 17:04:17 +00:00
|
|
|
foreach_array(StashPref, entry, group->entries)
|
2008-11-28 12:52:19 +00:00
|
|
|
{
|
2008-12-27 12:55:04 +00:00
|
|
|
/* don't override settings with default values */
|
|
|
|
if (!group->use_defaults && action == SETTING_READ &&
|
|
|
|
!g_key_file_has_key(keyfile, group->name, entry->key_name, NULL))
|
|
|
|
continue;
|
2008-11-28 12:52:19 +00:00
|
|
|
|
2008-12-22 16:14:48 +00:00
|
|
|
switch (entry->setting_type)
|
2008-11-28 12:52:19 +00:00
|
|
|
{
|
|
|
|
case G_TYPE_BOOLEAN:
|
2008-12-09 13:16:52 +00:00
|
|
|
handle_boolean_setting(group, entry, keyfile, action); break;
|
2008-11-28 12:52:19 +00:00
|
|
|
case G_TYPE_INT:
|
2008-12-09 13:16:52 +00:00
|
|
|
handle_integer_setting(group, entry, keyfile, action); break;
|
2008-12-01 14:54:47 +00:00
|
|
|
case G_TYPE_STRING:
|
|
|
|
handle_string_setting(group, entry, keyfile, action); break;
|
2008-11-28 12:52:19 +00:00
|
|
|
default:
|
2010-08-10 12:46:30 +00:00
|
|
|
/* Note: G_TYPE_STRV is not a constant, can't use case label */
|
2008-12-28 13:21:35 +00:00
|
|
|
if (entry->setting_type == G_TYPE_STRV)
|
|
|
|
handle_strv_setting(group, entry, keyfile, action);
|
|
|
|
else
|
2008-12-28 16:20:52 +00:00
|
|
|
g_warning("Unhandled type for %s::%s in %s()!", group->name, entry->key_name,
|
|
|
|
G_STRFUNC);
|
2008-11-28 12:52:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-30 16:29:38 +00:00
|
|
|
/** Reads key values from @a keyfile into the group settings.
|
|
|
|
* @note You should still call this even if the keyfile couldn't be loaded from disk
|
|
|
|
* so that all Stash settings are initialized to defaults.
|
2010-03-17 17:15:47 +00:00
|
|
|
* @param group .
|
2010-03-30 16:29:38 +00:00
|
|
|
* @param keyfile Usually loaded from a configuration file first. */
|
2010-03-18 17:04:17 +00:00
|
|
|
void stash_group_load_from_key_file(StashGroup *group, GKeyFile *keyfile)
|
2008-11-28 12:52:19 +00:00
|
|
|
{
|
2008-12-11 12:49:26 +00:00
|
|
|
keyfile_action(SETTING_READ, group, keyfile);
|
2008-11-28 12:52:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-30 16:29:38 +00:00
|
|
|
/** Writes group settings into key values in @a keyfile.
|
|
|
|
* @a keyfile is usually written to a configuration file afterwards.
|
2010-03-17 17:15:47 +00:00
|
|
|
* @param group .
|
|
|
|
* @param keyfile . */
|
2010-03-18 17:04:17 +00:00
|
|
|
void stash_group_save_to_key_file(StashGroup *group, GKeyFile *keyfile)
|
2008-11-28 12:52:19 +00:00
|
|
|
{
|
2008-12-11 12:49:26 +00:00
|
|
|
keyfile_action(SETTING_WRITE, group, keyfile);
|
2008-11-28 12:52:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-19 17:38:27 +00:00
|
|
|
/** Reads group settings from a configuration file using @c GKeyFile.
|
2010-03-30 16:29:38 +00:00
|
|
|
* @note Stash settings will be initialized to defaults if the keyfile
|
|
|
|
* couldn't be loaded from disk.
|
2010-03-19 17:38:27 +00:00
|
|
|
* @param group .
|
2010-03-30 16:29:38 +00:00
|
|
|
* @param filename Filename of the file to read, in locale encoding.
|
2010-03-19 17:38:27 +00:00
|
|
|
* @return @c TRUE if a key file could be loaded.
|
|
|
|
* @see stash_group_load_from_key_file().
|
|
|
|
**/
|
2010-03-30 16:29:38 +00:00
|
|
|
gboolean stash_group_load_from_file(StashGroup *group, const gchar *filename)
|
2010-03-19 17:38:27 +00:00
|
|
|
{
|
|
|
|
GKeyFile *keyfile;
|
|
|
|
gboolean ret;
|
|
|
|
|
|
|
|
keyfile = g_key_file_new();
|
2010-03-30 16:29:38 +00:00
|
|
|
ret = g_key_file_load_from_file(keyfile, filename, 0, NULL);
|
2010-03-19 17:38:27 +00:00
|
|
|
/* even on failure we load settings to apply defaults */
|
|
|
|
stash_group_load_from_key_file(group, keyfile);
|
|
|
|
|
|
|
|
g_key_file_free(keyfile);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Writes group settings to a configuration file using @c GKeyFile.
|
|
|
|
*
|
|
|
|
* @param group .
|
|
|
|
* @param filename Filename of the file to write, in locale encoding.
|
|
|
|
* @param flags Keyfile options - @c G_KEY_FILE_NONE is the most efficient.
|
|
|
|
* @return 0 if the file was successfully written, otherwise the @c errno of the
|
|
|
|
* failed operation is returned.
|
|
|
|
* @see stash_group_save_to_key_file().
|
|
|
|
**/
|
|
|
|
gint stash_group_save_to_file(StashGroup *group, const gchar *filename,
|
|
|
|
GKeyFileFlags flags)
|
|
|
|
{
|
|
|
|
GKeyFile *keyfile;
|
|
|
|
gchar *data;
|
|
|
|
gint ret;
|
|
|
|
|
|
|
|
keyfile = g_key_file_new();
|
|
|
|
/* if we need to keep comments or translations, try to load first */
|
|
|
|
if (flags)
|
|
|
|
g_key_file_load_from_file(keyfile, filename, flags, NULL);
|
|
|
|
|
|
|
|
stash_group_save_to_key_file(group, keyfile);
|
|
|
|
data = g_key_file_to_data(keyfile, NULL, NULL);
|
|
|
|
ret = utils_write_file(filename, data);
|
|
|
|
g_free(data);
|
|
|
|
g_key_file_free(keyfile);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-17 17:15:47 +00:00
|
|
|
/** Creates a new group.
|
|
|
|
* @param name Name used for @c GKeyFile group.
|
|
|
|
* @return Group. */
|
2010-03-18 17:04:17 +00:00
|
|
|
StashGroup *stash_group_new(const gchar *name)
|
2008-12-09 13:16:52 +00:00
|
|
|
{
|
2010-03-18 17:04:17 +00:00
|
|
|
StashGroup *group = g_new0(StashGroup, 1);
|
2008-12-09 13:16:52 +00:00
|
|
|
|
|
|
|
group->name = name;
|
2010-03-18 17:04:17 +00:00
|
|
|
group->entries = g_array_new(FALSE, FALSE, sizeof(StashPref));
|
2008-12-27 12:55:04 +00:00
|
|
|
group->use_defaults = TRUE;
|
2008-12-09 13:16:52 +00:00
|
|
|
return group;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-17 17:15:47 +00:00
|
|
|
/** Frees a group.
|
|
|
|
* @param group . */
|
2010-03-18 17:04:17 +00:00
|
|
|
void stash_group_free(StashGroup *group)
|
2008-12-09 13:16:52 +00:00
|
|
|
{
|
2010-03-18 17:04:17 +00:00
|
|
|
StashPref *entry;
|
2008-12-15 13:02:29 +00:00
|
|
|
|
2010-03-18 17:04:17 +00:00
|
|
|
foreach_array(StashPref, entry, group->entries)
|
2008-12-15 13:02:29 +00:00
|
|
|
{
|
|
|
|
if (entry->widget_type == GTK_TYPE_RADIO_BUTTON)
|
|
|
|
g_free(entry->fields);
|
2009-07-20 12:00:06 +00:00
|
|
|
else if (entry->widget_type == G_TYPE_PARAM)
|
|
|
|
continue;
|
2008-12-15 13:02:29 +00:00
|
|
|
else
|
2009-07-20 12:00:06 +00:00
|
|
|
g_assert(entry->fields == NULL); /* to prevent memory leaks, must handle fields above */
|
2008-12-15 13:02:29 +00:00
|
|
|
}
|
2008-12-09 13:16:52 +00:00
|
|
|
g_array_free(group->entries, TRUE);
|
|
|
|
g_free(group);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-09-22 11:52:58 +00:00
|
|
|
/* Used for selecting groups passed to stash_tree_setup().
|
2008-12-27 12:55:04 +00:00
|
|
|
* @c FALSE by default. */
|
2011-07-28 17:59:22 +00:00
|
|
|
void stash_group_set_various(StashGroup *group, gboolean various)
|
2008-12-09 13:16:52 +00:00
|
|
|
{
|
2011-07-28 17:59:22 +00:00
|
|
|
group->various = various;
|
2008-12-09 13:16:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-12-27 12:55:04 +00:00
|
|
|
/* When @c FALSE, Stash doesn't change the setting if there is no keyfile entry, so it
|
|
|
|
* remains whatever it was initialized/set to by user code.
|
|
|
|
* @c TRUE by default. */
|
2010-03-18 17:04:17 +00:00
|
|
|
void stash_group_set_use_defaults(StashGroup *group, gboolean use_defaults)
|
2008-12-27 12:55:04 +00:00
|
|
|
{
|
|
|
|
group->use_defaults = use_defaults;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-18 17:04:17 +00:00
|
|
|
static StashPref *
|
|
|
|
add_pref(StashGroup *group, GType type, gpointer setting,
|
2008-12-09 13:16:52 +00:00
|
|
|
const gchar *key_name, gpointer default_value)
|
|
|
|
{
|
2010-03-18 17:04:17 +00:00
|
|
|
StashPref entry = {type, setting, key_name, default_value, G_TYPE_NONE, NULL, NULL};
|
2008-12-11 12:49:26 +00:00
|
|
|
GArray *array = group->entries;
|
2008-12-09 13:16:52 +00:00
|
|
|
|
2010-03-19 12:51:08 +00:00
|
|
|
/* init any pointer settings to NULL so they can be freed later */
|
|
|
|
if (type == G_TYPE_STRING ||
|
|
|
|
type == G_TYPE_STRV)
|
2010-03-30 16:29:38 +00:00
|
|
|
if (group->use_defaults)
|
2010-03-19 12:51:08 +00:00
|
|
|
*(gpointer**)setting = NULL;
|
|
|
|
|
2008-12-11 12:49:26 +00:00
|
|
|
g_array_append_val(array, entry);
|
|
|
|
|
2010-03-18 17:04:17 +00:00
|
|
|
return &g_array_index(array, StashPref, array->len - 1);
|
2008-12-09 13:16:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-17 17:15:47 +00:00
|
|
|
/** Adds boolean setting.
|
|
|
|
* @param group .
|
|
|
|
* @param setting Address of setting variable.
|
|
|
|
* @param key_name Name for key in a @c GKeyFile.
|
|
|
|
* @param default_value Value to use if the key doesn't exist when loading. */
|
2010-03-18 17:04:17 +00:00
|
|
|
void stash_group_add_boolean(StashGroup *group, gboolean *setting,
|
2008-12-09 13:16:52 +00:00
|
|
|
const gchar *key_name, gboolean default_value)
|
|
|
|
{
|
|
|
|
add_pref(group, G_TYPE_BOOLEAN, setting, key_name, GINT_TO_POINTER(default_value));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-17 17:15:47 +00:00
|
|
|
/** Adds integer setting.
|
|
|
|
* @param group .
|
|
|
|
* @param setting Address of setting variable.
|
|
|
|
* @param key_name Name for key in a @c GKeyFile.
|
|
|
|
* @param default_value Value to use if the key doesn't exist when loading. */
|
2010-03-18 17:04:17 +00:00
|
|
|
void stash_group_add_integer(StashGroup *group, gint *setting,
|
2008-12-09 13:16:52 +00:00
|
|
|
const gchar *key_name, gint default_value)
|
|
|
|
{
|
|
|
|
add_pref(group, G_TYPE_INT, setting, key_name, GINT_TO_POINTER(default_value));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-17 17:15:47 +00:00
|
|
|
/** Adds string setting.
|
2010-03-19 12:51:08 +00:00
|
|
|
* The contents of @a setting will be initialized to @c NULL.
|
2010-03-17 17:15:47 +00:00
|
|
|
* @param group .
|
|
|
|
* @param setting Address of setting variable.
|
|
|
|
* @param key_name Name for key in a @c GKeyFile.
|
2010-03-19 12:51:08 +00:00
|
|
|
* @param default_value String to copy if the key doesn't exist when loading, or @c NULL. */
|
2010-03-18 17:04:17 +00:00
|
|
|
void stash_group_add_string(StashGroup *group, gchar **setting,
|
2008-12-09 13:16:52 +00:00
|
|
|
const gchar *key_name, const gchar *default_value)
|
|
|
|
{
|
2008-12-11 13:25:08 +00:00
|
|
|
add_pref(group, G_TYPE_STRING, setting, key_name, (gpointer)default_value);
|
2008-12-09 13:16:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-17 17:15:47 +00:00
|
|
|
/** Adds string vector setting (array of strings).
|
2010-03-19 12:51:08 +00:00
|
|
|
* The contents of @a setting will be initialized to @c NULL.
|
2010-03-17 17:15:47 +00:00
|
|
|
* @param group .
|
|
|
|
* @param setting Address of setting variable.
|
|
|
|
* @param key_name Name for key in a @c GKeyFile.
|
2010-03-19 12:51:08 +00:00
|
|
|
* @param default_value Vector to copy if the key doesn't exist when loading. Usually @c NULL. */
|
2010-03-18 17:04:17 +00:00
|
|
|
void stash_group_add_string_vector(StashGroup *group, gchar ***setting,
|
2008-12-28 13:21:35 +00:00
|
|
|
const gchar *key_name, const gchar **default_value)
|
|
|
|
{
|
|
|
|
add_pref(group, G_TYPE_STRV, setting, key_name, (gpointer)default_value);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-12-22 12:50:19 +00:00
|
|
|
/* *** GTK-related functions *** */
|
|
|
|
|
2008-12-11 12:49:26 +00:00
|
|
|
static void handle_toggle_button(GtkWidget *widget, gboolean *setting,
|
|
|
|
PrefAction action)
|
|
|
|
{
|
|
|
|
switch (action)
|
|
|
|
{
|
|
|
|
case PREF_DISPLAY:
|
|
|
|
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), *setting);
|
|
|
|
break;
|
|
|
|
case PREF_UPDATE:
|
|
|
|
*setting = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-18 17:04:17 +00:00
|
|
|
static void handle_spin_button(GtkWidget *widget, StashPref *entry,
|
2008-12-22 16:03:16 +00:00
|
|
|
PrefAction action)
|
|
|
|
{
|
|
|
|
gint *setting = entry->setting;
|
|
|
|
|
2008-12-22 16:14:48 +00:00
|
|
|
g_assert(entry->setting_type == G_TYPE_INT); /* only int spin prefs */
|
2008-12-22 16:03:16 +00:00
|
|
|
|
|
|
|
switch (action)
|
|
|
|
{
|
|
|
|
case PREF_DISPLAY:
|
|
|
|
gtk_spin_button_set_value(GTK_SPIN_BUTTON(widget), *setting);
|
|
|
|
break;
|
|
|
|
case PREF_UPDATE:
|
2009-01-08 17:14:18 +00:00
|
|
|
/* if the widget is focussed, the value might not be updated */
|
|
|
|
gtk_spin_button_update(GTK_SPIN_BUTTON(widget));
|
2008-12-22 16:03:16 +00:00
|
|
|
*setting = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(widget));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-18 17:04:17 +00:00
|
|
|
static void handle_combo_box(GtkWidget *widget, StashPref *entry,
|
2008-12-22 16:03:16 +00:00
|
|
|
PrefAction action)
|
|
|
|
{
|
|
|
|
gint *setting = entry->setting;
|
|
|
|
|
|
|
|
switch (action)
|
|
|
|
{
|
|
|
|
case PREF_DISPLAY:
|
|
|
|
gtk_combo_box_set_active(GTK_COMBO_BOX(widget), *setting);
|
|
|
|
break;
|
|
|
|
case PREF_UPDATE:
|
|
|
|
*setting = gtk_combo_box_get_active(GTK_COMBO_BOX(widget));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-18 17:04:17 +00:00
|
|
|
static void handle_entry(GtkWidget *widget, StashPref *entry,
|
2008-12-22 17:13:37 +00:00
|
|
|
PrefAction action)
|
|
|
|
{
|
|
|
|
gchararray *setting = entry->setting;
|
|
|
|
|
|
|
|
switch (action)
|
|
|
|
{
|
|
|
|
case PREF_DISPLAY:
|
|
|
|
gtk_entry_set_text(GTK_ENTRY(widget), *setting);
|
|
|
|
break;
|
|
|
|
case PREF_UPDATE:
|
|
|
|
g_free(*setting);
|
|
|
|
*setting = g_strdup(gtk_entry_get_text(GTK_ENTRY(widget)));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-18 17:04:17 +00:00
|
|
|
static void handle_combo_box_entry(GtkWidget *widget, StashPref *entry,
|
2008-12-22 17:13:37 +00:00
|
|
|
PrefAction action)
|
|
|
|
{
|
|
|
|
widget = gtk_bin_get_child(GTK_BIN(widget));
|
|
|
|
handle_entry(widget, entry, action);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-12-11 12:49:26 +00:00
|
|
|
/* taken from Glade 2.x generated support.c */
|
|
|
|
static GtkWidget*
|
2009-07-20 12:00:06 +00:00
|
|
|
lookup_widget(GtkWidget *widget, const gchar *widget_name)
|
2008-12-11 12:49:26 +00:00
|
|
|
{
|
|
|
|
GtkWidget *parent, *found_widget;
|
|
|
|
|
|
|
|
for (;;)
|
|
|
|
{
|
|
|
|
if (GTK_IS_MENU (widget))
|
|
|
|
parent = gtk_menu_get_attach_widget (GTK_MENU (widget));
|
|
|
|
else
|
|
|
|
parent = widget->parent;
|
|
|
|
if (!parent)
|
|
|
|
parent = (GtkWidget*) g_object_get_data (G_OBJECT (widget), "GladeParentKey");
|
|
|
|
if (parent == NULL)
|
|
|
|
break;
|
|
|
|
widget = parent;
|
|
|
|
}
|
|
|
|
|
|
|
|
found_widget = (GtkWidget*) g_object_get_data (G_OBJECT (widget), widget_name);
|
|
|
|
if (!found_widget)
|
|
|
|
g_warning ("Widget not found: %s", widget_name);
|
|
|
|
return found_widget;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-12-15 13:02:29 +00:00
|
|
|
static GtkWidget *
|
2010-03-18 17:04:17 +00:00
|
|
|
get_widget(GtkWidget *owner, StashWidgetID widget_id)
|
2008-12-15 13:02:29 +00:00
|
|
|
{
|
2011-06-17 22:53:01 +00:00
|
|
|
GtkWidget *widget;
|
2008-12-15 13:02:29 +00:00
|
|
|
|
|
|
|
if (owner)
|
2011-06-17 22:53:01 +00:00
|
|
|
widget = lookup_widget(owner, (const gchar *)widget_id);
|
|
|
|
else
|
|
|
|
widget = (GtkWidget *)widget_id;
|
2008-12-15 13:02:29 +00:00
|
|
|
|
|
|
|
if (!GTK_IS_WIDGET(widget))
|
|
|
|
{
|
2008-12-28 16:20:52 +00:00
|
|
|
g_warning("Unknown widget in %s()!", G_STRFUNC);
|
2008-12-15 13:02:29 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
return widget;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void handle_radio_button(GtkWidget *widget, gint enum_id, gboolean *setting,
|
|
|
|
PrefAction action)
|
|
|
|
{
|
|
|
|
switch (action)
|
|
|
|
{
|
|
|
|
case PREF_DISPLAY:
|
|
|
|
if (*setting == enum_id)
|
|
|
|
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(widget), TRUE);
|
|
|
|
break;
|
|
|
|
case PREF_UPDATE:
|
|
|
|
if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)))
|
|
|
|
*setting = enum_id;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void handle_radio_buttons(GtkWidget *owner, EnumWidget *fields,
|
|
|
|
gboolean *setting,
|
|
|
|
PrefAction action)
|
|
|
|
{
|
|
|
|
EnumWidget *field = fields;
|
|
|
|
gsize count = 0;
|
|
|
|
GtkWidget *widget = NULL;
|
|
|
|
|
|
|
|
while (1)
|
|
|
|
{
|
|
|
|
widget = get_widget(owner, field->widget_id);
|
|
|
|
|
|
|
|
if (!widget)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
count++;
|
|
|
|
handle_radio_button(widget, field->enum_id, setting, action);
|
|
|
|
field++;
|
|
|
|
if (!field->widget_id)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (g_slist_length(gtk_radio_button_get_group(GTK_RADIO_BUTTON(widget))) != count)
|
|
|
|
g_warning("Missing/invalid radio button widget IDs found!");
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-18 17:04:17 +00:00
|
|
|
static void handle_widget_property(GtkWidget *widget, StashPref *entry,
|
2009-07-20 12:00:06 +00:00
|
|
|
PrefAction action)
|
|
|
|
{
|
|
|
|
GObject *object = G_OBJECT(widget);
|
|
|
|
const gchar *name = entry->fields;
|
|
|
|
|
|
|
|
switch (action)
|
|
|
|
{
|
|
|
|
case PREF_DISPLAY:
|
|
|
|
g_object_set(object, name, entry->setting, NULL);
|
|
|
|
break;
|
|
|
|
case PREF_UPDATE:
|
|
|
|
if (entry->setting_type == G_TYPE_STRING)
|
|
|
|
g_free(entry->setting);
|
|
|
|
/* TODO: Which other types need freeing here? */
|
|
|
|
|
|
|
|
g_object_get(object, name, entry->setting, NULL);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-18 17:04:17 +00:00
|
|
|
static void pref_action(PrefAction action, StashGroup *group, GtkWidget *owner)
|
2008-12-11 12:49:26 +00:00
|
|
|
{
|
2010-03-18 17:04:17 +00:00
|
|
|
StashPref *entry;
|
2008-12-11 12:49:26 +00:00
|
|
|
|
2010-03-18 17:04:17 +00:00
|
|
|
foreach_array(StashPref, entry, group->entries)
|
2008-12-11 12:49:26 +00:00
|
|
|
{
|
2008-12-15 13:02:29 +00:00
|
|
|
GtkWidget *widget;
|
2008-12-11 12:49:26 +00:00
|
|
|
|
2009-07-20 12:00:06 +00:00
|
|
|
/* ignore settings with no widgets */
|
2008-12-11 12:49:26 +00:00
|
|
|
if (entry->widget_type == G_TYPE_NONE)
|
|
|
|
continue;
|
|
|
|
|
2009-07-20 12:00:06 +00:00
|
|
|
/* radio buttons have several widgets */
|
2008-12-15 13:02:29 +00:00
|
|
|
if (entry->widget_type == GTK_TYPE_RADIO_BUTTON)
|
2008-12-11 12:49:26 +00:00
|
|
|
{
|
2008-12-15 13:02:29 +00:00
|
|
|
handle_radio_buttons(owner, entry->fields, entry->setting, action);
|
|
|
|
continue;
|
2008-12-11 12:49:26 +00:00
|
|
|
}
|
2008-12-15 13:02:29 +00:00
|
|
|
|
|
|
|
widget = get_widget(owner, entry->widget_id);
|
|
|
|
if (!widget)
|
2008-12-11 12:49:26 +00:00
|
|
|
{
|
2008-12-28 16:20:52 +00:00
|
|
|
g_warning("Unknown widget for %s::%s in %s()!", group->name, entry->key_name,
|
|
|
|
G_STRFUNC);
|
2008-12-11 12:49:26 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2008-12-22 16:03:16 +00:00
|
|
|
/* note: can't use switch for GTK_TYPE macros */
|
2008-12-11 12:49:26 +00:00
|
|
|
if (entry->widget_type == GTK_TYPE_TOGGLE_BUTTON)
|
|
|
|
handle_toggle_button(widget, entry->setting, action);
|
2008-12-22 16:03:16 +00:00
|
|
|
else if (entry->widget_type == GTK_TYPE_SPIN_BUTTON)
|
|
|
|
handle_spin_button(widget, entry, action);
|
|
|
|
else if (entry->widget_type == GTK_TYPE_COMBO_BOX)
|
|
|
|
handle_combo_box(widget, entry, action);
|
2008-12-22 17:13:37 +00:00
|
|
|
else if (entry->widget_type == GTK_TYPE_COMBO_BOX_ENTRY)
|
|
|
|
handle_combo_box_entry(widget, entry, action);
|
|
|
|
else if (entry->widget_type == GTK_TYPE_ENTRY)
|
|
|
|
handle_entry(widget, entry, action);
|
2009-07-20 12:00:06 +00:00
|
|
|
else if (entry->widget_type == G_TYPE_PARAM)
|
|
|
|
handle_widget_property(widget, entry, action);
|
2008-12-11 12:49:26 +00:00
|
|
|
else
|
2008-12-28 16:20:52 +00:00
|
|
|
g_warning("Unhandled type for %s::%s in %s()!", group->name, entry->key_name,
|
|
|
|
G_STRFUNC);
|
2008-12-11 12:49:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-31 16:21:28 +00:00
|
|
|
/** Applies Stash settings to widgets, usually called before displaying the widgets.
|
|
|
|
* The @a owner argument depends on which type you use for @ref StashWidgetID.
|
|
|
|
* @param group .
|
|
|
|
* @param owner If non-NULL, used to lookup widgets by name, otherwise
|
|
|
|
* widget pointers are assumed.
|
|
|
|
* @see stash_group_update(). */
|
2010-03-18 17:04:17 +00:00
|
|
|
void stash_group_display(StashGroup *group, GtkWidget *owner)
|
2008-12-11 12:49:26 +00:00
|
|
|
{
|
|
|
|
pref_action(PREF_DISPLAY, group, owner);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-31 16:21:28 +00:00
|
|
|
/** Applies widget values to Stash settings, usually called after displaying the widgets.
|
|
|
|
* The @a owner argument depends on which type you use for @ref StashWidgetID.
|
|
|
|
* @param group .
|
|
|
|
* @param owner If non-NULL, used to lookup widgets by name, otherwise
|
|
|
|
* widget pointers are assumed.
|
|
|
|
* @see stash_group_display(). */
|
2010-03-18 17:04:17 +00:00
|
|
|
void stash_group_update(StashGroup *group, GtkWidget *owner)
|
2008-12-11 12:49:26 +00:00
|
|
|
{
|
|
|
|
pref_action(PREF_UPDATE, group, owner);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-18 17:04:17 +00:00
|
|
|
static StashPref *
|
|
|
|
add_widget_pref(StashGroup *group, GType setting_type, gpointer setting,
|
2008-12-22 13:01:50 +00:00
|
|
|
const gchar *key_name, gpointer default_value,
|
2010-03-18 17:04:17 +00:00
|
|
|
GType widget_type, StashWidgetID widget_id)
|
2008-12-22 13:01:50 +00:00
|
|
|
{
|
2010-03-18 17:04:17 +00:00
|
|
|
StashPref *entry =
|
2008-12-22 13:01:50 +00:00
|
|
|
add_pref(group, setting_type, setting, key_name, default_value);
|
|
|
|
|
|
|
|
entry->widget_type = widget_type;
|
|
|
|
entry->widget_id = widget_id;
|
|
|
|
return entry;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-31 16:21:28 +00:00
|
|
|
/** Adds a @c GtkToggleButton (or @c GtkCheckButton) widget pref.
|
|
|
|
* @param group .
|
|
|
|
* @param setting Address of setting variable.
|
|
|
|
* @param key_name Name for key in a @c GKeyFile.
|
|
|
|
* @param default_value Value to use if the key doesn't exist when loading.
|
|
|
|
* @param widget_id @c GtkWidget pointer or string to lookup widget later.
|
2008-12-15 13:02:29 +00:00
|
|
|
* @see stash_group_add_radio_buttons(). */
|
2010-03-18 17:04:17 +00:00
|
|
|
void stash_group_add_toggle_button(StashGroup *group, gboolean *setting,
|
|
|
|
const gchar *key_name, gboolean default_value, StashWidgetID widget_id)
|
2008-12-11 12:49:26 +00:00
|
|
|
{
|
2008-12-22 13:01:50 +00:00
|
|
|
add_widget_pref(group, G_TYPE_BOOLEAN, setting, key_name, GINT_TO_POINTER(default_value),
|
|
|
|
GTK_TYPE_TOGGLE_BUTTON, widget_id);
|
2008-12-11 12:49:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-31 16:21:28 +00:00
|
|
|
/** Adds a @c GtkRadioButton widget group pref.
|
|
|
|
* @param group .
|
|
|
|
* @param setting Address of setting variable.
|
|
|
|
* @param key_name Name for key in a @c GKeyFile.
|
|
|
|
* @param default_value Value to use if the key doesn't exist when loading.
|
|
|
|
* @param widget_id @c GtkWidget pointer or string to lookup widget later.
|
|
|
|
* @param enum_id Enum value for @a widget_id.
|
|
|
|
* @param ... pairs of @a widget_id, @a enum_id.
|
2008-12-15 13:02:29 +00:00
|
|
|
* Example (using widget lookup strings, but widget pointers can also work):
|
|
|
|
* @code
|
|
|
|
* enum {FOO, BAR};
|
|
|
|
* stash_group_add_radio_buttons(group, &which_one_setting, "which_one", BAR,
|
|
|
|
* "radio_foo", FOO, "radio_bar", BAR, NULL);
|
|
|
|
* @endcode */
|
2010-03-18 17:04:17 +00:00
|
|
|
void stash_group_add_radio_buttons(StashGroup *group, gint *setting,
|
2008-12-15 13:02:29 +00:00
|
|
|
const gchar *key_name, gint default_value,
|
2010-03-18 17:04:17 +00:00
|
|
|
StashWidgetID widget_id, gint enum_id, ...)
|
2008-12-15 13:02:29 +00:00
|
|
|
{
|
2010-03-18 17:04:17 +00:00
|
|
|
StashPref *entry =
|
2008-12-22 13:01:50 +00:00
|
|
|
add_widget_pref(group, G_TYPE_INT, setting, key_name, GINT_TO_POINTER(default_value),
|
|
|
|
GTK_TYPE_RADIO_BUTTON, NULL);
|
2008-12-15 13:02:29 +00:00
|
|
|
va_list args;
|
|
|
|
gsize count = 1;
|
|
|
|
EnumWidget *item, *array;
|
|
|
|
|
|
|
|
/* count pairs of args */
|
|
|
|
va_start(args, enum_id);
|
|
|
|
while (1)
|
|
|
|
{
|
|
|
|
if (!va_arg(args, gpointer))
|
|
|
|
break;
|
2011-05-22 10:00:53 +00:00
|
|
|
va_arg(args, gint);
|
2008-12-15 13:02:29 +00:00
|
|
|
count++;
|
|
|
|
}
|
|
|
|
va_end(args);
|
|
|
|
|
|
|
|
array = g_new0(EnumWidget, count + 1);
|
|
|
|
entry->fields = array;
|
|
|
|
|
|
|
|
va_start(args, enum_id);
|
|
|
|
foreach_c_array(item, array, count)
|
|
|
|
{
|
|
|
|
if (item == array)
|
|
|
|
{
|
|
|
|
/* first element */
|
|
|
|
item->widget_id = widget_id;
|
|
|
|
item->enum_id = enum_id;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
item->widget_id = va_arg(args, gpointer);
|
|
|
|
item->enum_id = va_arg(args, gint);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
va_end(args);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-31 16:21:28 +00:00
|
|
|
/** Adds a @c GtkSpinButton widget pref.
|
|
|
|
* @param group .
|
|
|
|
* @param setting Address of setting variable.
|
|
|
|
* @param key_name Name for key in a @c GKeyFile.
|
|
|
|
* @param default_value Value to use if the key doesn't exist when loading.
|
|
|
|
* @param widget_id @c GtkWidget pointer or string to lookup widget later. */
|
2010-03-18 17:04:17 +00:00
|
|
|
void stash_group_add_spin_button_integer(StashGroup *group, gint *setting,
|
|
|
|
const gchar *key_name, gint default_value, StashWidgetID widget_id)
|
2008-12-22 16:03:16 +00:00
|
|
|
{
|
|
|
|
add_widget_pref(group, G_TYPE_INT, setting, key_name, GINT_TO_POINTER(default_value),
|
|
|
|
GTK_TYPE_SPIN_BUTTON, widget_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-31 16:21:28 +00:00
|
|
|
/** Adds a @c GtkComboBox widget pref.
|
|
|
|
* @param group .
|
|
|
|
* @param setting Address of setting variable.
|
|
|
|
* @param key_name Name for key in a @c GKeyFile.
|
|
|
|
* @param default_value Value to use if the key doesn't exist when loading.
|
|
|
|
* @param widget_id @c GtkWidget pointer or string to lookup widget later.
|
|
|
|
* @see stash_group_add_combo_box_entry(). */
|
2010-03-18 17:04:17 +00:00
|
|
|
void stash_group_add_combo_box(StashGroup *group, gint *setting,
|
|
|
|
const gchar *key_name, gint default_value, StashWidgetID widget_id)
|
2008-12-22 16:03:16 +00:00
|
|
|
{
|
|
|
|
add_widget_pref(group, G_TYPE_INT, setting, key_name, GINT_TO_POINTER(default_value),
|
|
|
|
GTK_TYPE_COMBO_BOX, widget_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-31 16:21:28 +00:00
|
|
|
/** Adds a @c GtkComboBoxEntry widget pref.
|
|
|
|
* @param group .
|
|
|
|
* @param setting Address of setting variable.
|
|
|
|
* @param key_name Name for key in a @c GKeyFile.
|
|
|
|
* @param default_value Value to use if the key doesn't exist when loading.
|
|
|
|
* @param widget_id @c GtkWidget pointer or string to lookup widget later. */
|
2008-12-22 18:18:14 +00:00
|
|
|
/* We could maybe also have something like stash_group_add_combo_box_entry_with_menu()
|
|
|
|
* for the history list - or should that be stored as a separate setting? */
|
2010-03-18 17:04:17 +00:00
|
|
|
void stash_group_add_combo_box_entry(StashGroup *group, gchar **setting,
|
|
|
|
const gchar *key_name, const gchar *default_value, StashWidgetID widget_id)
|
2008-12-22 17:13:37 +00:00
|
|
|
{
|
|
|
|
add_widget_pref(group, G_TYPE_STRING, setting, key_name, (gpointer)default_value,
|
|
|
|
GTK_TYPE_COMBO_BOX_ENTRY, widget_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-31 16:21:28 +00:00
|
|
|
/** Adds a @c GtkEntry widget pref.
|
|
|
|
* @param group .
|
|
|
|
* @param setting Address of setting variable.
|
|
|
|
* @param key_name Name for key in a @c GKeyFile.
|
|
|
|
* @param default_value Value to use if the key doesn't exist when loading.
|
|
|
|
* @param widget_id @c GtkWidget pointer or string to lookup widget later. */
|
2010-03-18 17:04:17 +00:00
|
|
|
void stash_group_add_entry(StashGroup *group, gchar **setting,
|
|
|
|
const gchar *key_name, const gchar *default_value, StashWidgetID widget_id)
|
2008-12-22 17:13:37 +00:00
|
|
|
{
|
|
|
|
add_widget_pref(group, G_TYPE_STRING, setting, key_name, (gpointer)default_value,
|
|
|
|
GTK_TYPE_ENTRY, widget_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-07-20 12:00:06 +00:00
|
|
|
static GType object_get_property_type(GObject *object, const gchar *property_name)
|
|
|
|
{
|
|
|
|
GObjectClass *klass = G_OBJECT_GET_CLASS(object);
|
|
|
|
GParamSpec *ps;
|
|
|
|
|
|
|
|
ps = g_object_class_find_property(klass, property_name);
|
|
|
|
return ps->value_type;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-03-31 16:21:28 +00:00
|
|
|
/** Adds a widget's read/write property to the stash group.
|
|
|
|
* The property will be set when calling
|
2009-07-20 12:00:06 +00:00
|
|
|
* stash_group_display(), and read when calling stash_group_update().
|
2010-03-31 16:21:28 +00:00
|
|
|
* @param group .
|
|
|
|
* @param setting Address of e.g. an integer if using an integer property.
|
|
|
|
* @param key_name Name for key in a @c GKeyFile.
|
|
|
|
* @param default_value Value to use if the key doesn't exist when loading.
|
|
|
|
* Should be cast into a pointer e.g. with @c GINT_TO_POINTER().
|
|
|
|
* @param widget_id @c GtkWidget pointer or string to lookup widget later.
|
|
|
|
* @param property_name .
|
|
|
|
* @param type can be @c 0 if passing a @c GtkWidget as the @a widget_id argument to look it up from the
|
|
|
|
* @c GObject data.
|
2009-07-20 12:00:06 +00:00
|
|
|
* @warning Currently only string GValue properties will be freed before setting; patch for
|
2010-03-31 16:21:28 +00:00
|
|
|
* other types - see @c handle_widget_property(). */
|
2010-03-18 17:04:17 +00:00
|
|
|
void stash_group_add_widget_property(StashGroup *group, gpointer setting,
|
|
|
|
const gchar *key_name, gpointer default_value, StashWidgetID widget_id,
|
2009-07-20 12:00:06 +00:00
|
|
|
const gchar *property_name, GType type)
|
|
|
|
{
|
|
|
|
if (!type)
|
|
|
|
type = object_get_property_type(G_OBJECT(widget_id), property_name);
|
|
|
|
|
|
|
|
add_widget_pref(group, type, setting, key_name, default_value,
|
|
|
|
G_TYPE_PARAM, widget_id)->fields = (gchar*)property_name;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-07-28 17:59:22 +00:00
|
|
|
enum
|
|
|
|
{
|
|
|
|
STASH_TREE_NAME,
|
|
|
|
STASH_TREE_VALUE,
|
|
|
|
STASH_TREE_COUNT
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
struct StashTreeValue
|
|
|
|
{
|
|
|
|
GType setting_type;
|
|
|
|
gpointer setting;
|
|
|
|
const gchar *key_name;
|
|
|
|
const gchar *group_name;
|
|
|
|
};
|
|
|
|
|
|
|
|
typedef struct StashTreeValue StashTreeValue;
|
|
|
|
|
|
|
|
|
|
|
|
static void stash_tree_renderer_set_data(GtkCellLayout *cell_layout, GtkCellRenderer *cell,
|
|
|
|
GtkTreeModel *model, GtkTreeIter *iter, gpointer user_data)
|
|
|
|
{
|
|
|
|
GType cell_type = GPOINTER_TO_SIZE(user_data);
|
|
|
|
StashTreeValue *value;
|
|
|
|
gboolean matches_type;
|
|
|
|
|
|
|
|
gtk_tree_model_get(model, iter, STASH_TREE_VALUE, &value, -1);
|
|
|
|
matches_type = value->setting_type == cell_type;
|
|
|
|
g_object_set(cell, "visible", matches_type, "sensitive", matches_type,
|
|
|
|
cell_type == G_TYPE_BOOLEAN ? "activatable" : "editable", matches_type, NULL);
|
|
|
|
|
|
|
|
if (matches_type)
|
|
|
|
{
|
|
|
|
switch (value->setting_type)
|
|
|
|
{
|
|
|
|
case G_TYPE_BOOLEAN:
|
|
|
|
g_object_set(cell, "active", GPOINTER_TO_INT(value->setting), NULL);
|
|
|
|
break;
|
|
|
|
case G_TYPE_INT:
|
|
|
|
{
|
|
|
|
gchar *text = g_strdup_printf("%d", GPOINTER_TO_INT(value->setting));
|
|
|
|
g_object_set(cell, "text", text, NULL);
|
|
|
|
g_free(text);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case G_TYPE_STRING:
|
|
|
|
g_object_set(cell, "text", (gchararray) value->setting, NULL);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void stash_tree_renderer_edited(gchar *path_str, gchar *new_text, GtkTreeModel *model)
|
|
|
|
{
|
|
|
|
GtkTreePath *path;
|
|
|
|
GtkTreeIter iter;
|
|
|
|
StashTreeValue *value;
|
|
|
|
|
|
|
|
path = gtk_tree_path_new_from_string(path_str);
|
|
|
|
gtk_tree_model_get_iter(model, &iter, path);
|
|
|
|
gtk_tree_model_get(model, &iter, STASH_TREE_VALUE, &value, -1);
|
|
|
|
|
|
|
|
switch (value->setting_type)
|
|
|
|
{
|
|
|
|
case G_TYPE_BOOLEAN:
|
|
|
|
value->setting = GINT_TO_POINTER(!GPOINTER_TO_INT(value->setting));
|
|
|
|
break;
|
|
|
|
case G_TYPE_INT:
|
|
|
|
value->setting = GINT_TO_POINTER(atoi(new_text));
|
|
|
|
break;
|
|
|
|
case G_TYPE_STRING:
|
|
|
|
g_free(value->setting);
|
|
|
|
value->setting = g_strdup(new_text);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
gtk_tree_model_row_changed(model, path, &iter);
|
|
|
|
gtk_tree_path_free(path);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void stash_tree_boolean_toggled(GtkCellRendererToggle *cell, gchar *path_str,
|
|
|
|
GtkTreeModel *model)
|
|
|
|
{
|
|
|
|
stash_tree_renderer_edited(path_str, NULL, model);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void stash_tree_string_edited(GtkCellRenderer *cell, gchar *path_str, gchar *new_text,
|
|
|
|
GtkTreeModel *model)
|
|
|
|
{
|
|
|
|
stash_tree_renderer_edited(path_str, new_text, model);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static gboolean stash_tree_discard_value(GtkTreeModel *model, GtkTreePath *path,
|
|
|
|
GtkTreeIter *iter, gpointer user_data)
|
|
|
|
{
|
|
|
|
StashTreeValue *value;
|
|
|
|
|
|
|
|
gtk_tree_model_get(model, iter, STASH_TREE_VALUE, &value, -1);
|
|
|
|
if (value->setting_type == G_TYPE_STRING)
|
|
|
|
g_free(value->setting);
|
|
|
|
g_free(value);
|
|
|
|
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void stash_tree_destroy_cb(GtkWidget *widget, gpointer user_data)
|
|
|
|
{
|
|
|
|
GtkTreeModel *model = gtk_tree_view_get_model(GTK_TREE_VIEW(widget));
|
|
|
|
gtk_tree_model_foreach(model, stash_tree_discard_value, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-09-21 15:50:36 +00:00
|
|
|
typedef void (*stash_foreach_pref_func)(StashGroup *group, StashPref *entry, gpointer container,
|
|
|
|
PrefAction action);
|
2011-07-28 17:59:22 +00:00
|
|
|
|
|
|
|
static void stash_foreach_various_pref(GPtrArray *group_array, stash_foreach_pref_func pref_func,
|
2011-09-21 15:50:36 +00:00
|
|
|
gpointer container, PrefAction action)
|
2011-07-28 17:59:22 +00:00
|
|
|
{
|
|
|
|
StashGroup *group;
|
|
|
|
guint i;
|
|
|
|
StashPref *entry;
|
|
|
|
|
|
|
|
foreach_ptr_array(group, i, group_array)
|
|
|
|
{
|
|
|
|
if (group->various)
|
|
|
|
{
|
|
|
|
foreach_array(StashPref, entry, group->entries)
|
2011-09-21 15:50:36 +00:00
|
|
|
pref_func(group, entry, container, action);
|
2011-07-28 17:59:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-09-21 15:50:36 +00:00
|
|
|
static void stash_tree_append_pref(StashGroup *group, StashPref *entry, GtkListStore *store,
|
|
|
|
PrefAction action)
|
2011-07-28 17:59:22 +00:00
|
|
|
{
|
2011-08-06 18:33:27 +00:00
|
|
|
GtkTreeIter iter;
|
|
|
|
StashTreeValue *value;
|
2011-07-28 17:59:22 +00:00
|
|
|
|
2011-08-06 18:33:27 +00:00
|
|
|
value = g_new(StashTreeValue, 1);
|
2011-07-28 17:59:22 +00:00
|
|
|
|
2011-08-06 18:33:27 +00:00
|
|
|
value->setting_type = entry->setting_type;
|
2011-09-21 15:50:36 +00:00
|
|
|
value->setting = NULL;
|
2011-08-06 18:33:27 +00:00
|
|
|
value->key_name = entry->key_name;
|
|
|
|
value->group_name = group->name;
|
2011-07-28 17:59:22 +00:00
|
|
|
|
2011-08-06 18:33:27 +00:00
|
|
|
gtk_list_store_append(store, &iter);
|
|
|
|
gtk_list_store_set(store, &iter, STASH_TREE_NAME, value->key_name,
|
|
|
|
STASH_TREE_VALUE, value, -1);
|
2011-07-28 17:59:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Setups a simple editor for stash preferences based on the widget arguments.
|
|
|
|
* group_array - Array of groups which's settings will be edited.
|
|
|
|
* tree - GtkTreeView in which to edit the preferences. Must be empty. */
|
|
|
|
void stash_tree_setup(GPtrArray *group_array, GtkTreeView *tree)
|
|
|
|
{
|
|
|
|
GtkListStore *store;
|
|
|
|
GtkTreeModel *model;
|
|
|
|
GtkCellRenderer *cell;
|
|
|
|
GtkTreeViewColumn *column;
|
|
|
|
GtkObject *adjustment;
|
|
|
|
|
|
|
|
store = gtk_list_store_new(STASH_TREE_COUNT, G_TYPE_STRING, G_TYPE_POINTER);
|
|
|
|
stash_foreach_various_pref(group_array,
|
2011-09-21 15:50:36 +00:00
|
|
|
(stash_foreach_pref_func) stash_tree_append_pref, store, PREF_DISPLAY);
|
2011-07-28 17:59:22 +00:00
|
|
|
gtk_tree_sortable_set_sort_column_id(GTK_TREE_SORTABLE(store), STASH_TREE_NAME,
|
|
|
|
GTK_SORT_ASCENDING);
|
|
|
|
|
|
|
|
model = GTK_TREE_MODEL(store);
|
|
|
|
gtk_tree_view_set_model(tree, model);
|
|
|
|
g_object_unref(G_OBJECT(store));
|
|
|
|
g_signal_connect(tree, "destroy", G_CALLBACK(stash_tree_destroy_cb), NULL);
|
|
|
|
|
|
|
|
cell = gtk_cell_renderer_text_new();
|
|
|
|
column = gtk_tree_view_column_new_with_attributes(_("Name"), cell, "text",
|
|
|
|
STASH_TREE_NAME, NULL);
|
|
|
|
gtk_tree_view_column_set_sort_column_id(column, STASH_TREE_NAME);
|
|
|
|
gtk_tree_view_column_set_sort_indicator(column, TRUE);
|
|
|
|
gtk_tree_view_append_column(tree, column);
|
|
|
|
|
|
|
|
column = gtk_tree_view_column_new();
|
|
|
|
gtk_tree_view_column_set_title(column, _("Value"));
|
|
|
|
gtk_tree_view_append_column(tree, column);
|
|
|
|
/* boolean renderer */
|
|
|
|
cell = gtk_cell_renderer_toggle_new();
|
|
|
|
g_signal_connect(cell, "toggled", G_CALLBACK(stash_tree_boolean_toggled), model);
|
|
|
|
gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(column), cell, FALSE);
|
|
|
|
gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(column), cell,
|
|
|
|
stash_tree_renderer_set_data, GSIZE_TO_POINTER(G_TYPE_BOOLEAN), NULL);
|
|
|
|
/* string renderer */
|
|
|
|
cell = gtk_cell_renderer_text_new();
|
|
|
|
g_object_set(cell, "ellipsize", PANGO_ELLIPSIZE_END, NULL);
|
|
|
|
g_signal_connect(cell, "edited", G_CALLBACK(stash_tree_string_edited), model);
|
|
|
|
gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(column), cell, TRUE);
|
|
|
|
gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(column), cell,
|
|
|
|
stash_tree_renderer_set_data, GSIZE_TO_POINTER(G_TYPE_STRING), NULL);
|
|
|
|
/* integer renderer */
|
|
|
|
cell = gtk_cell_renderer_spin_new();
|
|
|
|
adjustment = gtk_adjustment_new(0, G_MININT, G_MAXINT, 1, 10, 0);
|
|
|
|
g_object_set(cell, "adjustment", adjustment, NULL);
|
|
|
|
g_signal_connect(cell, "edited", G_CALLBACK(stash_tree_string_edited), model);
|
|
|
|
gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(column), cell, FALSE);
|
|
|
|
gtk_cell_layout_set_cell_data_func(GTK_CELL_LAYOUT(column), cell,
|
|
|
|
stash_tree_renderer_set_data, GSIZE_TO_POINTER(G_TYPE_INT), NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-09-21 15:50:36 +00:00
|
|
|
static void stash_tree_display_pref(StashTreeValue *value, StashPref *entry)
|
|
|
|
{
|
|
|
|
switch (entry->setting_type)
|
|
|
|
{
|
|
|
|
case G_TYPE_BOOLEAN:
|
|
|
|
value->setting = GINT_TO_POINTER(*(gboolean *) entry->setting);
|
|
|
|
break;
|
|
|
|
case G_TYPE_INT:
|
|
|
|
value->setting = GINT_TO_POINTER(*(gint *) entry->setting);
|
|
|
|
break;
|
|
|
|
case G_TYPE_STRING:
|
|
|
|
{
|
|
|
|
g_free(value->setting);
|
|
|
|
value->setting = g_strdup(*(gchararray *) entry->setting);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
g_warning("Unhandled type for %s::%s in %s()!", value->group_name,
|
|
|
|
entry->key_name, G_STRFUNC);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void stash_tree_update_pref(StashTreeValue *value, StashPref *entry)
|
|
|
|
{
|
|
|
|
gpointer *setting = value->setting;
|
|
|
|
|
|
|
|
switch (entry->setting_type)
|
|
|
|
{
|
|
|
|
case G_TYPE_BOOLEAN:
|
|
|
|
*(gboolean *) entry->setting = GPOINTER_TO_INT(setting);
|
|
|
|
break;
|
|
|
|
case G_TYPE_INT:
|
|
|
|
*(gint *) entry->setting = GPOINTER_TO_INT(setting);
|
|
|
|
break;
|
|
|
|
case G_TYPE_STRING:
|
|
|
|
{
|
|
|
|
gchararray *text = entry->setting;
|
|
|
|
g_free(*text);
|
|
|
|
*text = g_strdup((gchararray) setting);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
g_warning("Unhandled type for %s::%s in %s()!", value->group_name,
|
|
|
|
value->key_name, G_STRFUNC);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-07-28 17:59:22 +00:00
|
|
|
/* These functions can handle about 200 settings on a 1GHz x86 CPU in ~0.06 seconds.
|
|
|
|
* For 250+ settings, you'd better write something more efficient. */
|
2011-09-21 15:50:36 +00:00
|
|
|
static void stash_tree_handle_pref(StashGroup *group, StashPref *entry, GtkTreeModel *model,
|
|
|
|
PrefAction action)
|
2011-07-28 17:59:22 +00:00
|
|
|
{
|
|
|
|
GtkTreeIter iter;
|
|
|
|
gboolean valid = gtk_tree_model_get_iter_first(model, &iter);
|
|
|
|
StashTreeValue *value;
|
|
|
|
|
|
|
|
while (valid)
|
|
|
|
{
|
|
|
|
gtk_tree_model_get(model, &iter, STASH_TREE_VALUE, &value, -1);
|
|
|
|
|
|
|
|
if (strcmp(group->name, value->group_name) == 0 &&
|
|
|
|
strcmp(entry->key_name, value->key_name) == 0)
|
|
|
|
{
|
2011-09-21 15:50:36 +00:00
|
|
|
switch (action)
|
2011-07-28 17:59:22 +00:00
|
|
|
{
|
2011-09-21 15:50:36 +00:00
|
|
|
case PREF_DISPLAY:
|
|
|
|
stash_tree_display_pref(value, entry);
|
2011-07-28 17:59:22 +00:00
|
|
|
break;
|
2011-09-21 15:50:36 +00:00
|
|
|
case PREF_UPDATE:
|
|
|
|
stash_tree_update_pref(value, entry);
|
2011-07-28 17:59:22 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
valid = gtk_tree_model_iter_next(model, &iter);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-09-21 15:50:36 +00:00
|
|
|
void stash_tree_display(GPtrArray *group_array, GtkTreeView *tree)
|
|
|
|
{
|
|
|
|
stash_foreach_various_pref(group_array, (stash_foreach_pref_func) stash_tree_handle_pref,
|
|
|
|
gtk_tree_view_get_model(tree), PREF_DISPLAY);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2011-07-28 17:59:22 +00:00
|
|
|
void stash_tree_update(GPtrArray *group_array, GtkTreeView *tree)
|
|
|
|
{
|
2011-09-21 15:50:36 +00:00
|
|
|
stash_foreach_various_pref(group_array, (stash_foreach_pref_func) stash_tree_handle_pref,
|
|
|
|
gtk_tree_view_get_model(tree), PREF_UPDATE);
|
2011-07-28 17:59:22 +00:00
|
|
|
}
|