/*
 * Copyright (c) 2002, 2003, 2004 Jean-Yves Lefort
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of Jean-Yves Lefort nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include <string.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include "st-category-bag.h"
#include "st-handler.h"
#include "st-handler-field.h"
#include "sg-util.h"
#include "st-stream-bag.h"
#include "st-category-store.h"
#include "st-stream-store.h"
#include "st-dialog-api.h"
#include "st-plugin.h"

/*** type definitions ********************************************************/

enum {
  PROP_0,
  PROP_NAME,
  PROP_DUMMY
};
    
typedef struct
{
  gpointer cb;
  gpointer data;
} STHandlerCallback;

struct _STHandlerPrivate
{
  gboolean		dummy;
  char			*name;
  char			*label;
  char			*description;
  char			*home;

  GdkPixbuf		*pixbuf;
  
  GSList		*fields;
  GNode			*stock_categories;
  
  STHandlerFlags	flags;
  int			stream_version;

  STHandlerCallback	callbacks[ST_HANDLER_N_EVENTS];

  STCategoryStore	*categories;
  /*
   * We don't need a categories mutex (in the current design, the
   * store is never accessed by multiple threads concurrently).
   */

  GHashTable		*streams;
  GMutex		*streams_mutex;

  int			fields_sort_index;
  int			fields_sort_order;

  int			paned_position;

  GHashTable		*config;
  GMutex		*config_mutex;
};

typedef struct
{
  const char		*name;
  STCategoryBag		*category_bag;
} GetStockCategoryInfo;

typedef struct
{
  STHandler		*handler;
  GHashTable		*stream_bag_lists;
} StreamListsToBagsInfo;

typedef struct
{
  STHandlerConfigForeachCallback	cb;
  gpointer				data;
} ConfigForeachInfo;

typedef struct
{
  GParamSpec		*pspec;
  GValue		value;
} ConfigEntry;

/*** function declarations ***************************************************/

static void        st_handler_class_init		(STHandlerClass *class);
static void        st_handler_init			(STHandler   *handler);
static GObject     *st_handler_constructor		(GType       type,
							 unsigned int n_construct_properties,
							 GObjectConstructParam *construct_params);
static void        st_handler_set_property		(GObject      *object,
							 unsigned int prop_id,
							 const GValue *value,
							 GParamSpec   *pspec);

static STCategory  *st_handler_category_new_default_cb	(gpointer    data);
static void	   st_handler_category_free_default_cb	(STCategory  *category,
							 gpointer    data);
static STStream    *st_handler_stream_new_default_cb	(gpointer    data);
static void	   st_handler_stream_free_default_cb	(STStream    *stream,
							 gpointer    data);

static gboolean    st_handler_set_stock_categories_cb	(GNode       *node,
							 gpointer    data);

static gboolean    st_handler_get_stock_category_cb	(GNode       *node,
							 gpointer    data);

static void        st_handler_reload_multiple_cb	(gpointer    key,
							 gpointer    value,
							 gpointer    user_data);

static gboolean    st_handler_reload_single		(STHandler	*handler,
							 STCategoryBag	*category_bag,
							 GNode		**category_bags,
							 GSList		**stream_bags,
							 GError		**err);
static gboolean    st_handler_reload_multiple		(STHandler	*handler,
							 GNode		**category_bags,
							 GHashTable	**stream_bag_lists,
							 GError		**err);

static GNode       *st_handler_categories_to_bags	(STHandler   *handler,
							 GNode       *categories);
static gboolean    st_handler_categories_to_bags_cb	(GNode       *node,
							 gpointer    data);
static void        st_handler_categories_free		(STHandler   *handler,
							 GNode       *categories);
static gboolean    st_handler_categories_free_cb	(GNode       *node,
							 gpointer    data);

static GSList      *st_handler_streams_to_bags		(STHandler   *handler,
							 GList       *streams);
static void        st_handler_streams_free              (STHandler   *handler,
							 GList       *streams);

static GHashTable  *st_handler_stream_lists_to_bags	(STHandler   *handler,
							 GHashTable  *stream_lists);
static void        st_handler_stream_lists_to_bags_cb	(gpointer    key,
							 gpointer    value,
							 gpointer    user_data);
static void        st_handler_stream_lists_free		(STHandler   *handler,
							 GHashTable  *stream_lists);
static void        st_handler_stream_lists_free_cb	(gpointer    key,
							 gpointer    value,
							 gpointer    user_data);

static gboolean    st_handler_get_selected_category_cb	(STCategoryStore *store,
							 STCategoryBag   *bag,
							 STCategoryBag   *parent,
							 gpointer        data);

static void        st_handler_config_foreach_cb		(gpointer    key,
							 gpointer    value,
							 gpointer    user_data);

/*** variable declarations ***************************************************/

static GObjectClass *parent_class = NULL;

/*** API implementation ******************************************************/

GType
st_handler_get_type (void)
{
  static GType handler_type = 0;
  
  if (! handler_type)
    {
      static const GTypeInfo handler_info = {
	sizeof(STHandlerClass),
	NULL,
	NULL,
	(GClassInitFunc) st_handler_class_init,
	NULL,
	NULL,
	sizeof(STHandler),
	0,
	(GInstanceInitFunc) st_handler_init,
      };
      
      handler_type = g_type_register_static(G_TYPE_OBJECT,
					    "STHandler",
					    &handler_info,
					    0);
    }
  
  return handler_type;
}

/**
 * st_handler_new:
 * @name: the name of the handler.
 *
 * Creates a new #STHandler. After creating the handler, you must set
 * its label using st_handler_set_label(), add your stream fields
 * using st_handler_add_field(), and bind functions to events using
 * st_handler_bind().
 *
 * Functions must be bound to #ST_HANDLER_EVENT_STREAM_FIELD_GET and
 * #ST_HANDLER_EVENT_STREAM_FIELD_SET. Additionally, if one of the
 * fields added with st_handler_add_field() has the
 * #ST_HANDLER_FIELD_EDITABLE flag set, a function must be bound to
 * #ST_HANDLER_EVENT_STREAM_MODIFY.
 *
 * Return value: the newly created #STHandler.
 **/
STHandler *
st_handler_new (const char *name)
{
  g_return_val_if_fail(name != NULL, NULL);

  return g_object_new(ST_TYPE_HANDLER, "name", name, NULL);
}

/**
 * st_handler_new_from_plugin:
 * @plugin: a plugin to take the name, label and icon from.
 *
 * Creates a new #STHandler, using the name, label and icon of
 * @plugin. You must set the name and label of @plugin before calling
 * this function.
 *
 * Return value: the newly created #STHandler.
 **/
STHandler *
st_handler_new_from_plugin (STPlugin *plugin)
{
  STHandler *handler;

  g_return_val_if_fail(ST_IS_PLUGIN(plugin), NULL);

  handler = st_handler_new(st_plugin_get_name(plugin));

  handler->priv->label = g_strdup(st_plugin_get_label(plugin));
  handler->priv->pixbuf = st_plugin_get_pixbuf(plugin);
  if (handler->priv->pixbuf)
    g_object_ref(handler->priv->pixbuf);

  return handler;
}

static STCategory *
st_handler_category_new_default_cb (gpointer data)
{
  return g_new0(STCategory, 1);
}

static void
st_handler_category_free_default_cb (STCategory *category, gpointer data)
{
  st_category_free(category);
}

static STStream *
st_handler_stream_new_default_cb (gpointer data)
{
  return g_new0(STStream, 1);
}

static void
st_handler_stream_free_default_cb (STStream *stream, gpointer data)
{
  st_stream_free(stream);
}

/**
 * st_handler_set_label:
 * @handler: a handler.
 * @label: the handler label.
 *
 * Sets the handler label, which will be used in the handler tab, and
 * in various other places of the user interface.
 **/
void
st_handler_set_label (STHandler *handler, const char *label)
{
  g_return_if_fail(ST_IS_HANDLER(handler));
  g_return_if_fail(label != NULL);

  g_free(handler->priv->label);
  handler->priv->label = g_strdup(label);
}

/**
 * st_handler_set_description:
 * @handler: a handler.
 * @description: the handler description.
 *
 * Sets the handler description, which will be displayed in the
 * toolbar when the handler is selected.
 **/
void
st_handler_set_description (STHandler *handler, const char *description)
{
  g_return_if_fail(ST_IS_HANDLER(handler));
  g_return_if_fail(description != NULL);

  g_free(handler->priv->description);
  handler->priv->description = g_strdup(description);
}

/**
 * st_handler_set_home:
 * @handler: a handler.
 * @home: the handler home page URL.
 *
 * Sets the home page URL of the directory handled by
 * @handler. Setting the home page URL will allow the user to visit
 * the page by clicking on the toolbar link.
 **/
void
st_handler_set_home (STHandler *handler, const char *home)
{
  g_return_if_fail(ST_IS_HANDLER(handler));
  g_return_if_fail(home != NULL);

  g_free(handler->priv->home);
  handler->priv->home = g_strdup(home);
}

/*
 * Deprecated
 */
void
st_handler_set_copyright (STHandler *handler, const char *copyright)
{
  g_return_if_fail(ST_IS_HANDLER(handler));
  g_return_if_fail(copyright != NULL);

  /* nop */
}

/*
 * Deprecated
 */
void
st_handler_set_info (STHandler *handler,
		     const char *label,
		     const char *copyright)
{
  g_return_if_fail(ST_IS_HANDLER(handler));
  g_return_if_fail(label != NULL);
  g_return_if_fail(copyright != NULL);

  st_handler_set_label(handler, label);
}

/**
 * st_handler_set_icon_from_pixbuf:
 * @handler: a handler.
 * @pixbuf: an icon.
 *
 * Sets the handler icon from a #GdkPixbuf.
 **/
void
st_handler_set_icon_from_pixbuf (STHandler *handler, GdkPixbuf *pixbuf)
{
  g_return_if_fail(ST_IS_HANDLER(handler));
  g_return_if_fail(GDK_IS_PIXBUF(pixbuf));
  
  if (handler->priv->pixbuf)
    g_object_unref(handler->priv->pixbuf);
  handler->priv->pixbuf = g_object_ref(pixbuf);
}

/**
 * st_handler_set_icon_from_inline:
 * @handler: a handler.
 * @size: the size of the inline data.
 * @data: the inline data.
 *
 * Sets the handler icon using inline data. See the
 * gdk_pixbuf_new_from_inline() documentation.
 **/
void
st_handler_set_icon_from_inline (STHandler *handler,
				 int size,
				 const guint8 *data)
{
  g_return_if_fail(ST_IS_HANDLER(handler));
  g_return_if_fail(size > 0);
  g_return_if_fail(data != NULL);

  if (handler->priv->pixbuf)
    g_object_unref(handler->priv->pixbuf);
  handler->priv->pixbuf = gdk_pixbuf_new_from_inline(size, data, FALSE, NULL);
  g_return_if_fail(GDK_IS_PIXBUF(handler->priv->pixbuf));
}

/**
 * st_handler_set_icon_from_file:
 * @handler: a handler.
 * @filename: an icon file.
 * @err: a location to store errors, or %NULL.
 *
 * Sets the handler icon using an external file. See the
 * gdk_pixbuf_new_from_file() documentation.
 *
 * Return value: %FALSE if the file could not be loaded. In such case
 * @err will be set.
 **/
gboolean
st_handler_set_icon_from_file (STHandler *handler,
			       const char *filename,
			       GError **err)
{
  g_return_val_if_fail(ST_IS_HANDLER(handler), FALSE);
  g_return_val_if_fail(filename != NULL, FALSE);
  g_return_val_if_fail(handler->priv->pixbuf == NULL, FALSE);

  if (handler->priv->pixbuf)
    g_object_unref(handler->priv->pixbuf);
  handler->priv->pixbuf = gdk_pixbuf_new_from_file(filename, err);
  return handler->priv->pixbuf != NULL;
}

/*
 * Deprecated.
 */
void
st_handler_set_icon (STHandler *handler, int size, const guint8 *data)
{
  g_return_if_fail(ST_IS_HANDLER(handler));
  g_return_if_fail(size > 0);
  g_return_if_fail(data != NULL);

  st_handler_set_icon_from_inline(handler, size, data);
}

/**
 * st_handler_set_stock_categories:
 * @handler: a handler.
 * @categories: a tree of stock categories.
 *
 * Sets the handler stock categories. The "__main" category is
 * mandatory.
 *
 * Special category names: __main and __search.
 *
 * The stock categories will not be saved to permanent storage.
 **/
void
st_handler_set_stock_categories (STHandler *handler, GNode *categories)
{
  g_return_if_fail(ST_IS_HANDLER(handler));
  g_return_if_fail(categories != NULL);
  g_return_if_fail(handler->priv->stock_categories == NULL);

  g_node_traverse(categories,
		  G_PRE_ORDER,
		  G_TRAVERSE_ALL,
		  -1,
		  st_handler_set_stock_categories_cb,
		  handler);
  handler->priv->stock_categories = categories;
}

static gboolean
st_handler_set_stock_categories_cb (GNode *node, gpointer data)
{
  if (node->data)
    {
      STHandler *handler = data;
      STCategoryBag *bag;

      bag = st_category_bag_new_from_category(handler, node->data);
      node->data = bag;
    }

  return FALSE;			/* continue */
}

/**
 * st_handler_set_flags:
 * @handler: a handler.
 * @flags: the handler flags.
 *
 * Sets the handler flags.
 **/
void
st_handler_set_flags (STHandler *handler, STHandlerFlags flags)
{
  g_return_if_fail(ST_IS_HANDLER(handler));

  handler->priv->flags = flags;
}

/**
 * st_handler_set_stream_version:
 * @handler: a handler:
 * @version: the stream version.
 *
 * Sets the handler stream version.
 **/
void
st_handler_set_stream_version (STHandler *handler, int version)
{
  g_return_if_fail(ST_IS_HANDLER(handler));

  handler->priv->stream_version = version;
}

/**
 * st_handler_add_field:
 * @handler: a handler.
 * @field: a field to add to @handler.
 *
 * Add a stream field to @handler.
 **/
void
st_handler_add_field (STHandler *handler, STHandlerField *field)
{
  g_return_if_fail(ST_IS_HANDLER(handler));
  g_return_if_fail(field != NULL);

  handler->priv->fields = g_slist_append(handler->priv->fields, field);
}

/**
 * st_handler_bind:
 * @handler: a handler.
 * @event: an event to bind @cb to.
 * @cb: a callback function to bind to @event.
 * @data: data to pass to @cb.
 *
 * Binds callback function @cb to event @event. If a function is
 * already bound to @cb, the previous binding will be overriden (only
 * one function can be bound to an event).
 **/
void
st_handler_bind (STHandler *handler,
		 STHandlerEvent event,
		 gpointer cb,
		 gpointer data)
{
  g_return_if_fail(ST_IS_HANDLER(handler));
  g_return_if_fail(event >= 0 && event < ST_HANDLER_N_EVENTS);
  g_return_if_fail(cb != NULL);

  handler->priv->callbacks[event].cb = cb;
  handler->priv->callbacks[event].data = data;
}

/**
 * st_handler_notice:
 * @handler: a handler.
 * @format: the message format. See the printf() documentation.
 * @...: the parameters to insert into the format string.
 *
 * Outputs a formatted handler notice to the standard error output.
 **/
void
st_handler_notice (STHandler *handler, const char *format, ...)
{
  va_list args;
  char *notice;

  g_return_if_fail(ST_IS_HANDLER(handler));
  g_return_if_fail(format != NULL);

  va_start(args, format);
  notice = g_strdup_vprintf(format, args);
  va_end(args);

  st_notice("%s: %s", handler->priv->label, notice);
  g_free(notice);
}

/**
 * st_handler_config_register:
 * @handler: a handler.
 * @pspec: a param spec defining the configuration key to
 * register. The param spec value type must be #G_TYPE_BOOLEAN,
 * #G_TYPE_INT, #G_TYPE_UINT, #G_TYPE_DOUBLE, #G_TYPE_STRING or
 * #G_TYPE_VALUE_ARRAY. The param spec flags must be
 * #G_PARAM_READWRITE.
 *
 * Registers a new configuration key.
 **/
void
st_handler_config_register (STHandler *handler, GParamSpec *pspec)
{
  ConfigEntry *entry;

  /* keep in sync with st_config_load_handler_key_value() */

  g_return_if_fail(ST_IS_HANDLER(handler));
  g_return_if_fail(G_IS_PARAM_SPEC(pspec));
  g_return_if_fail(G_PARAM_SPEC_VALUE_TYPE(pspec) == G_TYPE_BOOLEAN
		   || G_PARAM_SPEC_VALUE_TYPE(pspec) == G_TYPE_INT
		   || G_PARAM_SPEC_VALUE_TYPE(pspec) == G_TYPE_UINT
		   || G_PARAM_SPEC_VALUE_TYPE(pspec) == G_TYPE_DOUBLE
		   || G_PARAM_SPEC_VALUE_TYPE(pspec) == G_TYPE_STRING
		   || G_PARAM_SPEC_VALUE_TYPE(pspec) == G_TYPE_VALUE_ARRAY);
  g_return_if_fail((pspec->flags & G_PARAM_READWRITE) != 0);
  
  g_mutex_lock(handler->priv->config_mutex);

  entry = g_hash_table_lookup(handler->priv->config, g_param_spec_get_name(pspec));
  g_return_if_fail(entry == NULL);

  entry = g_new0(ConfigEntry, 1);
  entry->pspec = g_param_spec_ref(pspec);
  g_param_spec_sink(pspec);
  g_value_init(&entry->value, G_PARAM_SPEC_VALUE_TYPE(entry->pspec));
  g_param_value_set_default(entry->pspec, &entry->value);

  g_hash_table_insert(handler->priv->config, (gpointer) g_param_spec_get_name(pspec), entry);

  g_mutex_unlock(handler->priv->config_mutex);
}

/**
 * st_handler_config_lookup:
 * @handler: a handler.
 * @key: a configuration key.
 *
 * Looks up the #GParamSpec of a configuration key.
 *
 * Return value: the #GParamSpec of @key, or %NULL if no such
 * configuration key exists.
 **/
GParamSpec *
st_handler_config_lookup (STHandler *handler, const char *key)
{
  const ConfigEntry *entry;
  GParamSpec *pspec;

  g_return_val_if_fail(ST_IS_HANDLER(handler), NULL);
  g_return_val_if_fail(key != NULL, NULL);

  g_mutex_lock(handler->priv->config_mutex);
  entry = g_hash_table_lookup(handler->priv->config, key);
  pspec = entry ? entry->pspec : NULL;
  g_mutex_unlock(handler->priv->config_mutex);

  return pspec;
}

/**
 * st_handler_config_foreach:
 * @handler: a handler.
 * @cb: a function to call for each key/value pair.
 * @data: data to pass to @cb.
 *
 * Calls the given function for each key/value pair in the
 * configuration database of @handler. You must not call any other
 * st_handler_config_* function while iterating over the database.
 **/
void
st_handler_config_foreach (STHandler *handler,
			   STHandlerConfigForeachCallback cb,
			   gpointer data)
{
  ConfigForeachInfo info;

  g_return_if_fail(ST_IS_HANDLER(handler));
  g_return_if_fail(cb != NULL);

  info.cb = cb;
  info.data = data;

  g_mutex_lock(handler->priv->config_mutex);
  g_hash_table_foreach(handler->priv->config, st_handler_config_foreach_cb, &info);
  g_mutex_unlock(handler->priv->config_mutex);
}

static void
st_handler_config_foreach_cb (gpointer key,
			      gpointer value,
			      gpointer user_data)
{
  ConfigForeachInfo *info = user_data;
  const ConfigEntry *entry = value;

  info->cb(entry->pspec, &entry->value, info->data);
}

/**
 * st_handler_config_get_value:
 * @handler: a handler.
 * @key: a configuration key previously registered with
 * st_handler_config_register().
 * @value: a location to copy the value of @key to.
 *
 * Gets a configuration value. @value must be zero-filled (but not
 * initialized), and should be unset with g_value_unset() when no
 * longer needed.
 **/
void
st_handler_config_get_value (STHandler *handler,
			     const char *key,
			     GValue *value)
{
  const ConfigEntry *entry;

  g_return_if_fail(ST_IS_HANDLER(handler));
  g_return_if_fail(key != NULL);

  g_mutex_lock(handler->priv->config_mutex);

  entry = g_hash_table_lookup(handler->priv->config, key);
  g_return_if_fail(entry != NULL);

  g_value_init(value, G_PARAM_SPEC_VALUE_TYPE(entry->pspec));
  g_value_copy(&entry->value, value);

  g_mutex_unlock(handler->priv->config_mutex);
}

/**
 * st_handler_config_set_value:
 * @handler: a handler.
 * @key: a configuration key previously registered with
 * st_handler_config_register().
 * @value: the value to set.
 *
 * Sets a configuration value. @value must match the value type of the
 * param spec of @key.
 **/
void
st_handler_config_set_value (STHandler *handler,
			     const char *key,
			     const GValue *value)
{
  ConfigEntry *entry;

  g_return_if_fail(ST_IS_HANDLER(handler));
  g_return_if_fail(key != NULL);
  g_return_if_fail(G_IS_VALUE(value));

  g_mutex_lock(handler->priv->config_mutex);

  entry = g_hash_table_lookup(handler->priv->config, key);
  g_return_if_fail(entry != NULL);

  g_value_copy(value, &entry->value);
  g_param_value_validate(entry->pspec, &entry->value);

  g_mutex_unlock(handler->priv->config_mutex);
}

#define CONFIG_GETTER(_name, _gtype, _ctype, _getter, _failret)		\
  _ctype st_handler_config_get_ ## _name (STHandler *handler,		\
					  const char *key)		\
  {									\
    const ConfigEntry *entry;						\
    _ctype value;							\
									\
    g_return_val_if_fail(ST_IS_HANDLER(handler), _failret);		\
    g_return_val_if_fail(key != NULL, _failret);			\
									\
    g_mutex_lock(handler->priv->config_mutex);				\
									\
    entry = g_hash_table_lookup(handler->priv->config, key);		\
    g_return_val_if_fail(entry != NULL, _failret);			\
    g_return_val_if_fail(G_VALUE_HOLDS(&entry->value, _gtype), _failret); \
									\
    value = _getter(&entry->value);					\
									\
    g_mutex_unlock(handler->priv->config_mutex);			\
									\
    return value;							\
  }

#define CONFIG_SETTER(_name, _gtype, _ctype, _setter)		\
  void st_handler_config_set_ ## _name (STHandler *handler,	\
					const char *key,	\
					_ctype value)		\
  {								\
    ConfigEntry *entry;						\
    								\
    g_return_if_fail(ST_IS_HANDLER(handler));			\
    g_return_if_fail(key != NULL);				\
    								\
    g_mutex_lock(handler->priv->config_mutex);			\
    								\
    entry = g_hash_table_lookup(handler->priv->config, key);	\
    g_return_if_fail(entry != NULL);				\
    g_return_if_fail(G_VALUE_HOLDS(&entry->value, _gtype));	\
    								\
    _setter(&entry->value, value);				\
    g_param_value_validate(entry->pspec, &entry->value);	\
    								\
    g_mutex_unlock(handler->priv->config_mutex);		\
  }

CONFIG_GETTER(boolean, G_TYPE_BOOLEAN, gboolean, g_value_get_boolean, FALSE)
CONFIG_SETTER(boolean, G_TYPE_BOOLEAN, gboolean, g_value_set_boolean)

CONFIG_GETTER(int, G_TYPE_INT, int, g_value_get_int, 0)
CONFIG_SETTER(int, G_TYPE_INT, int, g_value_set_int)

CONFIG_GETTER(uint, G_TYPE_UINT, unsigned int, g_value_get_uint, 0)
CONFIG_SETTER(uint, G_TYPE_UINT, unsigned int, g_value_set_uint)

CONFIG_GETTER(double, G_TYPE_DOUBLE, double, g_value_get_double, 0)
CONFIG_SETTER(double, G_TYPE_DOUBLE, double, g_value_set_double)

CONFIG_GETTER(string, G_TYPE_STRING, char *, g_value_dup_string, NULL)
CONFIG_SETTER(string, G_TYPE_STRING, const char *, g_value_set_string)

CONFIG_GETTER(value_array, G_TYPE_VALUE_ARRAY, GValueArray *, g_value_dup_boxed, NULL)
CONFIG_SETTER(value_array, G_TYPE_VALUE_ARRAY, const GValueArray *, g_value_set_boxed)

#undef CONFIG_GETTER
#undef CONFIG_SETTER

/*** private implementation **************************************************/

static void
st_handler_class_init (STHandlerClass *class)
{
  GObjectClass *object_class = G_OBJECT_CLASS(class);

  parent_class = g_type_class_peek_parent(class);

  g_type_class_add_private(class, sizeof(STHandlerPrivate));

  object_class->constructor = st_handler_constructor;
  object_class->set_property = st_handler_set_property;

  g_object_class_install_property(object_class,
				  PROP_NAME,
				  g_param_spec_string("name",
						      _("Name"),
						      _("The handler name"),
						      NULL,
						      G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));

  /* private properties */

  g_object_class_install_property(object_class,
				  PROP_DUMMY,
				  g_param_spec_boolean("dummy",
						       NULL,
						       NULL,
						       FALSE,
						       G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
}

static void
st_handler_init (STHandler *handler)
{
  handler->priv = G_TYPE_INSTANCE_GET_PRIVATE(handler, ST_TYPE_HANDLER, STHandlerPrivate);
}

static GObject *
st_handler_constructor (GType type,
			unsigned int n_construct_properties,
			GObjectConstructParam *construct_params)
{
  GObject *object; 
  STHandler *handler;

  object = parent_class->constructor(type, n_construct_properties, construct_params);
  handler = ST_HANDLER(object);

  handler->priv->paned_position = 150;

  handler->priv->config = g_hash_table_new(g_str_hash, g_str_equal);
  handler->priv->config_mutex = g_mutex_new();

  if (! handler->priv->dummy)
    {
      handler->priv->streams = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_object_unref);
      handler->priv->streams_mutex = g_mutex_new();

      /* provide handy defaults for some mandatory callbacks */

      st_handler_bind(handler, ST_HANDLER_EVENT_CATEGORY_NEW, st_handler_category_new_default_cb, NULL);
      st_handler_bind(handler, ST_HANDLER_EVENT_CATEGORY_FREE, st_handler_category_free_default_cb, NULL);
      st_handler_bind(handler, ST_HANDLER_EVENT_STREAM_NEW, st_handler_stream_new_default_cb, NULL);
      st_handler_bind(handler, ST_HANDLER_EVENT_STREAM_FREE, st_handler_stream_free_default_cb, NULL);
    }
  
  return object;
}

static void
st_handler_set_property (GObject *object,
			 unsigned int prop_id,
			 const GValue *value,
			 GParamSpec *pspec)
{
  STHandler *handler = ST_HANDLER(object);

  switch (prop_id)
    {
    case PROP_NAME:
      handler->priv->name = g_value_dup_string(value);
      break;

    case PROP_DUMMY:
      handler->priv->dummy = g_value_get_boolean(value);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
      break;
    }
}

STHandler *
st_handler_new_dummy (const char *name)
{
  g_return_val_if_fail(name != NULL, NULL);

  return g_object_new(ST_TYPE_HANDLER,
		      "name", name,
		      "dummy", TRUE,
		      NULL);
}

void
st_handler_complete (STHandler *handler)
{
  g_return_if_fail(ST_IS_HANDLER(handler));

  if (! handler->priv->stock_categories)
    {
      STCategoryBag *category_bag;

      handler->priv->stock_categories = g_node_new(NULL);

      category_bag = st_category_bag_new(handler);
      ST_CATEGORY(category_bag)->name = g_strdup(ST_CATEGORY_BAG_MAIN);

      g_node_append_data(handler->priv->stock_categories, category_bag);
    }

  handler->priv->categories = st_category_store_new(handler->priv->stock_categories);
}

gboolean
st_handler_validate (STHandler *handler, GError **err)
{
  if (! handler)
    {
      g_set_error(err, 0, 0, _("passed handler is NULL"));
      return FALSE;
    }
  if (! ST_IS_HANDLER(handler))
    {
      g_set_error(err, 0, 0, _("passed handler is not a STHandler object"));
      return FALSE;
    }
  if (! handler->priv->label)
    {
      g_set_error(err, 0, 0, _("handler label is not set"));
      return FALSE;
    }
  if (! st_handler_event_is_bound(handler, ST_HANDLER_EVENT_STREAM_NEW))
    {
      g_set_error(err, 0, 0, _("ST_HANDLER_EVENT_STREAM_NEW is not bound"));
      return FALSE;
    }
  if (! st_handler_event_is_bound(handler, ST_HANDLER_EVENT_STREAM_FIELD_GET))
    {
      g_set_error(err, 0, 0, _("ST_HANDLER_EVENT_STREAM_FIELD_GET is not bound"));
      return FALSE;
    }
  if (! st_handler_event_is_bound(handler, ST_HANDLER_EVENT_STREAM_FIELD_SET))
    {
      g_set_error(err, 0, 0, _("ST_HANDLER_EVENT_STREAM_FIELD_SET is not bound"));
      return FALSE;
    }
  if (! st_handler_event_is_bound(handler, ST_HANDLER_EVENT_STREAM_MODIFY))
    {
      GSList *l;

      SG_LIST_FOREACH(l, handler->priv->fields)
        {
	  STHandlerField *field = l->data;

	  if (ST_HANDLER_FIELD_IS_EDITABLE(field))
	    {
	      g_set_error(err, 0, 0, _("ST_HANDLER_EVENT_STREAM_MODIFY is not bound, but field \"%s\" is editable"), st_handler_field_get_label(field));
	      return FALSE;
	    }
	}
    }
  if (! st_handler_event_is_bound(handler, ST_HANDLER_EVENT_STREAM_FREE))
    {
      g_set_error(err, 0, 0, _("ST_HANDLER_EVENT_STREAM_FREE is not bound"));
      return FALSE;
    }
  if (! st_handler_event_is_bound(handler, ST_HANDLER_EVENT_CATEGORY_NEW))
    {
      g_set_error(err, 0, 0, _("ST_HANDLER_EVENT_CATEGORY_NEW is not bound"));
      return FALSE;
    }
  if (! st_handler_event_is_bound(handler, ST_HANDLER_EVENT_CATEGORY_FREE))
    {
      g_set_error(err, 0, 0, _("ST_HANDLER_EVENT_CATEGORY_FREE is not bound"));
      return FALSE;
    }
  /*
   * If handler->priv->stock_categories is NULL, we'll add the main
   * category in st_handler_complete().
   */
  if (handler->priv->stock_categories && ! st_handler_get_stock_category(handler, ST_CATEGORY_BAG_MAIN))
    {
      g_set_error(err, 0, 0, _("main category is missing"));
      return FALSE;
    }
  
  return TRUE;
}

gboolean
st_handler_reload (STHandler *handler,
		   STCategoryBag *category_bag,
		   GNode **categories,
		   STStreamStore **stream_store,
		   GError **err)
{
  gboolean status;

  g_return_val_if_fail(ST_IS_HANDLER(handler), FALSE);
  g_return_val_if_fail(ST_IS_CATEGORY_BAG(category_bag), FALSE);
  g_return_val_if_fail(categories != NULL, FALSE);
  g_return_val_if_fail(stream_store != NULL, FALSE);

  if (st_handler_event_is_bound(handler, ST_HANDLER_EVENT_RELOAD_MULTIPLE))
    {
      GHashTable *stream_lists;

      status = st_handler_reload_multiple(handler,
					  categories,
					  &stream_lists,
					  err);
      if (status)
	{
	  g_hash_table_foreach(stream_lists, st_handler_reload_multiple_cb, handler);
	  g_hash_table_destroy(stream_lists);

	  *stream_store = st_handler_get_streams(handler, ST_CATEGORY(category_bag)->name);
	}
    }
  else
    {
      GSList *streams;
      
      status = st_handler_reload_single(handler,
					category_bag,
					categories,
					&streams,
					err);
      if (status)
	{
	  *stream_store = st_stream_store_new(handler);
	  st_stream_store_append_list(*stream_store, streams);
	  sg_objects_free(streams);
	  
	  st_stream_store_touch(*stream_store);
	  st_handler_set_streams(handler, ST_CATEGORY(category_bag)->name, *stream_store);
	}
    }

  return status;
}

static void
st_handler_reload_multiple_cb (gpointer key,
			       gpointer value,
			       gpointer user_data)
{
  const char *category_name = key;
  GSList *streams = value;
  STHandler *handler = user_data;
  STStreamStore *stream_store;

  stream_store = st_stream_store_new(handler);
  st_stream_store_append_list(stream_store, streams);
  st_stream_store_touch(stream_store);

  st_handler_set_streams(handler, category_name, stream_store);
  g_object_unref(stream_store);
}

static gboolean
st_handler_reload_single (STHandler *handler,
			  STCategoryBag *category_bag,
			  GNode **category_bags,
			  GSList **stream_bags,
			  GError **err)
{
  gboolean status;
  GNode *categories = NULL;
  GList *streams = NULL;

  g_return_val_if_fail(ST_IS_HANDLER(handler), FALSE);
  g_return_val_if_fail(ST_IS_CATEGORY_BAG(category_bag), FALSE);
  g_return_val_if_fail(category_bags != NULL, FALSE);
  g_return_val_if_fail(stream_bags != NULL, FALSE);

  status = st_handler_event_reload(handler,
				   ST_CATEGORY(category_bag),
				   &categories,
				   &streams,
				   err);
  
  if (status)
    {
      *category_bags = st_handler_categories_to_bags(handler, categories);
      *stream_bags = st_handler_streams_to_bags(handler, streams);

      if (categories) g_node_destroy(categories);
      g_list_free(streams);
    }
  else
    {
      st_handler_categories_free(handler, categories);
      st_handler_streams_free(handler, streams);
    }
  
  return status;
}

static gboolean
st_handler_reload_multiple (STHandler *handler,
			    GNode **category_bags,
			    GHashTable **stream_bag_lists,
			    GError **err)
{
  gboolean status;
  GNode *categories = NULL;
  GHashTable *stream_lists = NULL;

  g_return_val_if_fail(ST_IS_HANDLER(handler), FALSE);
  g_return_val_if_fail(category_bags != NULL, FALSE);
  g_return_val_if_fail(stream_bag_lists != NULL, FALSE);

  status = st_handler_event_reload_multiple(handler,
					    &categories,
					    &stream_lists,
					    err);

  if (status)
    {
      *category_bags = st_handler_categories_to_bags(handler, categories);
      *stream_bag_lists = st_handler_stream_lists_to_bags(handler, stream_lists);

      if (categories) g_node_destroy(categories);
      g_hash_table_destroy(stream_lists);
    }
  else
    {
      st_handler_categories_free(handler, categories);
      st_handler_stream_lists_free(handler, stream_lists);
    }

  return status;
}

static GNode *
st_handler_categories_to_bags (STHandler *handler, GNode *categories)
{
  GNode *copy;

  g_return_val_if_fail(ST_IS_HANDLER(handler), NULL);

  if (categories)
    {
      copy = g_node_copy(categories);
      g_node_traverse(copy,
		      G_PRE_ORDER,
		      G_TRAVERSE_ALL,
		      -1,
		      st_handler_categories_to_bags_cb,
		      handler);
    }
  else
    copy = g_node_new(NULL);

  return copy;
}

static gboolean
st_handler_categories_to_bags_cb (GNode *node, gpointer data)
{
  if (node->data)
    node->data = st_category_bag_new_from_category(data, node->data);

  return FALSE;			/* continue */
}

static void
st_handler_categories_free (STHandler *handler, GNode *categories)
{
  g_return_if_fail(ST_IS_HANDLER(handler));

  if (categories)
    {
      g_node_traverse(categories,
		      G_PRE_ORDER,
		      G_TRAVERSE_ALL,
		      -1,
		      st_handler_categories_free_cb,
		      handler);
      g_node_destroy(categories);
    }
}

static gboolean
st_handler_categories_free_cb (GNode *node, gpointer data)
{
  if (node->data)
    {
      STHandler *handler = data;
      st_handler_event_category_free(handler, node->data);
    }

  return FALSE;			/* continue */
}

static GSList *
st_handler_streams_to_bags (STHandler *handler, GList *streams)
{
  GSList *bags = NULL;
  GList *l;

  g_return_val_if_fail(ST_IS_HANDLER(handler), NULL);

  SG_LIST_FOREACH(l, streams)
    {
      STStreamBag *bag;

      bag = st_stream_bag_new_from_stream(handler, l->data);
      bags = g_slist_append(bags, bag);
    }

  return bags;
}

static void
st_handler_streams_free (STHandler *handler, GList *streams)
{
  GList *l;

  g_return_if_fail(ST_IS_HANDLER(handler));

  SG_LIST_FOREACH(l, streams)
    st_handler_event_stream_free(handler, l->data);
  g_list_free(streams);
}

static GHashTable *
st_handler_stream_lists_to_bags (STHandler *handler, GHashTable *stream_lists)
{
  StreamListsToBagsInfo info;

  g_return_val_if_fail(ST_IS_HANDLER(handler), NULL);

  info.handler = handler;
  info.stream_bag_lists = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify) sg_objects_free);

  if (stream_lists)
    g_hash_table_foreach(stream_lists, st_handler_stream_lists_to_bags_cb, &info);
  
  return info.stream_bag_lists;
}

static void
st_handler_stream_lists_to_bags_cb (gpointer key,
				    gpointer value,
				    gpointer user_data)
{
  const char *category_name = key;
  GList *streams = value;
  GSList *stream_bags;
  StreamListsToBagsInfo *info = user_data;

  stream_bags = st_handler_streams_to_bags(info->handler, streams);
  g_list_free(streams);

  g_hash_table_insert(info->stream_bag_lists, g_strdup(category_name), stream_bags);
}

static void
st_handler_stream_lists_free (STHandler *handler, GHashTable *stream_lists)
{
  g_return_if_fail(ST_IS_HANDLER(handler));
  
  if (stream_lists)
    {
      g_hash_table_foreach(stream_lists, st_handler_stream_lists_free_cb, handler);
      g_hash_table_destroy(stream_lists);
    }
}

static void
st_handler_stream_lists_free_cb (gpointer key,
				 gpointer value,
				 gpointer user_data)
{
  GList *streams = value;
  STHandler *handler = user_data;

  st_handler_streams_free(handler, streams);
}

STCategoryStore *
st_handler_get_categories (STHandler *handler)
{
  g_return_val_if_fail(ST_IS_HANDLER(handler), NULL);
  g_return_val_if_fail(handler->priv->categories != NULL, NULL);

  return g_object_ref(handler->priv->categories);
}

void
st_handler_set_streams (STHandler *handler,
			const char *category_name,
			STStreamStore *streams)
{
  g_return_if_fail(ST_IS_HANDLER(handler));
  g_return_if_fail(category_name != NULL);
  g_return_if_fail(ST_IS_STREAM_STORE(streams));

  g_mutex_lock(handler->priv->streams_mutex);
  g_hash_table_insert(handler->priv->streams,
		      g_strdup(category_name),
		      g_object_ref(streams));
  g_mutex_unlock(handler->priv->streams_mutex);
}

STStreamStore *
st_handler_get_streams (STHandler *handler, const char *category_name)
{
  STStreamStore *store;

  g_return_val_if_fail(ST_IS_HANDLER(handler), NULL);
  g_return_val_if_fail(category_name != NULL, NULL);

  g_mutex_lock(handler->priv->streams_mutex);
  store = g_hash_table_lookup(handler->priv->streams, category_name);
  if (store)
    g_object_ref(store);
  g_mutex_unlock(handler->priv->streams_mutex);

  return store;
}

STCategoryBag *
st_handler_get_stock_category (STHandler *handler, const char *name)
{
  GetStockCategoryInfo info = { name, NULL };

  g_return_val_if_fail(ST_IS_HANDLER(handler), NULL);
  g_return_val_if_fail(name != NULL, NULL);

  g_node_traverse(handler->priv->stock_categories,
		  G_PRE_ORDER,
		  G_TRAVERSE_ALL,
		  -1,
		  st_handler_get_stock_category_cb,
		  &info);

  return info.category_bag;
}

static gboolean
st_handler_get_stock_category_cb (GNode *node, gpointer data)
{
  if (node->data)
    {
      GetStockCategoryInfo *info = data;
      STCategoryBag *bag = node->data;
      
      if (ST_CATEGORY_BAG_IS(bag, info->name))
	{
	  info->category_bag = bag;
	  return TRUE;		/* abort */
	}
    }

  return FALSE;			/* continue */
}

/*
 * If no category is selected, select the __main category (if there is
 * no __main category, abort).
 */
void
st_handler_select_category (STHandler *handler)
{
  STCategoryBag *category_bag = NULL;

  g_return_if_fail(ST_IS_HANDLER(handler));

  category_bag = st_handler_get_selected_category(handler);
  if (category_bag)
    g_object_unref(category_bag); /* we're done */
  else
    {
      category_bag = st_handler_get_stock_category(handler, ST_CATEGORY_BAG_MAIN);

      g_assert(category_bag != NULL);
      SG_SET_FLAGS(category_bag->flags, ST_CATEGORY_BAG_SELECTED);
    }
}

STCategoryBag *
st_handler_get_selected_category (STHandler *handler)
{
  STCategoryBag *selected_category = NULL;

  g_return_val_if_fail(ST_IS_HANDLER(handler), NULL);

  st_category_store_foreach(handler->priv->categories,
			    st_handler_get_selected_category_cb,
			    &selected_category);
  
  return selected_category;
}

static gboolean
st_handler_get_selected_category_cb (STCategoryStore *store,
				     STCategoryBag *bag,
				     STCategoryBag *parent,
				     gpointer data)
{
  if (bag->flags & ST_CATEGORY_BAG_SELECTED)
    {
      STCategoryBag **selected = data;

      *selected = g_object_ref(bag);
      return TRUE;		/* stop */
    }
  else
    return FALSE;		/* continue */
}

gboolean
st_handler_event_is_bound (STHandler *handler, STHandlerEvent event)
{
  g_return_val_if_fail(ST_IS_HANDLER(handler), FALSE);
  g_return_val_if_fail(event >= 0 && event < ST_HANDLER_N_EVENTS, FALSE);

  return handler->priv->callbacks[event].cb != NULL;
}

gboolean
st_handler_is_dummy (STHandler *handler)
{
  g_return_val_if_fail(ST_IS_HANDLER(handler), FALSE);

  return handler->priv->dummy;
}

const char *
st_handler_get_name (STHandler *handler)
{
  g_return_val_if_fail(ST_IS_HANDLER(handler), NULL);

  return handler->priv->name;
}

const char *
st_handler_get_label (STHandler *handler)
{
  g_return_val_if_fail(ST_IS_HANDLER(handler), NULL);

  return handler->priv->label;
}

const char *
st_handler_get_description (STHandler *handler)
{
  g_return_val_if_fail(ST_IS_HANDLER(handler), NULL);

  return handler->priv->description;
}

const char *
st_handler_get_home (STHandler *handler)
{
  g_return_val_if_fail(ST_IS_HANDLER(handler), NULL);

  return handler->priv->home;
}

GdkPixbuf *
st_handler_get_pixbuf (STHandler *handler)
{
  g_return_val_if_fail(ST_IS_HANDLER(handler), NULL);

  return handler->priv->pixbuf;
}

unsigned int
st_handler_get_flags (STHandler *handler)
{
  g_return_val_if_fail(ST_IS_HANDLER(handler), 0);

  return handler->priv->flags;
}

int
st_handler_get_stream_version (STHandler *handler)
{
  g_return_val_if_fail(ST_IS_HANDLER(handler), 0);

  return handler->priv->stream_version;
}

void
st_handler_set_paned_position (STHandler *handler, int paned_position)
{
  g_return_if_fail(ST_IS_HANDLER(handler));

  handler->priv->paned_position = paned_position;
}

int
st_handler_get_paned_position (STHandler *handler)
{
  g_return_val_if_fail(ST_IS_HANDLER(handler), -1);

  return handler->priv->paned_position;
}

GSList *
st_handler_get_fields (STHandler *handler)
{
  g_return_val_if_fail(ST_IS_HANDLER(handler), NULL);

  return handler->priv->fields;
}

int
st_handler_count_fields (STHandler *handler, unsigned int flags)
{
  int count = 0;
  GSList *l;

  SG_LIST_FOREACH(l, handler->priv->fields)
    if (st_handler_field_get_flags(l->data) & flags)
      count++;

  return count;
}

void
st_handler_set_fields_sort_index (STHandler *handler, int sort_index)
{
  g_return_if_fail(ST_IS_HANDLER(handler));

  handler->priv->fields_sort_index = sort_index;
}

int
st_handler_get_fields_sort_index (STHandler *handler)
{
  g_return_val_if_fail(ST_IS_HANDLER(handler), -1);

  return handler->priv->fields_sort_index;
}

void
st_handler_set_fields_sort_order (STHandler *handler, int sort_order)
{
  g_return_if_fail(ST_IS_HANDLER(handler));

  handler->priv->fields_sort_order = sort_order;
}

int
st_handler_get_fields_sort_order (STHandler *handler)
{
  g_return_val_if_fail(ST_IS_HANDLER(handler), -1);

  return handler->priv->fields_sort_order;
}

/*** events ******************************************************************/

gboolean
st_handler_event_reload (STHandler *handler,
			 STCategory *category,
			 GNode **categories,
			 GList **streams,
			 GError **err)
{
  STHandlerCallback *callback = &handler->priv->callbacks[ST_HANDLER_EVENT_RELOAD];
  STHandlerReloadCallback cb = callback->cb;

  g_return_val_if_fail(cb != NULL, FALSE);
  return cb(category, categories, streams, callback->data, err);
}

gboolean
st_handler_event_reload_multiple (STHandler *handler,
				  GNode **categories,
				  GHashTable **streams,
				  GError **err)
{
  STHandlerCallback *callback = &handler->priv->callbacks[ST_HANDLER_EVENT_RELOAD_MULTIPLE];
  STHandlerReloadMultipleCallback cb = callback->cb;

  g_return_val_if_fail(cb != NULL, FALSE);
  return cb(categories, streams, callback->data, err);
}

STCategory *
st_handler_event_category_new (STHandler *handler)
{
  STHandlerCallback *callback = &handler->priv->callbacks[ST_HANDLER_EVENT_CATEGORY_NEW];
  STCategoryNewCallback cb = callback->cb;

  g_return_val_if_fail(cb != NULL, NULL);
  return cb(callback->data);
}

void
st_handler_event_category_free (STHandler *handler, STCategory *category)
{
  STHandlerCallback *callback = &handler->priv->callbacks[ST_HANDLER_EVENT_CATEGORY_FREE];
  STCategoryFreeCallback cb = callback->cb;

  g_return_if_fail(cb != NULL);
  cb(category, callback->data);
}

STStream *
st_handler_event_stream_new (STHandler *handler)
{
  STHandlerCallback *callback = &handler->priv->callbacks[ST_HANDLER_EVENT_STREAM_NEW];
  STStreamNewCallback cb = callback->cb;

  g_return_val_if_fail(cb != NULL, NULL);
  return cb(callback->data);
}

void
st_handler_event_stream_field_get (STHandler *handler,
				   STStream *stream,
				   STHandlerField *field,
				   GValue *value)
{
  STHandlerCallback *callback = &handler->priv->callbacks[ST_HANDLER_EVENT_STREAM_FIELD_GET];
  STStreamFieldGetCallback cb = callback->cb;

  g_return_if_fail(cb != NULL);
  cb(stream, field, value, callback->data);
}

void
st_handler_event_stream_field_set (STHandler *handler,
				   STStream *stream,
				   STHandlerField *field,
				   const GValue *value)
{
  STHandlerCallback *callback = &handler->priv->callbacks[ST_HANDLER_EVENT_STREAM_FIELD_SET];
  STStreamFieldSetCallback cb = callback->cb;

  g_return_if_fail(cb != NULL);
  cb(stream, field, value, callback->data);
}

void
st_handler_event_stream_stock_field_get (STHandler *handler,
					 STStream *stream,
					 STHandlerStockField stock_field,
					 GValue *value)
{
  STHandlerCallback *callback = &handler->priv->callbacks[ST_HANDLER_EVENT_STREAM_STOCK_FIELD_GET];
  STStreamStockFieldGetCallback cb = callback->cb;

  g_return_if_fail(cb != NULL);
  cb(stream, stock_field, value, callback->data);
}

gboolean
st_handler_event_stream_modify (STHandler *handler,
				STStream *stream,
				GSList *fields,
				GSList *values,
				GError **err)
{
  STHandlerCallback *callback = &handler->priv->callbacks[ST_HANDLER_EVENT_STREAM_MODIFY];
  STStreamModifyCallback cb = callback->cb;

  g_return_val_if_fail(cb != NULL, FALSE);
  return cb(stream, fields, values, callback->data, err);
}

gboolean
st_handler_event_stream_delete (STHandler *handler,
				STStream *stream,
				GError **err)
{
  STHandlerCallback *callback = &handler->priv->callbacks[ST_HANDLER_EVENT_STREAM_DELETE];
  STStreamDeleteCallback cb = callback->cb;

  g_return_val_if_fail(cb != NULL, FALSE);
  return cb(stream, callback->data, err);
}

void
st_handler_event_stream_free (STHandler *handler, STStream *stream)
{
  STHandlerCallback *callback = &handler->priv->callbacks[ST_HANDLER_EVENT_STREAM_FREE];
  STStreamFreeCallback cb = callback->cb;

  g_return_if_fail(cb != NULL);
  cb(stream, callback->data);
}

gboolean
st_handler_event_stream_resolve (STHandler *handler,
				 STStream *stream,
				 GError **err)
{
  STHandlerCallback *callback = &handler->priv->callbacks[ST_HANDLER_EVENT_STREAM_RESOLVE];
  STStreamResolveCallback cb = callback->cb;
  
  g_return_val_if_fail(cb != NULL, FALSE);
  return cb(stream, callback->data, err);
}

gboolean
st_handler_event_stream_tune_in (STHandler *handler,
				 STStream *stream,
				 GError **err)
{
  STHandlerCallback *callback = &handler->priv->callbacks[ST_HANDLER_EVENT_STREAM_TUNE_IN];
  STStreamTuneInCallback cb = callback->cb;
  
  g_return_val_if_fail(cb != NULL, FALSE);
  return cb(stream, callback->data, err);
}

gboolean
st_handler_event_stream_tune_in_multiple (STHandler *handler,
					  GSList *streams,
					  GError **err)
{
  STHandlerCallback *callback = &handler->priv->callbacks[ST_HANDLER_EVENT_STREAM_TUNE_IN_MULTIPLE];
  STStreamTuneInMultipleCallback cb = callback->cb;
  
  g_return_val_if_fail(cb != NULL, FALSE);
  return cb(streams, callback->data, err);
}

gboolean
st_handler_event_stream_record (STHandler *handler,
				STStream *stream,
				GError **err)
{
  STHandlerCallback *callback = &handler->priv->callbacks[ST_HANDLER_EVENT_STREAM_RECORD];
  STStreamRecordCallback cb = callback->cb;

  g_return_val_if_fail(cb != NULL, FALSE);
  return cb(stream, callback->data, err);
}

gboolean
st_handler_event_stream_browse (STHandler *handler,
				STStream *stream,
				GError **err)
{
  STHandlerCallback *callback = &handler->priv->callbacks[ST_HANDLER_EVENT_STREAM_BROWSE];
  STStreamBrowseCallback cb = callback->cb;

  g_return_val_if_fail(cb != NULL, FALSE);
  return cb(stream, callback->data, err);
}

gpointer
st_handler_event_thread_begin (STHandler *handler)
{
  STHandlerCallback *callback = &handler->priv->callbacks[ST_HANDLER_EVENT_THREAD_BEGIN];
  STThreadBeginCallback cb = callback->cb;

  g_return_val_if_fail(cb != NULL, NULL);
  return cb(callback->data);
}

void
st_handler_event_thread_end (STHandler *handler, gpointer thread_data)
{
  STHandlerCallback *callback = &handler->priv->callbacks[ST_HANDLER_EVENT_THREAD_END];
  STThreadEndCallback cb = callback->cb;

  g_return_if_fail(cb != NULL);
  cb(thread_data, callback->data);
}

GtkWidget *
st_handler_event_preferences_widget_new (STHandler *handler)
{
  STHandlerCallback *callback = &handler->priv->callbacks[ST_HANDLER_EVENT_PREFERENCES_WIDGET_NEW];
  STHandlerPreferencesWidgetNewCallback cb = callback->cb;

  g_return_val_if_fail(cb != NULL, NULL);
  return cb(callback->data);
}
