medit/moo/mooedit/mooeditconfig.c

868 lines
22 KiB
C

/*
* mooeditconfig.c
*
* Copyright (C) 2004-2006 by Yevgen Muntyan <muntyan@math.tamu.edu>
*
* 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.
*
* See COPYING file that comes with this distribution.
*/
#define MOOEDIT_COMPILATION
#include "mooedit/mooeditconfig.h"
#include "mooedit/moolang.h"
#include "mooedit/mooeditprefs.h"
#include "mooutils/mooutils-gobject.h"
#include "mooutils/mooprefs.h"
#include <gobject/gvaluecollector.h>
#include <string.h>
#define VALUE(c_,i_) (&(c_)->priv->values[i_])
#define GVALUE(c_,i_) (&VALUE(c_,i_)->gval)
typedef struct _Variable Variable;
typedef struct _VarArray VarArray;
typedef struct _Value Value;
struct _MooEditConfigPrivate {
Value *values;
guint n_values;
guint n_values_allocd__;
};
struct _Value {
GValue gval;
guint source : 16;
};
struct _Variable {
GParamSpec *pspec;
};
struct _VarArray {
Variable *data;
guint len;
};
static MooEditConfig *global;
static GSList *instances;
static VarArray *vars;
static GQuark prop_id_quark;
static GHashTable *aliases;
static void moo_edit_config_finalize (GObject *object);
static void moo_edit_config_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec);
static void moo_edit_config_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec);
static void global_changed (GObject *object,
GParamSpec *pspec);
/* MOO_TYPE_EDIT_CONFIG */
G_DEFINE_TYPE (MooEditConfig, moo_edit_config, G_TYPE_OBJECT)
static void
moo_edit_config_class_init (MooEditConfigClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->set_property = moo_edit_config_set_property;
gobject_class->get_property = moo_edit_config_get_property;
gobject_class->finalize = moo_edit_config_finalize;
g_type_class_add_private (klass, sizeof (MooEditConfigPrivate));
prop_id_quark = g_quark_from_static_string ("MooEditConfigPropId");
aliases = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
vars = (VarArray*) g_array_new (FALSE, TRUE, sizeof (Variable));
g_array_set_size ((GArray*) vars, 1);
}
static void
moo_edit_config_init (MooEditConfig *config)
{
guint i;
config->priv = G_TYPE_INSTANCE_GET_PRIVATE (config, MOO_TYPE_EDIT_CONFIG, MooEditConfigPrivate);
config->priv->values = g_new0 (Value, vars->len);
config->priv->n_values = config->priv->n_values_allocd__ = vars->len;
if (global)
{
instances = g_slist_prepend (instances, config);
for (i = 1; i < vars->len; ++i)
{
g_value_init (GVALUE (config, i), G_VALUE_TYPE (GVALUE (global, i)));
g_value_copy (GVALUE (global, i), GVALUE (config, i));
VALUE(config, i)->source = VALUE(global, i)->source;
}
}
}
static void
moo_edit_config_finalize (GObject *object)
{
guint i;
MooEditConfig *config = MOO_EDIT_CONFIG (object);
instances = g_slist_remove (instances, config);
for (i = 1; i < config->priv->n_values; ++i)
g_value_unset (GVALUE (config, i));
g_free (config->priv->values);
G_OBJECT_CLASS (moo_edit_config_parent_class)->finalize (object);
}
static void
global_init (void)
{
if (!global)
{
guint i;
global = g_object_new (MOO_TYPE_EDIT_CONFIG, NULL);
for (i = 1; i < vars->len; ++i)
{
g_value_init (GVALUE (global, i), G_PARAM_SPEC_VALUE_TYPE (vars->data[i].pspec));
g_param_value_set_default (vars->data[i].pspec, GVALUE (global, i));
VALUE(global, i)->source = MOO_EDIT_CONFIG_SOURCE_AUTO;
}
g_signal_connect (global, "notify",
G_CALLBACK (global_changed), NULL);
/* XXX read preferences here */
}
}
MooEditConfig *
moo_edit_config_new (void)
{
global_init ();
return g_object_new (MOO_TYPE_EDIT_CONFIG, NULL);
}
static void
moo_edit_config_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
MooEditConfig *config = MOO_EDIT_CONFIG (object);
if (prop_id == 0 || prop_id >= vars->len)
{
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
return;
}
g_assert (config->priv->n_values == vars->len);
g_value_copy (value, GVALUE (config, prop_id));
VALUE (config, prop_id)->source = MOO_EDIT_CONFIG_SOURCE_USER;
g_object_notify (object, pspec->name);
}
static void
moo_edit_config_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
MooEditConfig *config = MOO_EDIT_CONFIG (object);
if (prop_id == 0 || prop_id >= vars->len)
{
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
return;
}
g_assert (config->priv->n_values == vars->len);
g_value_copy (GVALUE (config, prop_id), value);
}
static void
object_notify (MooEditConfig *config,
guint prop_id)
{
g_object_notify (G_OBJECT (config),
vars->data[prop_id].pspec->name);
}
static void
update_prop_from_global (MooEditConfig *config,
gpointer data)
{
guint prop_id = GPOINTER_TO_UINT (data);
g_assert (prop_id > 0 && prop_id < vars->len);
g_assert (prop_id <= config->priv->n_values);
if (prop_id == config->priv->n_values)
{
if (config->priv->n_values_allocd__ == prop_id)
{
Value *tmp;
config->priv->n_values_allocd__ = MAX (config->priv->n_values_allocd__ * 1.2,
config->priv->n_values_allocd__ + 2);
tmp = g_new0 (Value, config->priv->n_values_allocd__);
memcpy (tmp, config->priv->values, config->priv->n_values * sizeof (Value));
g_free (config->priv->values);
config->priv->values = tmp;
}
config->priv->n_values += 1;
g_value_init (GVALUE (config, prop_id), G_VALUE_TYPE (GVALUE (global, prop_id)));
VALUE(config, prop_id)->source = VALUE(global, prop_id)->source;
}
if (VALUE(global, prop_id)->source <= VALUE(config, prop_id)->source)
{
g_value_copy (GVALUE(global, prop_id), GVALUE(config, prop_id));
VALUE(config, prop_id)->source = VALUE(global, prop_id)->source;
object_notify (config, prop_id);
}
}
static void
global_changed (G_GNUC_UNUSED GObject *object,
GParamSpec *pspec)
{
gpointer prop_id;
g_assert (MOO_EDIT_CONFIG (object) == global);
prop_id = g_param_spec_get_qdata (pspec, prop_id_quark);
g_return_if_fail (prop_id != NULL);
g_slist_foreach (instances, (GFunc) g_object_freeze_notify, NULL);
g_slist_foreach (instances, (GFunc) update_prop_from_global, prop_id);
g_slist_foreach (instances, (GFunc) g_object_thaw_notify, NULL);
/* XXX write to preferences here */
}
static void
global_add_prop (GParamSpec *pspec,
guint prop_id)
{
g_assert (global->priv->n_values == prop_id);
if (global->priv->n_values_allocd__ == prop_id)
{
Value *tmp;
global->priv->n_values_allocd__ = MAX (global->priv->n_values_allocd__ * 1.2,
global->priv->n_values_allocd__ + 2);
tmp = g_new0 (Value, global->priv->n_values_allocd__);
memcpy (tmp, global->priv->values, global->priv->n_values * sizeof (Value));
g_free (global->priv->values);
global->priv->values = tmp;
}
global->priv->n_values += 1;
g_value_init (GVALUE (global, prop_id), G_PARAM_SPEC_VALUE_TYPE (pspec));
g_param_value_set_default (pspec, GVALUE (global, prop_id));
VALUE(global, prop_id)->source = MOO_EDIT_CONFIG_SOURCE_AUTO;
/* XXX prefs */
}
guint
moo_edit_config_install_setting (GParamSpec *pspec)
{
GObjectClass *klass;
guint prop_id;
g_return_val_if_fail (G_IS_PARAM_SPEC (pspec), 0);
global_init ();
klass = g_type_class_ref (MOO_TYPE_EDIT_CONFIG);
if (moo_edit_config_lookup_spec (pspec->name, NULL, TRUE))
{
g_warning ("%s: setting named '%s' already exists",
G_STRLOC, pspec->name);
return 0;
}
prop_id = vars->len;
g_array_set_size ((GArray*) vars, prop_id + 1);
vars->data[prop_id].pspec = pspec;
g_param_spec_set_qdata (pspec, prop_id_quark, GUINT_TO_POINTER (prop_id));
g_object_class_install_property (klass, prop_id, pspec);
global_add_prop (pspec, prop_id);
g_slist_foreach (instances, (GFunc) g_object_freeze_notify, NULL);
g_slist_foreach (instances, (GFunc) update_prop_from_global, GUINT_TO_POINTER (prop_id));
g_slist_foreach (instances, (GFunc) g_object_thaw_notify, NULL);
g_type_class_unref (klass);
return prop_id;
}
GParamSpec *
moo_edit_config_get_spec (guint id)
{
g_return_val_if_fail (id > 0 && id < vars->len, NULL);
return vars->data[id].pspec;
}
GParamSpec *
moo_edit_config_lookup_spec (const char *name,
guint *id,
gboolean try_alias)
{
GParamSpec *pspec;
GObjectClass *klass;
const char *real_name;
char *norm_name;
g_return_val_if_fail (name != NULL, NULL);
global_init ();
klass = g_type_class_ref (MOO_TYPE_EDIT_CONFIG);
norm_name = g_strdelimit (g_strdup (name), "_", '-');
real_name = norm_name;
if (try_alias)
real_name = g_hash_table_lookup (aliases, norm_name);
if (!real_name)
real_name = norm_name;
pspec = g_object_class_find_property (klass, real_name);
if (id)
{
if (pspec)
*id = GPOINTER_TO_UINT (g_param_spec_get_qdata (pspec, prop_id_quark));
else
*id = 0;
}
g_type_class_unref (klass);
g_free (norm_name);
return pspec;
}
static const GValue *
config_get_value (MooEditConfig *config,
const char *setting)
{
guint prop_id;
g_return_val_if_fail (MOO_IS_EDIT_CONFIG (config), NULL);
g_return_val_if_fail (setting != NULL, NULL);
moo_edit_config_lookup_spec (setting, &prop_id, FALSE);
g_return_val_if_fail (prop_id != 0, NULL);
return GVALUE (config, prop_id);
}
const char *
moo_edit_config_get_string (MooEditConfig *config,
const char *setting)
{
const GValue *val;
g_return_val_if_fail (MOO_IS_EDIT_CONFIG (config), NULL);
g_return_val_if_fail (setting != NULL, NULL);
val = config_get_value (config, setting);
g_return_val_if_fail (val && G_VALUE_HOLDS_STRING (val), NULL);
return g_value_get_string (val);
}
guint
moo_edit_config_get_uint (MooEditConfig *config,
const char *setting)
{
const GValue *val;
g_return_val_if_fail (MOO_IS_EDIT_CONFIG (config), 0);
g_return_val_if_fail (setting != NULL, 0);
val = config_get_value (config, setting);
g_return_val_if_fail (val && G_VALUE_HOLDS_UINT (val), 0);
return g_value_get_uint (val);
}
gboolean
moo_edit_config_get_bool (MooEditConfig *config,
const char *setting)
{
const GValue *val;
g_return_val_if_fail (MOO_IS_EDIT_CONFIG (config), FALSE);
g_return_val_if_fail (setting != NULL, FALSE);
val = config_get_value (config, setting);
g_return_val_if_fail (val && G_VALUE_HOLDS_BOOLEAN (val), FALSE);
return g_value_get_boolean (val);
}
static void
moo_edit_config_set_value (MooEditConfig *config,
guint prop_id,
MooEditConfigSource source,
const GValue *value)
{
Value *val;
g_return_if_fail (MOO_IS_EDIT_CONFIG (config));
g_return_if_fail (prop_id > 0 && prop_id < vars->len);
g_return_if_fail (G_IS_VALUE (value));
g_assert (prop_id < config->priv->n_values);
val = VALUE (config, prop_id);
if (val->source >= source &&
(val->source != source ||
g_param_values_cmp (vars->data[prop_id].pspec, &val->gval, value)))
{
g_value_copy (value, &val->gval);
val->source = source;
object_notify (config, prop_id);
}
}
static void
moo_edit_config_set_valist (MooEditConfig *config,
MooEditConfigSource source,
const char *first_setting,
va_list var_args)
{
const gchar *name;
g_return_if_fail (MOO_IS_EDIT_CONFIG (config));
g_return_if_fail (first_setting != NULL);
g_object_ref (config);
g_object_freeze_notify (G_OBJECT (config));
name = first_setting;
while (name)
{
GValue value;
GParamSpec *pspec;
gchar *error = NULL;
guint prop_id;
value.g_type = 0;
pspec = moo_edit_config_lookup_spec (name, &prop_id, FALSE);
if (!pspec)
{
g_warning ("%s: there is no setting named `%s'",
G_STRLOC, name);
break;
}
g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec));
G_VALUE_COLLECT (&value, var_args, 0, &error);
if (error)
{
g_warning ("%s: %s", G_STRLOC, error);
g_free (error);
g_value_unset (&value);
break;
}
moo_edit_config_set_value (config, prop_id, source, &value);
g_value_unset (&value);
name = va_arg (var_args, gchar*);
}
g_object_thaw_notify (G_OBJECT (config));
g_object_unref (config);
}
void
moo_edit_config_set (MooEditConfig *config,
MooEditConfigSource source,
const char *first_setting,
...)
{
va_list var_args;
g_return_if_fail (MOO_IS_EDIT_CONFIG (config));
g_return_if_fail (first_setting != NULL);
va_start (var_args, first_setting);
moo_edit_config_set_valist (config, source, first_setting, var_args);
va_end (var_args);
}
void
moo_edit_config_get (MooEditConfig *config,
const char *first_setting,
...)
{
va_list var_args;
g_return_if_fail (MOO_IS_EDIT_CONFIG (config));
va_start (var_args, first_setting);
g_object_get_valist (G_OBJECT (config), first_setting, var_args);
va_end (var_args);
}
void
moo_edit_config_set_global (MooEditConfigSource source,
const char *first_setting,
...)
{
va_list var_args;
g_return_if_fail (first_setting != NULL);
global_init ();
va_start (var_args, first_setting);
moo_edit_config_set_valist (global, source, first_setting, var_args);
va_end (var_args);
}
#if 0
void
moo_edit_config_get_global (const char *first_setting,
...)
{
va_list var_args;
global_init ();
va_start (var_args, first_setting);
g_object_get_valist (G_OBJECT (global), first_setting, var_args);
va_end (var_args);
}
#endif
void
moo_edit_config_unset_by_source (MooEditConfig *config,
MooEditConfigSource source)
{
guint i;
g_return_if_fail (MOO_IS_EDIT_CONFIG (config));
g_object_ref (config);
g_object_freeze_notify (G_OBJECT (config));
for (i = 1; i < vars->len; ++i)
{
Value *val = VALUE (config, i);
if (val->source < source)
continue;
g_param_value_set_default (vars->data[i].pspec, &val->gval);
val->source = MOO_EDIT_CONFIG_SOURCE_AUTO;
if (config != global)
update_prop_from_global (config, GUINT_TO_POINTER (i));
object_notify (config, i);
}
g_object_thaw_notify (G_OBJECT (config));
g_object_unref (config);
}
#if 0
int
moo_edit_config_get_source (MooEditConfig *config,
const char *setting)
{
guint id;
g_return_val_if_fail (MOO_IS_EDIT_CONFIG (config), 0);
g_return_val_if_fail (setting != 0, 0);
if (!moo_edit_config_lookup_spec (setting, &id, TRUE))
g_return_val_if_reached (0);
return VALUE(config, id)->source;
}
void
moo_edit_config_compose (MooEditConfig *target,
MooEditConfig *src)
{
guint i;
g_return_if_fail (MOO_IS_EDIT_CONFIG (target));
g_return_if_fail (MOO_IS_EDIT_CONFIG (src));
g_object_ref (target);
g_object_freeze_notify (G_OBJECT (target));
for (i = 1; i < vars->len; ++i)
{
Value *old = VALUE (target, i);
Value *new = VALUE (src, i);
if (old->source < new->source)
continue;
g_value_copy (&new->gval, &old->gval);
old->source = new->source;
object_notify (target, i);
}
g_object_thaw_notify (G_OBJECT (target));
g_object_unref (target);
}
#endif
guint
moo_edit_config_get_setting_id (GParamSpec *pspec)
{
g_return_val_if_fail (G_IS_PARAM_SPEC (pspec), 0);
return GPOINTER_TO_UINT (g_param_spec_get_qdata (pspec, prop_id_quark));
}
gboolean
moo_edit_config_parse_bool (const char *string,
gboolean *value)
{
g_return_val_if_fail (string != NULL, FALSE);
if (!g_ascii_strcasecmp (string, "true") ||
!g_ascii_strcasecmp (string, "on") ||
!g_ascii_strcasecmp (string, "yes") ||
!g_ascii_strcasecmp (string, "1"))
{
if (value)
*value = TRUE;
}
else if (!g_ascii_strcasecmp (string, "false") ||
!g_ascii_strcasecmp (string, "off") ||
!g_ascii_strcasecmp (string, "no") ||
!g_ascii_strcasecmp (string, "0"))
{
if (value)
*value = FALSE;
}
else
{
return FALSE;
}
return TRUE;
}
static gboolean
parse_boolean (const char *value,
GValue *gval)
{
gboolean bval;
if (moo_edit_config_parse_bool (value, &bval))
{
g_value_set_boolean (gval, bval);
return TRUE;
}
else
{
return FALSE;
}
}
void
moo_edit_config_parse_one (MooEditConfig *config,
const char *name,
const char *value,
MooEditConfigSource source)
{
GValue gval;
gboolean result = FALSE;
GParamSpec *pspec;
guint prop_id;
g_return_if_fail (MOO_IS_EDIT_CONFIG (config));
g_return_if_fail (name != NULL);
g_return_if_fail (value != NULL);
pspec = moo_edit_config_lookup_spec (name, &prop_id, TRUE);
if (!pspec)
{
g_warning ("%s: no setting named '%s'", G_STRLOC, name);
return;
}
gval.g_type = 0;
g_value_init (&gval, G_PARAM_SPEC_VALUE_TYPE (pspec));
if (G_PARAM_SPEC_VALUE_TYPE (pspec) == G_TYPE_BOOLEAN)
result = parse_boolean (value, &gval);
else
result = _moo_value_convert_from_string (value, &gval);
if (result)
moo_edit_config_set_value (config, prop_id, source, &gval);
else
g_warning ("%s: could not convert '%s' to type '%s'",
G_STRLOC, value, g_type_name (G_VALUE_TYPE (&gval)));
g_value_unset (&gval);
}
void
moo_edit_config_parse (MooEditConfig *config,
const char *string,
MooEditConfigSource source)
{
char **vars, **p;
char *copy;
g_return_if_fail (MOO_IS_EDIT_CONFIG (config));
g_return_if_fail (string != NULL);
copy = g_strdelimit (g_strstrip (g_strdup (string)), ",", ';');
vars = g_strsplit (copy, ";", 0);
g_free (copy);
if (!vars)
goto out;
for (p = vars; *p != NULL; p++)
{
char *sep, *var, *value;
g_strstrip (*p);
sep = strchr (*p, ':');
if (!sep || sep == *p || !sep[1])
goto out;
var = g_strndup (*p, sep - *p);
g_strstrip (var);
if (!var[0])
{
g_free (var);
goto out;
}
value = sep + 1;
g_strstrip (value);
if (!value)
{
g_free (var);
goto out;
}
moo_edit_config_parse_one (config, var, value, source);
g_free (var);
}
out:
g_strfreev (vars);
}
void
moo_edit_config_install_alias (const char *name,
const char *alias)
{
char *s1, *s2;
GParamSpec *pspec;
g_return_if_fail (name != NULL);
g_return_if_fail (alias != NULL);
pspec = moo_edit_config_lookup_spec (name, NULL, FALSE);
if (!pspec)
{
g_warning ("%s: no setting named '%s'", G_STRLOC, name);
return;
}
if (moo_edit_config_lookup_spec (alias, NULL, TRUE))
{
g_warning ("%s: setting named '%s' already exists", G_STRLOC, alias);
return;
}
if (g_hash_table_lookup (aliases, alias))
{
g_warning ("%s: alias '%s' already exists", G_STRLOC, alias);
return;
}
s1 = g_strdup (alias);
g_strdelimit (s1, "_", '-');
s2 = g_strdup (alias);
g_strdelimit (s2, "-", '_');
g_hash_table_insert (aliases, s1, g_strdup (pspec->name));
g_hash_table_insert (aliases, s2, g_strdup (pspec->name));
}