/* The GIMP -- an image manipulation program
 * Copyright (C) 1995-1997 Spencer Kimball and Peter Mattis
 *
 * gimpcontainer.c
 * Copyright (C) 2001 Michael Natterer <mitch@gimp.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#include "config.h"

#include <string.h>

#include <glib-object.h>

#include "core-types.h"

#include "gimp.h"
#include "gimpcontainer.h"
#include "gimpmarshal.h"

#include "config/gimpconfig.h"
#include "config/gimpconfig-deserialize.h"
#include "config/gimpconfigwriter.h"
#include "config/gimpscanner.h"


/* #define DEBUG_CONTAINER */

#ifdef DEBUG_CONTAINER
#define D(stmnt) stmnt
#else
#define D(stmnt)
#endif


typedef struct _GimpContainerHandler
{
  gchar     *signame;
  GCallback  callback;
  gpointer   callback_data;

  GQuark     quark;  /*  used to attach the signal id's of child signals  */
} GimpContainerHandler;


enum
{
  ADD,
  REMOVE,
  REORDER,
  FREEZE,
  THAW,
  LAST_SIGNAL
};

enum
{
  PROP_0,
  PROP_CHILDREN_TYPE,
  PROP_POLICY
};


/*  local function prototypes  */

static void   gimp_container_class_init          (GimpContainerClass  *klass);
static void   gimp_container_init                (GimpContainer       *container);
static void   gimp_container_config_iface_init   (GimpConfigInterface *config_iface);

static void       gimp_container_dispose         (GObject            *object);

static void       gimp_container_set_property    (GObject            *object,
                                                  guint               property_id,
                                                  const GValue       *value,
                                                  GParamSpec         *pspec);
static void       gimp_container_get_property    (GObject            *object,
                                                  guint               property_id,
                                                  GValue             *value,
                                                  GParamSpec         *pspec);

static gint64     gimp_container_get_memsize     (GimpObject         *object,
                                                  gint64             *gui_size);

static gboolean   gimp_container_serialize       (GimpConfig         *config,
                                                  GimpConfigWriter   *writer,
                                                  gpointer            data);
static gboolean   gimp_container_deserialize     (GimpConfig         *config,
                                                  GScanner           *scanner,
                                                  gint                nest_level,
                                                  gpointer            data);

static void   gimp_container_disconnect_callback (GimpObject         *object,
						  gpointer            data);


static guint container_signals[LAST_SIGNAL] = { 0, };

static GimpObjectClass *parent_class = NULL;


GType
gimp_container_get_type (void)
{
  static GType container_type = 0;

  if (! container_type)
    {
      static const GTypeInfo container_info =
      {
        sizeof (GimpContainerClass),
	(GBaseInitFunc) NULL,
	(GBaseFinalizeFunc) NULL,
	(GClassInitFunc) gimp_container_class_init,
	NULL,		/* class_finalize */
	NULL,		/* class_data     */
	sizeof (GimpContainer),
	0,              /* n_preallocs    */
	(GInstanceInitFunc) gimp_container_init,
      };
      static const GInterfaceInfo config_iface_info =
      {
        (GInterfaceInitFunc) gimp_container_config_iface_init,
        NULL,           /* iface_finalize */
        NULL            /* iface_data     */
      };

      container_type = g_type_register_static (GIMP_TYPE_OBJECT,
					       "GimpContainer",
                                               &container_info, 0);

      g_type_add_interface_static (container_type, GIMP_TYPE_CONFIG,
                                   &config_iface_info);
    }

  return container_type;
}

static void
gimp_container_class_init (GimpContainerClass *klass)
{
  GObjectClass    *object_class;
  GimpObjectClass *gimp_object_class;

  object_class      = G_OBJECT_CLASS (klass);
  gimp_object_class = GIMP_OBJECT_CLASS (klass);

  parent_class = g_type_class_peek_parent (klass);

  container_signals[ADD] =
    g_signal_new ("add",
		  G_TYPE_FROM_CLASS (klass),
		  G_SIGNAL_RUN_FIRST,
		  G_STRUCT_OFFSET (GimpContainerClass, add),
		  NULL, NULL,
		  gimp_marshal_VOID__OBJECT,
		  G_TYPE_NONE, 1,
		  GIMP_TYPE_OBJECT);

  container_signals[REMOVE] =
    g_signal_new ("remove",
		  G_TYPE_FROM_CLASS (klass),
		  G_SIGNAL_RUN_FIRST,
		  G_STRUCT_OFFSET (GimpContainerClass, remove),
		  NULL, NULL,
		  gimp_marshal_VOID__OBJECT,
		  G_TYPE_NONE, 1,
		  GIMP_TYPE_OBJECT);

  container_signals[REORDER] =
    g_signal_new ("reorder",
		  G_TYPE_FROM_CLASS (klass),
		  G_SIGNAL_RUN_FIRST,
		  G_STRUCT_OFFSET (GimpContainerClass, reorder),
		  NULL, NULL,
		  gimp_marshal_VOID__OBJECT_INT,
		  G_TYPE_NONE, 2,
		  GIMP_TYPE_OBJECT,
		  G_TYPE_INT);

  container_signals[FREEZE] =
    g_signal_new ("freeze",
		  G_TYPE_FROM_CLASS (klass),
		  G_SIGNAL_RUN_LAST,
		  G_STRUCT_OFFSET (GimpContainerClass, freeze),
		  NULL, NULL,
		  gimp_marshal_VOID__VOID,
		  G_TYPE_NONE, 0);

  container_signals[THAW] =
    g_signal_new ("thaw",
		  G_TYPE_FROM_CLASS (klass),
		  G_SIGNAL_RUN_LAST,
		  G_STRUCT_OFFSET (GimpContainerClass, thaw),
		  NULL, NULL,
		  gimp_marshal_VOID__VOID,
		  G_TYPE_NONE, 0);

  object_class->dispose          = gimp_container_dispose;
  object_class->set_property     = gimp_container_set_property;
  object_class->get_property     = gimp_container_get_property;

  gimp_object_class->get_memsize = gimp_container_get_memsize;

  klass->add                     = NULL;
  klass->remove                  = NULL;
  klass->reorder                 = NULL;
  klass->freeze                  = NULL;
  klass->thaw                    = NULL;

  klass->clear                   = NULL;
  klass->have                    = NULL;
  klass->foreach                 = NULL;
  klass->get_child_by_name       = NULL;
  klass->get_child_by_index      = NULL;
  klass->get_child_index         = NULL;

  g_object_class_install_property (object_class,
				   PROP_CHILDREN_TYPE,
				   g_param_spec_pointer ("children_type",
                                                         NULL, NULL,
                                                         G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));

  g_object_class_install_property (object_class,
				   PROP_POLICY,
				   g_param_spec_enum ("policy",
                                                      NULL, NULL,
                                                      GIMP_TYPE_CONTAINER_POLICY,
                                                      GIMP_CONTAINER_POLICY_STRONG,
                                                      G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
}

static void
gimp_container_init (GimpContainer *container)
{
  container->children_type = G_TYPE_NONE;
  container->policy        = GIMP_CONTAINER_POLICY_STRONG;
  container->num_children  = 0;

  container->handlers      = NULL;
  container->freeze_count  = 0;
}

static void
gimp_container_config_iface_init (GimpConfigInterface *config_iface)
{
  config_iface->serialize   = gimp_container_serialize;
  config_iface->deserialize = gimp_container_deserialize;
}

static void
gimp_container_dispose (GObject *object)
{
  GimpContainer *container = GIMP_CONTAINER (object);

  gimp_container_clear (container);

  while (container->handlers)
    gimp_container_remove_handler (container,
                                   ((GimpContainerHandler *)
                                    container->handlers->data)->quark);

  if (container->children_type != G_TYPE_NONE)
    {
      g_type_class_unref (g_type_class_peek (container->children_type));
      container->children_type = G_TYPE_NONE;
    }

  G_OBJECT_CLASS (parent_class)->dispose (object);
}

static void
gimp_container_set_property (GObject      *object,
                             guint         property_id,
                             const GValue *value,
                             GParamSpec   *pspec)
{
  GimpContainer *container = GIMP_CONTAINER (object);

  switch (property_id)
    {
    case PROP_CHILDREN_TYPE:
      container->children_type = (GType) g_value_get_pointer (value);
      g_type_class_ref (container->children_type);
      break;
    case PROP_POLICY:
      container->policy = g_value_get_enum (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static void
gimp_container_get_property (GObject    *object,
                             guint       property_id,
                             GValue     *value,
                             GParamSpec *pspec)
{
  GimpContainer *container = GIMP_CONTAINER (object);

  switch (property_id)
    {
    case PROP_CHILDREN_TYPE:
      g_value_set_pointer (value, (gpointer) container->children_type);
      break;
    case PROP_POLICY:
      g_value_set_enum (value, container->policy);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
      break;
    }
}

static gint64
gimp_container_get_memsize (GimpObject *object,
                            gint64     *gui_size)
{
  GimpContainer *container = GIMP_CONTAINER (object);
  gint64         memsize   = 0;
  GList         *list;

  for (list = container->handlers; list; list = g_list_next (list))
    {
      GimpContainerHandler *handler = list->data;

      memsize += (sizeof (GList) +
                  sizeof (GimpContainerHandler) +
                  strlen (handler->signame) + 1);
    }

  return memsize + GIMP_OBJECT_CLASS (parent_class)->get_memsize (object,
                                                                  gui_size);
}

typedef struct
{
  GimpConfigWriter *writer;
  gpointer          data;
  gboolean          success;
} SerializeData;

static void
gimp_container_serialize_foreach (GObject       *object,
                                  SerializeData *serialize_data)
{
  GimpConfigInterface *config_iface;
  const gchar         *name;

  config_iface = GIMP_CONFIG_GET_INTERFACE (object);

  if (! config_iface)
    serialize_data->success = FALSE;

  if (! serialize_data->success)
    return;

  gimp_config_writer_open (serialize_data->writer,
			   g_type_name (G_TYPE_FROM_INSTANCE (object)));

  name = gimp_object_get_name (GIMP_OBJECT (object));

  if (name)
    gimp_config_writer_string (serialize_data->writer, name);
  else
    gimp_config_writer_print (serialize_data->writer, "NULL", 4);

  serialize_data->success = config_iface->serialize (GIMP_CONFIG (object),
                                                     serialize_data->writer,
                                                     serialize_data->data);
  gimp_config_writer_close (serialize_data->writer);
}

static gboolean
gimp_container_serialize (GimpConfig       *config,
                          GimpConfigWriter *writer,
                          gpointer          data)
{
  GimpContainer *container = GIMP_CONTAINER (config);
  SerializeData  serialize_data;

  serialize_data.writer  = writer;
  serialize_data.data    = data;
  serialize_data.success = TRUE;

  gimp_container_foreach (container,
                          (GFunc) gimp_container_serialize_foreach,
                          &serialize_data);

  return serialize_data.success;
}

static gboolean
gimp_container_deserialize (GimpConfig *config,
                            GScanner   *scanner,
                            gint        nest_level,
                            gpointer    data)
{
  GimpContainer *container = GIMP_CONTAINER (config);
  GTokenType     token;

  token = G_TOKEN_LEFT_PAREN;

  while (g_scanner_peek_next_token (scanner) == token)
    {
      token = g_scanner_get_next_token (scanner);

      switch (token)
        {
        case G_TOKEN_LEFT_PAREN:
          token = G_TOKEN_IDENTIFIER;
          break;

        case G_TOKEN_IDENTIFIER:
          {
            GimpObject *child;
            GType       type;
            gchar      *name      = NULL;
            gboolean    add_child = FALSE;

            type = g_type_from_name (scanner->value.v_identifier);

            if (! type)
              {
                g_scanner_error (scanner,
                                 "unable to determine type of '%s'",
                                 scanner->value.v_identifier);
                return FALSE;
              }

            if (! g_type_is_a (type, container->children_type))
              {
                g_scanner_error (scanner,
                                 "'%s' is not a subclass of '%s'",
                                 scanner->value.v_identifier,
                                 g_type_name (container->children_type));
                return FALSE;
              }

            if (! g_type_is_a (type, GIMP_TYPE_CONFIG))
              {
                g_scanner_error (scanner,
                                 "'%s' does not implement GimpConfigInterface",
                                 scanner->value.v_identifier);
                return FALSE;
              }

            if (! gimp_scanner_parse_string (scanner, &name))
              {
                token = G_TOKEN_STRING;
                break;
              }

            if (! name)
              name = g_strdup ("");

            child = gimp_container_get_child_by_name (container, name);

            if (! child)
              {
                if (GIMP_IS_GIMP (data))
                  {
                    child = g_object_new (type,
                                          "name", name,
                                          "gimp", data, NULL);
                  }
                else
                  {
                    child = g_object_new (type,
                                          "name", name, NULL);
                 }

                add_child = TRUE;
              }

            g_free (name);

            if (! GIMP_CONFIG_GET_INTERFACE (child)->deserialize (GIMP_CONFIG (child),
                                                                  scanner,
                                                                  nest_level + 1,
                                                                  NULL))
              {
                if (add_child)
                  g_object_unref (child);

                /*  warning should be already set by child  */
                return FALSE;
              }

            if (add_child)
              {
                gimp_container_add (container, child);

                if (container->policy == GIMP_CONTAINER_POLICY_STRONG)
                  g_object_unref (child);
              }
          }
          token = G_TOKEN_RIGHT_PAREN;
          break;

        case G_TOKEN_RIGHT_PAREN:
          token = G_TOKEN_LEFT_PAREN;
          break;

        default: /* do nothing */
          break;
        }
    }

  return gimp_config_deserialize_return (scanner, token, nest_level);
}

static void
gimp_container_disconnect_callback (GimpObject *object,
				    gpointer    data)
{
  GimpContainer *container = GIMP_CONTAINER (data);

  gimp_container_remove (container, object);
}

GType
gimp_container_children_type (const GimpContainer *container)
{
  g_return_val_if_fail (GIMP_IS_CONTAINER (container), G_TYPE_NONE);

  return container->children_type;
}

GimpContainerPolicy
gimp_container_policy (const GimpContainer *container)
{
  g_return_val_if_fail (GIMP_IS_CONTAINER (container), 0);

  return container->policy;
}

gint
gimp_container_num_children (const GimpContainer *container)
{
  g_return_val_if_fail (GIMP_IS_CONTAINER (container), 0);

  return container->num_children;
}

gboolean
gimp_container_add (GimpContainer *container,
		    GimpObject    *object)
{
  GimpContainerHandler *handler;
  GList                *list;
  gulong                handler_id;

  g_return_val_if_fail (GIMP_IS_CONTAINER (container), FALSE);
  g_return_val_if_fail (object != NULL, FALSE);
  g_return_val_if_fail (G_TYPE_CHECK_INSTANCE_TYPE (object,
						    container->children_type),
			FALSE);

  if (gimp_container_have (container, object))
    {
      g_warning ("%s: container %p already contains object %p",
		 G_STRFUNC, container, object);
      return FALSE;
    }

  for (list = container->handlers; list; list = g_list_next (list))
    {
      handler = (GimpContainerHandler *) list->data;

      handler_id = g_signal_connect (object,
				     handler->signame,
				     handler->callback,
				     handler->callback_data);

      g_object_set_qdata (G_OBJECT (object), handler->quark,
			  GUINT_TO_POINTER (handler_id));
    }

  switch (container->policy)
    {
    case GIMP_CONTAINER_POLICY_STRONG:
      g_object_ref (object);
      break;

    case GIMP_CONTAINER_POLICY_WEAK:
      g_signal_connect (object, "disconnect",
			G_CALLBACK (gimp_container_disconnect_callback),
			container);
      break;
    }

  container->num_children++;

  g_signal_emit (container, container_signals[ADD], 0, object);

  return TRUE;
}

gboolean
gimp_container_remove (GimpContainer *container,
		       GimpObject    *object)
{
  GimpContainerHandler *handler;
  GList                *list;
  gulong                handler_id;

  g_return_val_if_fail (GIMP_IS_CONTAINER (container), FALSE);
  g_return_val_if_fail (object != NULL, FALSE);
  g_return_val_if_fail (G_TYPE_CHECK_INSTANCE_TYPE (object,
						    container->children_type),
			FALSE);

  if (! gimp_container_have (container, object))
    {
      g_warning ("%s: container %p does not contain object %p",
		 G_STRFUNC, container, object);
      return FALSE;
    }

  for (list = container->handlers; list; list = g_list_next (list))
    {
      handler = (GimpContainerHandler *) list->data;

      handler_id = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (object),
                                                         handler->quark));

      if (handler_id)
	{
	  g_signal_handler_disconnect (object, handler_id);

	  g_object_set_qdata (G_OBJECT (object), handler->quark, NULL);
	}
    }

  container->num_children--;

  g_signal_emit (container, container_signals[REMOVE], 0,
		 object);

  switch (container->policy)
    {
    case GIMP_CONTAINER_POLICY_STRONG:
      g_object_unref (object);
      break;

    case GIMP_CONTAINER_POLICY_WEAK:
      g_signal_handlers_disconnect_by_func (object,
                                            gimp_container_disconnect_callback,
                                            container);
      break;
    }

  return TRUE;
}

gboolean
gimp_container_insert (GimpContainer *container,
		       GimpObject    *object,
		       gint           index)
{
  g_return_val_if_fail (GIMP_IS_CONTAINER (container), FALSE);
  g_return_val_if_fail (object != NULL, FALSE);
  g_return_val_if_fail (G_TYPE_CHECK_INSTANCE_TYPE (object,
						    container->children_type),
			FALSE);

  g_return_val_if_fail (index >= -1 &&
			index <= container->num_children, FALSE);

  if (gimp_container_have (container, object))
    {
      g_warning ("%s: container %p already contains object %p",
		 G_STRFUNC, container, object);
      return FALSE;
    }

  if (gimp_container_add (container, object))
    {
      return gimp_container_reorder (container, object, index);
    }

  return FALSE;
}

gboolean
gimp_container_reorder (GimpContainer *container,
			GimpObject    *object,
			gint           new_index)
{
  g_return_val_if_fail (GIMP_IS_CONTAINER (container), FALSE);
  g_return_val_if_fail (object != NULL, FALSE);
  g_return_val_if_fail (G_TYPE_CHECK_INSTANCE_TYPE (object,
						    container->children_type),
			FALSE);

  g_return_val_if_fail (new_index >= -1 &&
			new_index < container->num_children, FALSE);

  if (! gimp_container_have (container, object))
    {
      g_warning ("%s: container %p does not contain object %p",
		 G_STRFUNC, container, object);
      return FALSE;
    }

  if (container->num_children == 1)
    return TRUE;

  g_signal_emit (container, container_signals[REORDER], 0,
		 object, new_index);

  return TRUE;
}

void
gimp_container_freeze (GimpContainer *container)
{
  g_return_if_fail (GIMP_IS_CONTAINER (container));

  container->freeze_count++;

  if (container->freeze_count == 1)
    g_signal_emit (container, container_signals[FREEZE], 0);
}

void
gimp_container_thaw (GimpContainer *container)
{
  g_return_if_fail (GIMP_IS_CONTAINER (container));

  if (container->freeze_count > 0)
    container->freeze_count--;

  if (container->freeze_count == 0)
    g_signal_emit (container, container_signals[THAW], 0);
}

gboolean
gimp_container_frozen (GimpContainer *container)
{
  g_return_val_if_fail (GIMP_IS_CONTAINER (container), FALSE);

  return (container->freeze_count > 0) ? TRUE : FALSE;
}

void
gimp_container_clear (GimpContainer *container)
{
  g_return_if_fail (GIMP_IS_CONTAINER (container));

  if (container->num_children > 0)
    {
      gimp_container_freeze (container);
      GIMP_CONTAINER_GET_CLASS (container)->clear (container);
      gimp_container_thaw (container);
    }
}

gboolean
gimp_container_have (const GimpContainer *container,
		     GimpObject          *object)
{
  g_return_val_if_fail (GIMP_IS_CONTAINER (container), FALSE);

  if (container->num_children < 1)
    return FALSE;

  return GIMP_CONTAINER_GET_CLASS (container)->have (container, object);
}

void
gimp_container_foreach (const GimpContainer *container,
			GFunc                func,
			gpointer             user_data)
{
  g_return_if_fail (GIMP_IS_CONTAINER (container));
  g_return_if_fail (func != NULL);

  if (container->num_children > 0)
    GIMP_CONTAINER_GET_CLASS (container)->foreach (container, func, user_data);
}

GimpObject *
gimp_container_get_child_by_name (const GimpContainer *container,
				  const gchar         *name)
{
  g_return_val_if_fail (GIMP_IS_CONTAINER (container), NULL);

  if (!name)
    return NULL;

  return GIMP_CONTAINER_GET_CLASS (container)->get_child_by_name (container,
								  name);
}

GimpObject *
gimp_container_get_child_by_index (const GimpContainer *container,
				   gint                 index)
{
  g_return_val_if_fail (GIMP_IS_CONTAINER (container), NULL);

  if (index < 0 || index >= container->num_children)
    return NULL;

  return GIMP_CONTAINER_GET_CLASS (container)->get_child_by_index (container,
								   index);
}

gint
gimp_container_get_child_index (const GimpContainer *container,
				const GimpObject    *object)
{
  g_return_val_if_fail (GIMP_IS_CONTAINER (container), -1);
  g_return_val_if_fail (object != NULL, -1);
  g_return_val_if_fail (G_TYPE_CHECK_INSTANCE_TYPE (object,
						    container->children_type),
			-1);

  return GIMP_CONTAINER_GET_CLASS (container)->get_child_index (container,
								object);
}

static void
gimp_container_get_name_array_foreach_func (GimpObject   *object,
                                            gchar      ***iter)
{
  gchar **array = *iter;

  *array = g_strdup (gimp_object_get_name (object));
  (*iter)++;
}

gchar **
gimp_container_get_name_array (const GimpContainer *container,
                               gint                *length)
{
  gchar **names;
  gchar **iter;

  g_return_val_if_fail (GIMP_IS_CONTAINER (container), NULL);
  g_return_val_if_fail (length != NULL, NULL);

  *length = gimp_container_num_children (container);
  if (*length == 0)
    return NULL;

  names = iter = g_new (gchar *, *length);

  gimp_container_foreach (container,
                          (GFunc) gimp_container_get_name_array_foreach_func,
                          &iter);

  return names;
}

static void
gimp_container_add_handler_foreach_func (GimpObject           *object,
					 GimpContainerHandler *handler)
{
  gulong handler_id;

  handler_id = g_signal_connect (object,
				 handler->signame,
				 handler->callback,
				 handler->callback_data);

  g_object_set_qdata (G_OBJECT (object), handler->quark,
		      GUINT_TO_POINTER (handler_id));
}

GQuark
gimp_container_add_handler (GimpContainer *container,
			    const gchar   *signame,
			    GCallback      callback,
			    gpointer       callback_data)
{
  GimpContainerHandler *handler;
  gchar                *key;

  static gint           handler_id = 0;

  g_return_val_if_fail (GIMP_IS_CONTAINER (container), 0);
  g_return_val_if_fail (signame != NULL, 0);
  g_return_val_if_fail (callback != NULL, 0);

  if (strncmp (signame, "notify::", 8))
    g_return_val_if_fail (g_signal_lookup (signame,
                                           container->children_type), 0);

  handler = g_new0 (GimpContainerHandler, 1);

  /*  create a unique key for this handler  */
  key = g_strdup_printf ("%s-%d", signame, handler_id++);

  handler->signame       = g_strdup (signame);
  handler->callback      = callback;
  handler->callback_data = callback_data;
  handler->quark         = g_quark_from_string (key);

  D (g_print ("%s: key = %s, id = %d\n", G_STRFUNC, key, handler->quark));

  g_free (key);

  container->handlers = g_list_prepend (container->handlers, handler);

  gimp_container_foreach (container,
			  (GFunc) gimp_container_add_handler_foreach_func,
			  handler);

  return handler->quark;
}

static void
gimp_container_remove_handler_foreach_func (GimpObject           *object,
					    GimpContainerHandler *handler)
{
  gulong handler_id;

  handler_id = GPOINTER_TO_UINT (g_object_get_qdata (G_OBJECT (object),
                                                     handler->quark));

  if (handler_id)
    {
      g_signal_handler_disconnect (object, handler_id);

      g_object_set_qdata (G_OBJECT (object), handler->quark, NULL);
    }
}

void
gimp_container_remove_handler (GimpContainer *container,
			       GQuark         id)
{
  GimpContainerHandler *handler;
  GList                *list;

  g_return_if_fail (GIMP_IS_CONTAINER (container));
  g_return_if_fail (id != 0);

  for (list = container->handlers; list; list = g_list_next (list))
    {
      handler = (GimpContainerHandler *) list->data;

      if (handler->quark == id)
	break;
    }

  if (! list)
    {
      g_warning ("%s: tried to remove handler which unknown id %d",
                 G_STRFUNC, id);
      return;
    }

  D (g_print ("%s: id = %d\n", G_STRFUNC, handler->quark));

  gimp_container_foreach (container,
			  (GFunc) gimp_container_remove_handler_foreach_func,
			  handler);

  container->handlers = g_list_remove (container->handlers, handler);

  g_free (handler->signame);
  g_free (handler);
}
