/* * mooeditconfig.c * * Copyright (C) 2004-2010 by Yevgen Muntyan * * This file is part of medit. medit is free software; you can * redistribute it and/or modify it under the terms of the * GNU Lesser General Public License as published by the * Free Software Foundation; either version 2.1 of the License, * or (at your option) any later version. * * You should have received a copy of the GNU Lesser General Public * License along with medit. If not, see . */ #define MOOEDIT_COMPILATION #include "mooedit/mooeditconfig.h" #include "mooedit/moolang.h" #include "mooedit/mooeditprefs.h" #include "mooutils/mooutils-gobject.h" #include "mooutils/mooutils-debug.h" #include "mooutils/mooutils-mem.h" #include "mooutils/mooprefs.h" #include #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 { MOO_IP_ARRAY_ELMS (Value, values); }; struct Value { GValue gval; MooEditConfigSource source; }; 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); MOO_IP_ARRAY_INIT (Value, config->priv, values, 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)); MOO_IP_ARRAY_DESTROY (config->priv, values); G_OBJECT_CLASS (moo_edit_config_parent_class)->finalize (object); } static void global_init (void) { if (!global) { guint i; global = MOO_EDIT_CONFIG (g_object_new (MOO_TYPE_EDIT_CONFIG, (const char*) 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 MOO_EDIT_CONFIG (g_object_new (MOO_TYPE_EDIT_CONFIG, (const char*) 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) { MOO_IP_ARRAY_GROW (Value, config->priv, 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); MOO_IP_ARRAY_GROW (Value, global->priv, 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); } static gboolean char_is_space (const char *s) { return *s && g_unichar_isspace (g_utf8_get_char (s)); } static const char * skip_space (const char *string) { while (*string && char_is_space (string)) string = g_utf8_next_char (string); return string; } static const char * skip_separator (const char *string) { gboolean seen_separator = FALSE; while (*string) { if (!seen_separator && (*string == ',' || *string == ';')) { seen_separator = TRUE; string += 1; } if (*string && char_is_space (string)) string = g_utf8_next_char (string); else break; } return string; } void moo_edit_config_parse (MooEditConfig *config, const char *string, MooEditConfigSource source) { const char *p = string; g_return_if_fail (MOO_IS_EDIT_CONFIG (config)); g_return_if_fail (string != NULL); g_return_if_fail (g_utf8_validate (string, -1, NULL)); while (*p) { const char *colon; const char *assign; const char *name_start, *name_end; const char *value_start, *value_end; char *name, *value; p = skip_space (p); if (!*p) break; colon = strchr (p, ':'); assign = strchr (p, '='); if (!colon && !assign) { g_warning ("%s: invalid config string '%s'", G_STRLOC, string); return; } if (colon && assign && assign < colon) colon = NULL; name_start = p; name_end = colon ? colon : assign; while (name_end > name_start && char_is_space (g_utf8_prev_char (name_end))) name_end = g_utf8_prev_char (name_end); if (name_end == name_start) { g_warning ("%s: invalid config string '%s'", G_STRLOC, string); return; } if (colon) { value_start = skip_space (colon + 1); if (!*value_start || *value_start == ';' || *value_start == ',') { g_warning ("%s: invalid config string '%s'", G_STRLOC, string); return; } for (value_end = value_start + 1; *value_end && !char_is_space (value_end) && *value_end != ';' && *value_end != ','; value_end = g_utf8_next_char (value_end)) ; p = skip_separator (value_end); } else { gunichar separator; value_start = skip_space (assign + 1); if (!(separator = g_utf8_get_char (value_start))) { g_warning ("%s: invalid config string '%s'", G_STRLOC, string); return; } value_start = g_utf8_next_char (value_start); value_end = g_utf8_strchr (g_utf8_next_char (value_start), -1, separator); if (!value_end) { g_warning ("%s: unterminated value in '%s'", G_STRLOC, string); return; } p = skip_separator (g_utf8_next_char (value_end)); } name = g_strndup (name_start, name_end - name_start); value = g_strndup (value_start, value_end - value_start); moo_edit_config_parse_one (config, name, value, source); g_free (value); g_free (name); } } 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)); }