Logo Search packages:      
Sourcecode: nautilus version File versions  Download package

eel-preferences.c

/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */

/* eel-preferences.c - Preference peek/poke/notify implementation.

   Copyright (C) 1999, 2000 Eazel, Inc.

   The Gnome Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.

   The Gnome Library 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
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public
   License along with the Gnome Library; see the file COPYING.LIB.  If not,
   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.

   Authors: Ramiro Estrugo <ramiro@eazel.com>
*/

#include <config.h>
#include "eel-preferences.h"

#include "eel-debug.h"
#include "eel-gconf-extensions.h"
#include "eel-lib-self-check-functions.h"
#include "eel-enumeration.h"
#include "eel-glib-extensions.h"
#include "eel-string.h"
#include <gconf/gconf-client.h>
#include <gconf/gconf.h>
#include <gtk/gtk.h>

/* An enumeration used for updating auto-storage variables in a type-specific way. 
 * FIXME: there is another enumeration like this in eel-global-preferences.c,
 * used for different purposes but in a related way. Should we combine them?
 */
typedef enum {
      PREFERENCE_BOOLEAN = 1,
      PREFERENCE_INTEGER,
      PREFERENCE_STRING,
      PREFERENCE_STRING_ARRAY,
      PREFERENCE_STRING_ARRAY_AS_QUARKS
} PreferenceType;

/*
 * PreferencesEntry:
 *
 * A structure to manage preference hash table nodes.
 * Preferences are hash tables.  The hash key is the preference name
 * (a string).  The  hash value is a pointer of the following struct:
 */
typedef struct {
      char *name;
      char *description;
      PreferenceType type;
      gboolean invisible;
      GList *callback_list;
      GList *auto_storage_list;
      int gconf_connection_id;
      char *enumeration_id;
      GConfValue *fallback;
} PreferencesEntry;

/*
 * PreferencesCallbackEntry:
 *
 * A structure to manage callback lists.  A callback list is a GList.
 * The callback_data in each list node is a pointer to the following 
 * struct:
 */
typedef struct {
      EelPreferencesCallback callback;
      gpointer callback_data;
} PreferencesCallbackEntry;

static GHashTable *global_table = NULL;
static char *storage_path = NULL;
static gboolean initialized = FALSE;

static void              preferences_global_table_free             (void);
static char *            preferences_key_make                      (const char               *name);
static void              preferences_callback_entry_free           (PreferencesCallbackEntry *callback_entry);
static void              preferences_entry_update_auto_storage     (PreferencesEntry         *entry);
static PreferencesEntry *preferences_global_table_lookup_or_insert (const char               *name);

static int
preferences_gconf_value_get_int (const GConfValue *value)
{
      if (value == NULL) {
            return 0;
      }
      g_assert (value->type == GCONF_VALUE_INT);
      return gconf_value_get_int (value);
}

static gboolean
preferences_gconf_value_get_bool (const GConfValue *value)
{
      if (value == NULL) {
            return FALSE;
      }
      g_assert (value->type == GCONF_VALUE_BOOL);
      return gconf_value_get_bool (value);
}

static char *
preferences_gconf_value_get_string (const GConfValue *value)
{
      if (value == NULL) {
            return g_strdup ("");
      }
      g_assert (value->type == GCONF_VALUE_STRING);
      return g_strdup (gconf_value_get_string (value));
}

static char **
preferences_gconf_value_get_string_array (const GConfValue *value)
{
      GSList *slist, *l;
      GPtrArray *result;

      if (value == NULL) {
            return NULL;
      }

      g_assert (value->type == GCONF_VALUE_LIST);
      g_assert (gconf_value_get_list_type (value) == GCONF_VALUE_STRING);

      slist = eel_gconf_value_get_string_list (value);

      result = g_ptr_array_new ();
      for (l = slist; l != NULL; l = l->next) {
            g_ptr_array_add (result, l->data);
      }
      g_slist_free (slist);
      g_ptr_array_add (result, NULL);

      return (char **) g_ptr_array_free (result, FALSE);
}

static const char *
preferences_peek_storage_path (void)
{
      g_assert (storage_path != NULL);

      return storage_path;
}

static void
preferences_set_storage_path (const char *new_storage_path)
{
      g_assert (eel_strlen (new_storage_path) > 0);

      /* Make sure the path is indeed different */
      if (eel_str_is_equal (new_storage_path, storage_path)) {
            return;
      }

      /* Free the preference hash table */
      preferences_global_table_free ();

      /* Stop monitoring the old path */
      eel_gconf_monitor_remove (storage_path);

      g_free (storage_path);
      storage_path = g_strdup (new_storage_path);

      /* Start monitoring the new path */
      eel_gconf_monitor_add (storage_path);
}

static gboolean
preferences_is_initialized (void)
{
      return initialized;
}

static GConfValue *
preferences_get_value (const char *name)
{
      GConfValue *result;
      char *key;
      PreferencesEntry *entry;
      
      g_assert (name != NULL);
      g_assert (preferences_is_initialized ());

      key = preferences_key_make (name);
      result = eel_gconf_get_value (key);
      g_free (key);

      if (result == NULL) {         
            entry = preferences_global_table_lookup_or_insert (name);

            if (entry->fallback)
                  result = gconf_value_copy (entry->fallback);
      }
      
      return result;
}

/* If the preference name begind with a "/", we interpret 
 * it as a straight gconf key. */
static gboolean
preferences_preference_is_gconf_key (const char *name)
{
      g_assert (name != NULL);
      
      if (eel_str_has_prefix (name, "/")) {
            return FALSE;
      }
      
      return TRUE;
}

static char *
preferences_key_make (const char *name)
{
      g_assert (name != NULL);
      
      if (!preferences_preference_is_gconf_key (name)) {
            return g_strdup (name);
      }

      /* Otherwise, we prefix it with the path */
      return g_strconcat (preferences_peek_storage_path (), "/",
                      name, NULL);
}

/* Get default from schema or emergency fallback */
static GConfValue *
preferences_get_default_value (const char *name)
{
      GConfValue *result;
      PreferencesEntry *entry;
      char *key;

      g_assert (name != NULL);

      key = preferences_key_make (name);

      result = eel_gconf_get_default_value (key);

      g_free (key);

      if (result == NULL) {
            entry = preferences_global_table_lookup_or_insert (name);
            if (entry && entry->fallback)
                  result = gconf_value_copy (entry->fallback);
      }
      
        return result;
}

static int
preferences_callback_entry_compare (gconstpointer a,
                            gconstpointer b)
{
      const PreferencesCallbackEntry *a_entry = a;
      const PreferencesCallbackEntry *b_entry = b;

      if (a_entry->callback < b_entry->callback) {
            return -1;
      }

      if (a_entry->callback > b_entry->callback) {
            return +1;
      }

      if (a_entry->callback_data < b_entry->callback_data) {
            return -1;
      }

      if (a_entry->callback_data > b_entry->callback_data) {
            return +1;
      }

      return 0;
}

/* Public preferences functions */

gboolean
eel_preferences_get_is_invisible (const char *name)
{
      g_assert (name != NULL);
      g_assert (preferences_is_initialized ());

      return preferences_global_table_lookup_or_insert (name)->invisible;
}

void
eel_preferences_set_is_invisible (const char *name,
                          gboolean is_invisible)
{
      g_return_if_fail (name != NULL);
      g_return_if_fail (preferences_is_initialized ());

      preferences_global_table_lookup_or_insert (name)->invisible = is_invisible;
}

void
eel_preferences_set_boolean (const char *name,
                       gboolean boolean_value)
{
      char *key;

      g_return_if_fail (name != NULL);
      g_return_if_fail (preferences_is_initialized ());
      
      key = preferences_key_make (name);
      eel_gconf_set_boolean (key, boolean_value);
      g_free (key);

      eel_gconf_suggest_sync ();
}

gboolean
eel_preferences_get_boolean (const char *name)
{
      gboolean result;
      GConfValue *value;

      g_return_val_if_fail (name != NULL, 0);
      g_return_val_if_fail (preferences_is_initialized (), 0);

      value = preferences_get_value (name);
      result = preferences_gconf_value_get_bool (value);
      eel_gconf_value_free (value);
      
      return result;
}

void
eel_preferences_set_integer (const char *name,
                       int int_value)
{
      char *key;
      int old_value;

      g_return_if_fail (name != NULL);
      g_return_if_fail (preferences_is_initialized ());
      
      key = preferences_key_make (name);
      old_value = eel_preferences_get_integer (name);
      
      if (int_value != old_value) {
            eel_gconf_set_integer (key, int_value);
      }
      g_free (key);
}

int
eel_preferences_get_integer (const char *name)
{
      int result;
      GConfValue *value;

      g_return_val_if_fail (name != NULL, 0);
      g_return_val_if_fail (preferences_is_initialized (), 0);

      value = preferences_get_value (name);
      result = preferences_gconf_value_get_int (value);
      eel_gconf_value_free (value);

      return result;
}

/* GConf has no unsigned store, save as signed and cast */
guint
eel_preferences_get_uint (const char *name)
{
      return (guint)eel_preferences_get_integer (name);
}
void
eel_preferences_set_uint (const char              *name,
                    guint                    uint_value)
{
      eel_preferences_set_integer (name, (int)uint_value);
}

guint
eel_preferences_get_enum (const char *name)
{
      guint             ret_val;
      char             *str_value;
      GConfValue       *value;
      const EelEnumeration   *enumeration;
      PreferencesEntry *entry;

      g_return_val_if_fail (name != NULL, 0);
      g_return_val_if_fail (preferences_is_initialized (), 0);

      entry = preferences_global_table_lookup_or_insert (name);
      g_return_val_if_fail (entry != NULL, 0);

      enumeration = eel_enumeration_lookup (entry->enumeration_id);

      if (!enumeration) {
            g_warning ("No enum entry for '%s' (%s)",
                     name, entry->enumeration_id);
            return 0;
      }

      value = preferences_get_value (name);
      if (value->type == GCONF_VALUE_INT) { /* compatibility path */
            ret_val = (guint)preferences_gconf_value_get_int (value);
            eel_gconf_value_free (value);
            return ret_val;
      }

      str_value = preferences_gconf_value_get_string (value);
      eel_gconf_value_free (value);

      if (str_value == NULL) {
            g_warning ("No key for '%s' at %s", str_value, name);
            return 0;
      }

      ret_val = eel_enumeration_get_value_for_name (enumeration, str_value);

      g_free (str_value);

      return ret_val;
}

void
eel_preferences_set_enum (const char *name,
                    guint       int_value)
{
      const char       *str_value;
      const EelEnumeration   *enumeration;
      PreferencesEntry *entry;

      g_return_if_fail (name != NULL);
      g_return_if_fail (preferences_is_initialized ());

      entry = preferences_global_table_lookup_or_insert (name);
      g_return_if_fail (entry != NULL);

      enumeration = eel_enumeration_lookup (entry->enumeration_id);

      if (!enumeration) {
            g_warning ("No enum entry for '%s' (%s)",
                     name, entry->enumeration_id);
            return;
      }

      str_value = eel_enumeration_get_name_for_value (enumeration, int_value);

      if (str_value == NULL) {
            g_warning ("No enum match for '%d'", int_value);
            return;
      }

      eel_preferences_set (name, str_value);
}

void
eel_preferences_set (const char *name,
                 const char *string_value)
{
      char *key;
      char *old_value;

      g_return_if_fail (name != NULL);
      g_return_if_fail (preferences_is_initialized ());

      key = preferences_key_make (name);
      old_value = eel_preferences_get (name);

      if (strcmp (string_value, old_value) != 0) {
            eel_gconf_set_string (key, string_value);
      }
      g_free (key);
      g_free (old_value);
}

char *
eel_preferences_get (const char *name)
{
      char *result;
      GConfValue *value;

      g_return_val_if_fail (name != NULL, NULL);
      g_return_val_if_fail (preferences_is_initialized (), NULL);

      value = preferences_get_value (name);
      result = preferences_gconf_value_get_string (value);
      eel_gconf_value_free (value);
      
      return result;
}

void
eel_preferences_set_string_array (const char  *name,
                          char       **strv_value)
{
      GSList *slist;
      int i;
      char *key;

      g_return_if_fail (name != NULL);
      g_return_if_fail (preferences_is_initialized ());

      slist = NULL;
      if (strv_value != NULL) {
            for (i = 0; strv_value[i] != NULL; i++) {
                  slist = g_slist_prepend (slist, strv_value[i]);
            }
            slist = g_slist_reverse (slist);
      }

      key = preferences_key_make (name);
      eel_gconf_set_string_list (key, slist);
      g_free (key);

      g_slist_free (slist);
}

static gboolean
string_array_is_valid (char **strv, const char *enumeration_id)
{
      guint i;
      gboolean res;

      g_assert (strv != NULL);
      g_assert (enumeration_id != NULL);

      res = TRUE;
      for (i = 0; strv[i] != NULL; i++) {
            const EelEnumeration *enumeration;

            enumeration = eel_enumeration_lookup (enumeration_id);
            if (!enumeration) {
                  res = FALSE;
                  break;
            }

            if (!eel_enumeration_contains_name (enumeration, strv[i])) {
                  res = FALSE;
                  break;
            }
      }

      return res;
}

char **
eel_preferences_get_string_array (const char *name)
{
      char **result;
      GConfValue *value;
      PreferencesEntry *entry;
      GConfValue *default_value;

      g_return_val_if_fail (name != NULL, NULL);
      g_return_val_if_fail (preferences_is_initialized (), NULL);

      value = preferences_get_value (name);
      result = preferences_gconf_value_get_string_array (value);
      eel_gconf_value_free (value);

      entry = preferences_global_table_lookup_or_insert (name);
      g_assert (entry != NULL);

      /* No enumeration_id so we're done */
      if (entry->enumeration_id == NULL) {
            return result;
      }

      /* Do a sanity check on the validity of the values */
      if (string_array_is_valid (result, entry->enumeration_id)) {
            return result;
      }

      /* Forget the bad value and use the default instead */
      g_strfreev (result);

      default_value = preferences_get_default_value (name);
      if (default_value) {          
            result = preferences_gconf_value_get_string_array (default_value);
            gconf_value_free (default_value);
      }

      return result;
}

void
eel_preferences_unset (const char *name)
{
      char *key;

      g_return_if_fail (name != NULL);
      g_return_if_fail (preferences_is_initialized ());

      key = preferences_key_make (name);
      
      eel_gconf_unset (key);

      g_free (key);
}

gboolean
eel_preferences_key_is_writable (const char *name)
{
      gboolean result;
      char *key;
      
      g_return_val_if_fail (name != NULL, 0);
      g_return_val_if_fail (preferences_is_initialized (), 0);

      key = preferences_key_make (name);
      result = eel_gconf_key_is_writable (key);
      g_free (key);

      return result;
}

/**
 * preferences_callback_entry_invoke_function
 *
 * A function that invokes a callback from the given struct.  It is meant to be fed to 
 * g_list_foreach ()
 * @data: The list data privately maintained by the GList.
 * @callback_data: The callback_data privately maintained by the GList.
 **/
static void
preferences_callback_entry_invoke_function (gpointer data,
                                  gpointer callback_data)
{
      PreferencesCallbackEntry *callback_entry;
      
      g_assert (data != NULL);
      
      callback_entry = data;

      (* callback_entry->callback) (callback_entry->callback_data);
}

static void
preferences_entry_invoke_callbacks (PreferencesEntry *entry)
{
      g_assert (entry != NULL);

      /* Update the auto storage preferences */
      if (entry->auto_storage_list != NULL) {
            preferences_entry_update_auto_storage (entry);              
      }
      
      /* Invoke callbacks for this entry if any */
      if (entry->callback_list != NULL) {
            g_list_foreach (entry->callback_list,
                        preferences_callback_entry_invoke_function,
                        NULL);
      }
}

static void
update_auto_string (gpointer data, gpointer callback_data)
{
      char **storage;
      const char *value;

      g_assert (data != NULL);
      g_assert (callback_data != NULL);

      storage = (char **)data;
      value = (const char *)callback_data;

      g_free (*storage);
      *(char **)storage = g_strdup (value);
}

static void
update_auto_string_array (gpointer data, gpointer callback_data)
{
      char ***storage;
      char **value;
      
      g_assert (data != NULL);
      g_assert (callback_data != NULL);

      storage = (char ***)data;
      value = (char **)callback_data;

      g_strfreev (*storage);
      *(char ***)storage = value ? g_strdupv (value) : NULL;
}

static void
update_auto_string_array_as_quarks (gpointer data, gpointer callback_data)
{
      GQuark **storage;
      char **value;
      int i = 0;
      
      g_assert (data != NULL);
      g_assert (callback_data != NULL);

      storage = (GQuark **)data;
      value = (char **)callback_data;

      g_free (*storage);
      *storage = g_new (GQuark, g_strv_length (value) + 1);

      if (value != NULL) {
            for (i = 0; value[i] != NULL; ++i) {
                  (*storage)[i] = g_quark_from_string (value[i]);
            }
      }
      (*storage)[i] = 0;
}

static void
update_auto_integer_or_boolean (gpointer data, gpointer callback_data)
{
      g_assert (data != NULL);

      *(int *)data = GPOINTER_TO_INT (callback_data);
}

static void
preferences_entry_update_auto_storage (PreferencesEntry *entry)
{
      char *new_string_value;
      char **new_string_array_value;
      int new_int_value;
      guint new_uint_value;
      gboolean new_boolean_value;

      switch (entry->type) {
      case PREFERENCE_STRING:
            if (entry->enumeration_id != NULL) {
                  new_uint_value = eel_preferences_get_enum (entry->name);
                  g_list_foreach (entry->auto_storage_list,
                              update_auto_integer_or_boolean,
                              GINT_TO_POINTER (new_uint_value));
            } else {
                  new_string_value = eel_preferences_get (entry->name);
                  g_list_foreach (entry->auto_storage_list,
                              update_auto_string,
                              new_string_value);
                  g_free (new_string_value);
            }
            break;
      case PREFERENCE_STRING_ARRAY:
            new_string_array_value = eel_preferences_get_string_array (entry->name);
            g_list_foreach (entry->auto_storage_list,
                        update_auto_string_array,
                        new_string_array_value);
            g_strfreev (new_string_array_value);
            break;
      case PREFERENCE_STRING_ARRAY_AS_QUARKS:
            new_string_array_value = eel_preferences_get_string_array (entry->name);
            g_list_foreach (entry->auto_storage_list,
                        update_auto_string_array_as_quarks,
                        new_string_array_value);
            g_strfreev (new_string_array_value);
            break;
      case PREFERENCE_INTEGER:
            new_int_value = eel_preferences_get_integer (entry->name);
            g_list_foreach (entry->auto_storage_list,
                        update_auto_integer_or_boolean,
                        GINT_TO_POINTER (new_int_value));
            break;
      case PREFERENCE_BOOLEAN:
            new_boolean_value = eel_preferences_get_boolean (entry->name);
            g_list_foreach (entry->auto_storage_list,
                        update_auto_integer_or_boolean,
                        GINT_TO_POINTER (new_boolean_value));
            break;
      default:
            g_warning ("unexpected preferences type %d in preferences_entry_update_auto_storage", entry->type);
      }
}

static void
preferences_something_changed_notice (GConfClient *client, 
                              guint connection_id, 
                              GConfEntry *entry, 
                              gpointer notice_data)
{
      g_assert (entry != NULL);
      g_assert (entry->key != NULL);
      g_assert (notice_data != NULL);

      preferences_entry_invoke_callbacks (notice_data);
}

static void
preferences_entry_ensure_gconf_connection (PreferencesEntry *entry)
{
      char *key;
      
      /*
       * We install only one gconf notification for each preference entry.
       * Otherwise, we would invoke the installed callbacks more than once
       * per registered callback.
       */
      if (entry->gconf_connection_id != EEL_GCONF_UNDEFINED_CONNECTION) {
            return;
      }
            
      g_assert (entry->name != NULL);

      key = preferences_key_make (entry->name);

      entry->gconf_connection_id = eel_gconf_notification_add (key,
                                                 preferences_something_changed_notice,
                                                 entry);
      g_free (key);

      g_assert (entry->gconf_connection_id != EEL_GCONF_UNDEFINED_CONNECTION);
}

/**
 * preferences_entry_add_callback
 *
 * Add a callback to a pref node.  Callbacks are fired whenever
 * the pref value changes.
 * @preferences_entry: The hash node.
 * @callback: The user-supplied callback.
 * @callback_data: The user-supplied closure.
 **/
static void
preferences_entry_add_callback (PreferencesEntry *entry,
                        EelPreferencesCallback callback,
                        gpointer callback_data)
{
      PreferencesCallbackEntry *callback_entry;
      GList *l;

      g_assert (entry != NULL);
      g_assert (callback != NULL);

      callback_entry = g_new0 (PreferencesCallbackEntry, 1);
      callback_entry->callback = callback;
      callback_entry->callback_data = callback_data;

      l = g_list_find_custom (entry->callback_list, callback_entry, preferences_callback_entry_compare);
      if (l == NULL) {
            entry->callback_list = g_list_append (entry->callback_list, callback_entry);
            preferences_entry_ensure_gconf_connection (entry);
      } else {
            g_warning ("Trying to add a callback for %s that already exists.", entry->name);
      }
}

/**
 * preferences_entry_add_auto_storage
 *
 * Add an auto-storage variable to a pref node.  The variable will
 * be updated to match the pref value whenever the pref 
 * the pref value changes.
 * @preferences_entry: The hash node.
 * @storage: The user-supplied location at which to store the value.
 * @type: Which type of variable this is.
 **/
static void
preferences_entry_add_auto_storage (PreferencesEntry *entry,
                            gpointer storage,
                            PreferenceType type)
{
      g_assert (entry != NULL);
      g_assert (storage != NULL);
      g_assert (entry->type == 0 || entry->type == type);
      if (g_list_find (entry->auto_storage_list, storage) != NULL) {
            g_warning ("Trying to add an auto storage for %s that already exists.", entry->name);
            return;
      }

      entry->type = type;
      
      entry->auto_storage_list = g_list_append (entry->auto_storage_list, storage);

      preferences_entry_ensure_gconf_connection (entry);
}

static void
preferences_entry_check_remove_connection (PreferencesEntry *entry)
{
      /*
       * If there are no callbacks or auto-storage variables left in the entry, 
       * remove the gconf notification.
       */
      if (entry->callback_list != NULL || entry->auto_storage_list != NULL) {
            return;
      }

      eel_gconf_notification_remove (entry->gconf_connection_id);
      entry->gconf_connection_id = EEL_GCONF_UNDEFINED_CONNECTION;
}

/**
 * preferences_entry_remove_callback
 *
 * remove a callback from a pref entry.  Both the callback and the callback_data must
 * match in order for a callback to be removed from the entry.
 * @preferences_entry: The hash entry.
 * @callback: The user-supplied callback.
 * @callback_data: The user-supplied closure.
 **/
static void
preferences_entry_remove_callback (PreferencesEntry *entry,
                           EelPreferencesCallback callback,
                           gpointer callback_data)
{
      PreferencesCallbackEntry cb_entry;
      GList *l;

      g_assert (entry != NULL);
      g_assert (callback != NULL);

      cb_entry.callback = callback;
      cb_entry.callback_data = callback_data;

      l = g_list_find_custom (entry->callback_list, &cb_entry, preferences_callback_entry_compare);
      if (l != NULL) {
            preferences_callback_entry_free (l->data);
            entry->callback_list = g_list_delete_link (entry->callback_list, l);
            preferences_entry_check_remove_connection (entry);
      } else {
            g_warning ("Trying to remove a callback for %s without adding it first.", entry->name);
      }

      g_assert (g_list_find_custom (entry->callback_list, &cb_entry, preferences_callback_entry_compare) == NULL);
}

/**
 * preferences_entry_remove_auto_storage
 *
 * remove an auto-storage variable from a pref entry.
 * @preferences_entry: The hash entry.
 * @storage: The user-supplied location.
 **/
static void
preferences_entry_remove_auto_storage (PreferencesEntry *entry,
                               gpointer storage)
{
      GList *new_list;
      const GList *node;
      gpointer storage_in_entry;

      g_assert (entry != NULL);
      g_assert (storage != NULL);
      g_assert (entry->auto_storage_list != NULL);
      
      new_list = g_list_copy (entry->auto_storage_list);
      
      for (node = new_list; node != NULL; node = node->next) {
            storage_in_entry = node->data;
            
            g_assert (storage_in_entry != NULL);
            
            if (storage_in_entry == storage) {
                  entry->auto_storage_list = g_list_remove (entry->auto_storage_list, 
                                                    storage);

                  switch (entry->type) {
                  case PREFERENCE_STRING:
                        update_auto_string (storage, NULL);
                        break;
                  case PREFERENCE_STRING_ARRAY:
                        update_auto_string_array (storage, NULL);
                        break;
                  case PREFERENCE_STRING_ARRAY_AS_QUARKS:
                        update_auto_string_array_as_quarks (storage, NULL);
                        break;
                  case PREFERENCE_BOOLEAN:
                  case PREFERENCE_INTEGER:
                        update_auto_integer_or_boolean (storage, NULL);
                        break;
                  default:
                        g_warning ("unexpected preference type %d in preferences_entry_remove_auto_storage", entry->type);
                  }
            }
      }

      g_list_free (new_list);

      preferences_entry_check_remove_connection (entry);
}

/**
 * preferences_callback_entry_free
 *
 * Free a callback info struct.
 * @preferences_callback_entry: The struct to free.
 **/
static void
preferences_callback_entry_free (PreferencesCallbackEntry *callback_entry)
{
      g_assert (callback_entry != NULL);

      callback_entry->callback = NULL;
      callback_entry->callback_data = NULL;

      g_free (callback_entry);
}

/**
 * preferences_callback_entry_free_func
 *
 * A function that frees a callback info struct.  It is meant to be fed to 
 * g_list_foreach ()
 * @data: The list data privately maintained by the GList.
 * @callback_data: The callback_data privately maintained by the GList.
 **/
static void
preferences_callback_entry_free_func (gpointer  data,
                              gpointer    callback_data)
{
      g_assert (data != NULL);
      
      preferences_callback_entry_free (data);
}

/**
 * preferences_entry_free
 *
 * Free a preference hash node's members along with the node itself.
 * @preferences_hash_node: The node to free.
 **/
static void
preferences_entry_free (PreferencesEntry *entry)
{
      g_assert (entry != NULL);

      eel_gconf_notification_remove (entry->gconf_connection_id);
      entry->gconf_connection_id = EEL_GCONF_UNDEFINED_CONNECTION;

      g_list_free (entry->auto_storage_list);
      eel_g_list_free_deep_custom (entry->callback_list,
                             preferences_callback_entry_free_func,
                             NULL);
      
      entry->auto_storage_list = NULL;
      entry->callback_list = NULL;

      g_free (entry->name);
      g_free (entry->description);
      g_free (entry->enumeration_id);

      eel_gconf_value_free (entry->fallback);

      g_free (entry);
}

/**
 * preferences_entry_free_func
 *
 * A function that frees a pref hash node.  It is meant to be fed to 
 * g_hash_table_foreach ()
 * @key: The hash key privately maintained by the GHashTable.
 * @value: The hash value privately maintained by the GHashTable.
 * @callback_data: The callback_data privately maintained by the GHashTable.
 **/
static void
preferences_entry_free_func (gpointer key,
                       gpointer value,
                       gpointer callback_data)
{
      g_assert (value != NULL);

      preferences_entry_free (value);
}

static void
preferences_global_table_free (void)
{
      if (global_table == NULL) {
            return;
      }
      
      g_hash_table_foreach (global_table, preferences_entry_free_func, NULL);
      g_hash_table_destroy (global_table);
      global_table = NULL;

      g_free (storage_path);
      storage_path = NULL;
}

static void
preferences_uninitialize (void)
{
      initialized = FALSE;
}

static GHashTable *
preferences_global_table_get_global (void)
{
      static gboolean at_exit_handler_added = FALSE;

      if (global_table == NULL) {
            global_table = g_hash_table_new (g_str_hash, g_str_equal);

            if (!at_exit_handler_added) {
                  at_exit_handler_added = TRUE;
                  eel_debug_call_at_shutdown (preferences_global_table_free);
                  /* ensure that we catch calls to preferences functions after eel shutdown */
                  eel_debug_call_at_shutdown (preferences_uninitialize);
            }
      }
      
      return global_table;
}

static PreferencesEntry *
preferences_global_table_lookup (const char *name)
{
      g_assert (name != NULL);
      g_assert (preferences_global_table_get_global () != NULL);
      
      return g_hash_table_lookup (preferences_global_table_get_global (), name);
}

static PreferencesEntry *
preferences_global_table_insert (const char *name)
{
      PreferencesEntry *entry;

      g_assert (name != NULL);
      g_assert (preferences_global_table_get_global () != NULL);
      g_assert (preferences_global_table_lookup (name) == NULL);
      
      entry = g_new0 (PreferencesEntry, 1);
      entry->name = g_strdup (name);

      g_hash_table_insert (preferences_global_table_get_global (), entry->name, entry);

      g_assert (entry == preferences_global_table_lookup (name));

      return entry;
}

static PreferencesEntry *
preferences_global_table_lookup_or_insert (const char *name)
{
      PreferencesEntry *entry;

      g_assert (name != NULL);
      
      entry = preferences_global_table_lookup (name);

      if (entry != NULL) {
            return entry;
      }

      entry = preferences_global_table_insert (name);
      g_assert (entry != NULL);

      return entry;
}

void
eel_preferences_add_callback (const char *name,
                        EelPreferencesCallback callback,
                        gpointer callback_data)
{
      PreferencesEntry *entry;

      g_return_if_fail (name != NULL);
      g_return_if_fail (callback != NULL);
      g_return_if_fail (preferences_is_initialized ());

      entry = preferences_global_table_lookup_or_insert (name);
      g_assert (entry != NULL);

      preferences_entry_add_callback (entry, callback, callback_data);
}

void
eel_preferences_add_auto_string (const char *name,
                         const char **storage)
{
      PreferencesEntry *entry;
      char *value;

      g_return_if_fail (name != NULL);
      g_return_if_fail (storage != NULL);
      g_return_if_fail (preferences_is_initialized ());

      entry = preferences_global_table_lookup_or_insert (name);
      g_assert (entry != NULL);

      preferences_entry_add_auto_storage (entry, storage, PREFERENCE_STRING);

      value = eel_preferences_get (entry->name);
      update_auto_string (storage, value);
      g_free (value);
}

void
eel_preferences_add_auto_string_array (const char   *name,
                               char       ***storage)
{
      PreferencesEntry *entry;
      char **value;

      g_return_if_fail (name != NULL);
      g_return_if_fail (storage != NULL);
      g_return_if_fail (preferences_is_initialized ());

      entry = preferences_global_table_lookup_or_insert (name);
      g_assert (entry != NULL);

      preferences_entry_add_auto_storage (entry, storage, PREFERENCE_STRING_ARRAY);

      value = eel_preferences_get_string_array (entry->name);
      update_auto_string_array (storage, value);
      g_strfreev (value);
}

void
eel_preferences_add_auto_string_array_as_quarks (const char *name,
                                     GQuark **storage)
{
      PreferencesEntry *entry;
      char **value;

      g_return_if_fail (name != NULL);
      g_return_if_fail (storage != NULL);
      g_return_if_fail (preferences_is_initialized ());

      entry = preferences_global_table_lookup_or_insert (name);
      g_assert (entry != NULL);

      preferences_entry_add_auto_storage (entry, storage, PREFERENCE_STRING_ARRAY_AS_QUARKS);

      value = eel_preferences_get_string_array (entry->name);
      update_auto_string_array_as_quarks (storage, value);
      g_strfreev (value);
}

void
eel_preferences_add_auto_integer (const char *name,
                          int *storage)
{
      PreferencesEntry *entry;
      int value;

      g_return_if_fail (name != NULL);
      g_return_if_fail (storage != NULL);
      g_return_if_fail (preferences_is_initialized ());

      entry = preferences_global_table_lookup_or_insert (name);
      g_assert (entry != NULL);

      preferences_entry_add_auto_storage (entry, storage, PREFERENCE_INTEGER);

      value = eel_preferences_get_integer (entry->name);
      update_auto_integer_or_boolean (storage, GINT_TO_POINTER (value));
}


void
eel_preferences_add_auto_enum (const char *name,
                         guint *storage)
{
      PreferencesEntry *entry;
      guint value;

      g_return_if_fail (name != NULL);
      g_return_if_fail (storage != NULL);
      g_return_if_fail (preferences_is_initialized ());

      entry = preferences_global_table_lookup_or_insert (name);
      g_assert (entry != NULL);
      g_assert (entry->enumeration_id != NULL);

      preferences_entry_add_auto_storage (entry, storage, PREFERENCE_STRING);

      value = eel_preferences_get_enum (entry->name);
      update_auto_integer_or_boolean (storage, GINT_TO_POINTER (value));
}

void
eel_preferences_add_auto_boolean (const char *name,
                          gboolean *storage)
{
      PreferencesEntry *entry;
      gboolean value;

      g_return_if_fail (name != NULL);
      g_return_if_fail (storage != NULL);
      g_return_if_fail (preferences_is_initialized ());

      entry = preferences_global_table_lookup_or_insert (name);
      g_assert (entry != NULL);

      preferences_entry_add_auto_storage (entry, storage, PREFERENCE_BOOLEAN);

      value = eel_preferences_get_boolean (entry->name);
      update_auto_integer_or_boolean (storage, GINT_TO_POINTER (value));
}

void
eel_preferences_remove_auto_string (const char *name,
                            const char **storage)
{
      PreferencesEntry *entry;

      g_return_if_fail (name != NULL);
      g_return_if_fail (storage != NULL);
      g_return_if_fail (preferences_is_initialized ());

      entry = preferences_global_table_lookup (name);
      if (entry == NULL) {
            g_warning ("Trying to remove auto-string for %s without adding it first.", name);
            return;
      }

      preferences_entry_remove_auto_storage (entry, storage);
}

void
eel_preferences_remove_auto_string_array (const char *name,
                                char ***storage)
{
      PreferencesEntry *entry;

      g_return_if_fail (name != NULL);
      g_return_if_fail (storage != NULL);
      g_return_if_fail (preferences_is_initialized ());

      entry = preferences_global_table_lookup (name);
      if (entry == NULL) {
            g_warning ("Trying to remove auto-string for %s without adding it first.", name);
            return;
      }

      preferences_entry_remove_auto_storage (entry, storage);
}

void
eel_preferences_remove_auto_integer (const char *name,
                             int *storage)
{
      PreferencesEntry *entry;

      g_return_if_fail (name != NULL);
      g_return_if_fail (storage != NULL);
      g_return_if_fail (preferences_is_initialized ());

      entry = preferences_global_table_lookup (name);
      if (entry == NULL) {
            g_warning ("Trying to remove auto-integer for %s without adding it first.", name);
            return;
      }

      preferences_entry_remove_auto_storage (entry, storage);
}

void
eel_preferences_remove_auto_boolean (const char *name,
                             gboolean *storage)
{
      PreferencesEntry *entry;

      g_return_if_fail (name != NULL);
      g_return_if_fail (storage != NULL);
      g_return_if_fail (preferences_is_initialized ());

      entry = preferences_global_table_lookup (name);
      if (entry == NULL) {
            g_warning ("Trying to remove auto-boolean for %s without adding it first.", name);
            return;
      }

      preferences_entry_remove_auto_storage (entry, storage);
}

typedef struct
{
      char *name;
      EelPreferencesCallback callback;
      gpointer callback_data;
} WhileAliveData;

static void
preferences_while_alive_disconnector (gpointer callback_data, GObject *where_object_was)
{
      WhileAliveData *data;

      g_assert (callback_data != NULL);

      data = callback_data;

      /* we might have survived an eel shutdown, which
       * already cleared all the callbacks */
      if (preferences_is_initialized ()) {
            eel_preferences_remove_callback (data->name,
                                     data->callback,
                                     data->callback_data);
      }

      g_free (data->name);
      g_free (data);
}

void
eel_preferences_add_callback_while_alive (const char *name,
                                EelPreferencesCallback callback,
                                gpointer callback_data,
                                GObject *alive_object)
{
      WhileAliveData *data;

      g_return_if_fail (name != NULL);
      g_return_if_fail (callback != NULL);
      g_return_if_fail (G_IS_OBJECT (alive_object));
      g_return_if_fail (preferences_is_initialized ());

      data = g_new (WhileAliveData, 1);
      data->name = g_strdup (name);
      data->callback = callback;
      data->callback_data = callback_data;

      eel_preferences_add_callback (name, callback, callback_data);

      g_object_weak_ref (alive_object,
                     preferences_while_alive_disconnector,
                     data);
}

void
eel_preferences_remove_callback (const char *name,
                         EelPreferencesCallback callback,
                         gpointer callback_data)
{
      PreferencesEntry *entry;

      g_return_if_fail (name != NULL);
      g_return_if_fail (callback != NULL);
      g_return_if_fail (preferences_is_initialized ());

      entry = preferences_global_table_lookup (name);

      if (entry == NULL) {
            g_warning ("Trying to remove a callback for %s without adding it first.", name);
            return;
      }
      
      preferences_entry_remove_callback (entry, callback, callback_data);
}

void
eel_preferences_set_description (const char *name,
                         const char *description)
{
      PreferencesEntry *entry;

      g_return_if_fail (name != NULL);
      g_return_if_fail (description != NULL);
      g_return_if_fail (preferences_is_initialized ());

      entry = preferences_global_table_lookup_or_insert (name);
      g_assert (entry != NULL);

      g_free (entry->description);
      entry->description = g_strdup (description);
}

char *
eel_preferences_get_description (const char *name)
{
      PreferencesEntry *entry;

      g_return_val_if_fail (name != NULL, NULL);
      g_return_val_if_fail (preferences_is_initialized (), NULL);

      entry = preferences_global_table_lookup_or_insert (name);

      return g_strdup (entry->description ? entry->description : "");
}

void
eel_preferences_set_enumeration_id (const char *name,
                            const char *enumeration_id)
{
      PreferencesEntry *entry;

      g_return_if_fail (name != NULL);
      g_return_if_fail (enumeration_id != NULL);
      g_return_if_fail (preferences_is_initialized ());

      entry = preferences_global_table_lookup_or_insert (name);
      g_assert (entry != NULL);

      g_free (entry->enumeration_id);
      entry->enumeration_id = g_strdup (enumeration_id);
}

char *
eel_preferences_get_enumeration_id (const char *name)
{
      PreferencesEntry *entry;

      g_return_val_if_fail (name != NULL, NULL);
      g_return_val_if_fail (preferences_is_initialized (), NULL);

      entry = preferences_global_table_lookup_or_insert (name);

      return g_strdup (entry->enumeration_id);
}

static void
preferences_set_emergency_fallback_stealing_value (const char *name,
                                       GConfValue *value)
{
      PreferencesEntry *entry;

      g_assert (name != NULL);
      g_assert (preferences_is_initialized ());

      entry = preferences_global_table_lookup_or_insert (name);
      g_assert (entry != NULL);

      if (entry->fallback)
            gconf_value_free (entry->fallback);
      entry->fallback = value; /* steal ownership of value */
}

void
eel_preferences_set_emergency_fallback_string (const char *name,
                                     const char *value)
{
      GConfValue *gconf_value;

      g_return_if_fail (name != NULL);
      g_return_if_fail (value != NULL);
      
      gconf_value = gconf_value_new (GCONF_VALUE_STRING);

      gconf_value_set_string (gconf_value, value);

      preferences_set_emergency_fallback_stealing_value (name, gconf_value);
}

void
eel_preferences_set_emergency_fallback_integer (const char *name,
                                    int         value)
{
      GConfValue *gconf_value;

      g_return_if_fail (name != NULL);
      
      gconf_value = gconf_value_new (GCONF_VALUE_INT);

      gconf_value_set_int (gconf_value, value);

      preferences_set_emergency_fallback_stealing_value (name, gconf_value);
}

void
eel_preferences_set_emergency_fallback_boolean (const char *name,
                                    gboolean    value)
{
      GConfValue *gconf_value;

      g_return_if_fail (name != NULL);
      
      gconf_value = gconf_value_new (GCONF_VALUE_BOOL);

      gconf_value_set_bool (gconf_value, value);

      preferences_set_emergency_fallback_stealing_value (name, gconf_value);
}


void
eel_preferences_set_emergency_fallback_string_array (const char  *name,
                                         char       **value)
{
      GConfValue *gconf_value;
      GSList *list;
      int i;

      g_return_if_fail (name != NULL);
      g_return_if_fail (value != NULL);

      gconf_value = gconf_value_new (GCONF_VALUE_LIST);
      gconf_value_set_list_type (gconf_value, GCONF_VALUE_STRING);

      list = NULL;
      for (i = 0; value[i] != NULL; ++i)
      {
            GConfValue *v;

            v = gconf_value_new (GCONF_VALUE_STRING);
            gconf_value_set_string (v, value[i]);

            list = g_slist_prepend (list, v);
      }

      gconf_value_set_list_nocopy (gconf_value, g_slist_reverse (list));

      preferences_set_emergency_fallback_stealing_value (name, gconf_value);
}

GConfValue*
eel_preferences_get_emergency_fallback (const char *name)
{
      PreferencesEntry *entry;

      g_return_val_if_fail (name != NULL, NULL);
      g_return_val_if_fail (preferences_is_initialized (), NULL);

      entry = preferences_global_table_lookup_or_insert (name);
      
      return entry->fallback ? gconf_value_copy (entry->fallback) : NULL;
}

gboolean
eel_preferences_monitor_directory (const char *directory)
{
      g_return_val_if_fail (preferences_is_initialized (), FALSE);

      return eel_gconf_monitor_add (directory);
}

gboolean
eel_preferences_is_visible (const char *name)
{
      g_return_val_if_fail (name != NULL, FALSE);
      g_return_val_if_fail (preferences_is_initialized (), FALSE);

      return !preferences_global_table_lookup_or_insert (name)->invisible;
}

void
eel_preferences_init (const char *path)
{
      g_return_if_fail (eel_strlen (path) > 0);
      
      if (initialized) {
            return;
      }

      initialized = TRUE;

      preferences_set_storage_path (path);
}

#if !defined (EEL_OMIT_SELF_CHECK)

#define CHECK_BOOLEAN(name__, value__)                                              \
G_STMT_START {                                                                \
      eel_preferences_set_boolean ((name__), (value__));                            \
      EEL_CHECK_BOOLEAN_RESULT (eel_preferences_get_boolean (name__), (value__));   \
} G_STMT_END

#define CHECK_INTEGER(name__, value__)                                              \
G_STMT_START {                                                                \
      eel_preferences_set_integer ((name__), (value__));                            \
      EEL_CHECK_INTEGER_RESULT (eel_preferences_get_integer (name__), (value__));   \
} G_STMT_END

#define CHECK_STRING(name__, value__)                                         \
G_STMT_START {                                                          \
      eel_preferences_set ((name__), (value__));                              \
      EEL_CHECK_STRING_RESULT (eel_preferences_get (name__), (value__));            \
} G_STMT_END

void
eel_self_check_preferences (void)
{
      /* Disabled until I can debug why these seemingly harmless tests 
       * dont work. -re
       */
#if 0
      int original_user_level;

      original_user_level = eel_preferences_get_user_level ();

      EEL_CHECK_INTEGER_RESULT (eel_preferences_get_integer ("self-check/neverset/i"), 0);
      EEL_CHECK_STRING_RESULT (eel_preferences_get ("self-check/neverset/s"), "");
      EEL_CHECK_BOOLEAN_RESULT (eel_preferences_get_boolean ("self-check/neverset/b"), FALSE);

      eel_preferences_set_user_level (0);

      /* FIXME: Fails if you add the commented-out lines. */
      /* CHECK_INTEGER ("self-check/i", 0); */
      CHECK_INTEGER ("self-check/i", 666);
      /* CHECK_BOOLEAN ("self-check/b", FALSE); */
      CHECK_BOOLEAN ("self-check/b", TRUE);
      /* CHECK_STRING ("self-check/s", ""); */
      CHECK_STRING ("self-check/s", "foo");

      /* Restore the original user level */
      eel_preferences_set_user_level (original_user_level);
#endif
}

#endif /* !EEL_OMIT_SELF_CHECK */

Generated by  Doxygen 1.6.0   Back to index