/*
 *  Copyright (C) 2002-2004 Marco Pesenti Gritti
 *  Copyright (C) 2004 Christian Persch
 *
 *  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, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *  $Id$
 */

#include "config.h"

#include "egg-toolbars-model.h"
#include "eggtypebuiltins.h"
#include "eggmarshalers.h"

#include <unistd.h>
#include <string.h>
#include <libxml/tree.h>
#include <gdk/gdk.h>

static void egg_toolbars_model_finalize   (GObject               *object);

enum
{
  ITEM_ADDED,
  ITEM_REMOVED,
  TOOLBAR_ADDED,
  TOOLBAR_CHANGED,
  TOOLBAR_REMOVED,
  LAST_SIGNAL
};

typedef struct
{
  char *name;
  EggTbModelFlags flags;
} EggToolbarsToolbar;

typedef struct
{
  char *name;
} EggToolbarsItem;

static guint signals[LAST_SIGNAL] = { 0 };

#define EGG_TOOLBARS_MODEL_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EGG_TYPE_TOOLBARS_MODEL, EggToolbarsModelPrivate))

struct EggToolbarsModelPrivate
{
  GNode *toolbars;
  GList *types;
  GHashTable *flags;
};

G_DEFINE_TYPE (EggToolbarsModel, egg_toolbars_model, G_TYPE_OBJECT)

static xmlDocPtr
egg_toolbars_model_to_xml (EggToolbarsModel *model)
{
  GNode *l1, *l2, *tl;
  GList *l3;
  xmlDocPtr doc;

  g_return_val_if_fail (EGG_IS_TOOLBARS_MODEL (model), NULL);

  tl = model->priv->toolbars;

  xmlIndentTreeOutput = TRUE;
  doc = xmlNewDoc ((const xmlChar*) "1.0");
  doc->children = xmlNewDocNode (doc, NULL, (const xmlChar*) "toolbars", NULL);

  for (l1 = tl->children; l1 != NULL; l1 = l1->next)
    {
      xmlNodePtr tnode;
      EggToolbarsToolbar *toolbar = l1->data;

      tnode = xmlNewChild (doc->children, NULL, (const xmlChar*) "toolbar", NULL);
      xmlSetProp (tnode, (const xmlChar*) "name", (const xmlChar*) toolbar->name);
      xmlSetProp (tnode, (const xmlChar*) "hidden",
		  (toolbar->flags&EGG_TB_MODEL_HIDDEN) ? (const xmlChar*) "true" : (const xmlChar*) "false");
      xmlSetProp (tnode, (const xmlChar*) "editable",
		  (toolbar->flags&EGG_TB_MODEL_NOT_EDITABLE) ? (const xmlChar*) "false" : (const xmlChar*) "true");

      for (l2 = l1->children; l2 != NULL; l2 = l2->next)
	{
	  xmlNodePtr node;
	  EggToolbarsItem *item = l2->data;

          if (strcmp (item->name, "_separator") == 0)
            {
              node = xmlNewChild (tnode, NULL, (const xmlChar*) "separator", NULL);
              continue;
            }

          node = xmlNewChild (tnode, NULL, (const xmlChar*) "toolitem", NULL);
          xmlSetProp (node, (const xmlChar*) "name", (const xmlChar*) item->name);

          /* Add 'data' nodes for each data type which can be written out for this
           * item. Only write types which can be used to restore the data. */
          for (l3 = model->priv->types; l3 != NULL; l3 = l3->next)
            {
              EggToolbarsItemType *type = l3->data;
              if (type->get_name != NULL && type->get_data != NULL)
                {
                  xmlNodePtr dnode;
                  char *tmp;

                  tmp = type->get_data (type, item->name);
                  if (tmp != NULL)
                    {
                      dnode = xmlNewTextChild (node, NULL, (const xmlChar*) "data", (const xmlChar*) tmp);
                      g_free (tmp);

                      tmp = gdk_atom_name (type->type);
                      xmlSetProp (dnode, (const xmlChar*) "type", (const xmlChar*) tmp);
                      g_free (tmp);
                    }
                }
	    }
	}
    }

  return doc;
}

static gboolean
safe_save_xml (const char *xml_file, xmlDocPtr doc)
{
	char *tmp_file;
	char *old_file;
	gboolean old_exist;
	gboolean retval = TRUE;

	tmp_file = g_strconcat (xml_file, ".tmp", NULL);
	old_file = g_strconcat (xml_file, ".old", NULL);

	if (xmlSaveFormatFile (tmp_file, doc, 1) <= 0)
	{
		g_warning ("Failed to write XML data to %s", tmp_file);
		goto failed;
	}

	old_exist = g_file_test (xml_file, G_FILE_TEST_EXISTS);

	if (old_exist)
	{
		if (rename (xml_file, old_file) < 0)
		{
			g_warning ("Failed to rename %s to %s", xml_file, old_file);
			retval = FALSE;
			goto failed;
		}
	}

	if (rename (tmp_file, xml_file) < 0)
	{
		g_warning ("Failed to rename %s to %s", tmp_file, xml_file);

		if (rename (old_file, xml_file) < 0)
		{
			g_warning ("Failed to restore %s from %s", xml_file, tmp_file);
		}
		retval = FALSE;
		goto failed;
	}

	if (old_exist)
	{
		if (unlink (old_file) < 0)
		{
			g_warning ("Failed to delete old file %s", old_file);
		}
	}

	failed:
	g_free (old_file);
	g_free (tmp_file);

	return retval;
}

void
egg_toolbars_model_save_toolbars (EggToolbarsModel *model,
				  const char *xml_file,
				  const char *version)
{
  xmlDocPtr doc;
  xmlNodePtr root;

  g_return_if_fail (EGG_IS_TOOLBARS_MODEL (model));

  doc = egg_toolbars_model_to_xml (model);
  root = xmlDocGetRootElement (doc);
  xmlSetProp (root, (const xmlChar*) "version", (const xmlChar*) version);
  safe_save_xml (xml_file, doc);
  xmlFreeDoc (doc);
}

static gboolean
is_unique (EggToolbarsModel *model,
	   EggToolbarsItem *idata)
{
  EggToolbarsItem *idata2;
  GNode *toolbar, *item;


  for(toolbar = g_node_first_child (model->priv->toolbars);
      toolbar != NULL; toolbar = g_node_next_sibling (toolbar))
    {
      for(item = g_node_first_child (toolbar);
	  item != NULL; item = g_node_next_sibling (item))
        {
	  idata2 = item->data;

	  if (idata != idata2 && strcmp (idata->name, idata2->name) == 0)
	    {
	      return FALSE;
	    }
	}
    }

  return TRUE;
}

static GNode *
toolbar_node_new (const char *name)
{
  EggToolbarsToolbar *toolbar;

  toolbar = g_new (EggToolbarsToolbar, 1);
  toolbar->name = g_strdup (name);
  toolbar->flags = 0;

  return g_node_new (toolbar);
}

static GNode *
item_node_new (const char *name, EggToolbarsModel *model)
{
  EggToolbarsItem *item;
  int flags;

  g_return_val_if_fail (name != NULL, NULL);

  item = g_new (EggToolbarsItem, 1);
  item->name = g_strdup (name);

  flags = GPOINTER_TO_INT (g_hash_table_lookup (model->priv->flags, item->name));
  if ((flags & EGG_TB_MODEL_NAME_INFINITE) == 0)
    g_hash_table_insert (model->priv->flags,
			 g_strdup (item->name),
			 GINT_TO_POINTER (flags | EGG_TB_MODEL_NAME_USED));

  return g_node_new (item);
}

static void
item_node_free (GNode *item_node, EggToolbarsModel *model)
{
  EggToolbarsItem *item = item_node->data;
  int flags;

  flags = GPOINTER_TO_INT (g_hash_table_lookup (model->priv->flags, item->name));
  if ((flags & EGG_TB_MODEL_NAME_INFINITE) == 0 && is_unique (model, item))
    g_hash_table_insert (model->priv->flags,
			 g_strdup (item->name),
			 GINT_TO_POINTER (flags & ~EGG_TB_MODEL_NAME_USED));

  g_free (item->name);
  g_free (item);

  g_node_destroy (item_node);
}

static void
toolbar_node_free (GNode *toolbar_node, EggToolbarsModel *model)
{
  EggToolbarsToolbar *toolbar = toolbar_node->data;

  g_node_children_foreach (toolbar_node, G_TRAVERSE_ALL,
    			   (GNodeForeachFunc) item_node_free, model);

  g_free (toolbar->name);
  g_free (toolbar);

  g_node_destroy (toolbar_node);
}

EggTbModelFlags
egg_toolbars_model_get_flags (EggToolbarsModel *model,
			      int               toolbar_position)
{
  GNode *toolbar_node;
  EggToolbarsToolbar *toolbar;

  toolbar_node = g_node_nth_child (model->priv->toolbars, toolbar_position);
  g_return_val_if_fail (toolbar_node != NULL, 0);

  toolbar = toolbar_node->data;

  return toolbar->flags;
}

void
egg_toolbars_model_set_flags (EggToolbarsModel *model,
			      int               toolbar_position,
			      EggTbModelFlags   flags)
{
  GNode *toolbar_node;
  EggToolbarsToolbar *toolbar;

  toolbar_node = g_node_nth_child (model->priv->toolbars, toolbar_position);
  g_return_if_fail (toolbar_node != NULL);

  toolbar = toolbar_node->data;

  toolbar->flags = flags;

  g_signal_emit (G_OBJECT (model), signals[TOOLBAR_CHANGED],
		 0, toolbar_position);
}


char *
egg_toolbars_model_get_data (EggToolbarsModel *model,
                             GdkAtom           type,
                             const char       *name)
{
  EggToolbarsItemType *t;
  char *data = NULL;
  GList *l;

  if (type == GDK_NONE || type == gdk_atom_intern (EGG_TOOLBAR_ITEM_TYPE, FALSE))
    {
      g_return_val_if_fail (name != NULL, NULL);
      g_return_val_if_fail (*name != 0,   NULL);
      return strdup (name);
    }

  for (l = model->priv->types; l != NULL; l = l->next)
    {
      t = l->data;
      if (t->type == type && t->get_data != NULL)
        {
          data = t->get_data (t, name);
	  if (data != NULL) break;
        }
    }

  return data;
}

char *
egg_toolbars_model_get_name (EggToolbarsModel *model,
                             GdkAtom           type,
                             const char       *data,
                             gboolean          create)
{
  EggToolbarsItemType *t;
  char *name = NULL;
  GList *l;

  if (type == GDK_NONE || type == gdk_atom_intern (EGG_TOOLBAR_ITEM_TYPE, FALSE))
    {
      g_return_val_if_fail (data, NULL);
      g_return_val_if_fail (*data, NULL);
      return strdup (data);
    }

  if (create)
    {
      for (l = model->priv->types; name == NULL && l != NULL; l = l->next)
        {
          t = l->data;
          if (t->type == type && t->new_name != NULL)
            name = t->new_name (t, data);
        }

      return name;
    }
  else
    {
      for (l = model->priv->types; name == NULL && l != NULL; l = l->next)
        {
          t = l->data;
          if (t->type == type && t->get_name != NULL)
            name = t->get_name (t, data);
        }

      return name;
    }
}

static gboolean
impl_add_item (EggToolbarsModel    *model,
	       int		    toolbar_position,
	       int		    position,
	       const char          *name)
{
  GNode *parent_node;
  GNode *child_node;
  int real_position;

  g_return_val_if_fail (EGG_IS_TOOLBARS_MODEL (model), FALSE);
  g_return_val_if_fail (name != NULL, FALSE);

  parent_node = g_node_nth_child (model->priv->toolbars, toolbar_position);
  child_node = item_node_new (name, model);
  g_node_insert (parent_node, position, child_node);

  real_position = g_node_child_position (parent_node, child_node);

  g_signal_emit (G_OBJECT (model), signals[ITEM_ADDED], 0,
		 toolbar_position, real_position);

  return TRUE;
}

gboolean
egg_toolbars_model_add_item (EggToolbarsModel *model,
			     int	       toolbar_position,
			     int               position,
			     const char       *name)
{
  EggToolbarsModelClass *klass = EGG_TOOLBARS_MODEL_GET_CLASS (model);
  return klass->add_item (model, toolbar_position, position, name);
}

int
egg_toolbars_model_add_toolbar (EggToolbarsModel *model,
				int               position,
				const char       *name)
{
  GNode *node;
  int real_position;

  g_return_val_if_fail (EGG_IS_TOOLBARS_MODEL (model), -1);

  node = toolbar_node_new (name);
  g_node_insert (model->priv->toolbars, position, node);

  real_position = g_node_child_position (model->priv->toolbars, node);

  g_signal_emit (G_OBJECT (model), signals[TOOLBAR_ADDED],
		 0, real_position);

  return g_node_child_position (model->priv->toolbars, node);
}

static char *
parse_data_list (EggToolbarsModel *model,
		 xmlNodePtr        child,
                 gboolean          create)
{
  char *name = NULL;
  while (child && name == NULL)
    {
      if (xmlStrEqual (child->name, (const xmlChar*) "data"))
        {
          xmlChar *type = xmlGetProp (child, (const xmlChar*) "type");
          xmlChar *data = xmlNodeGetContent (child);

          if (type != NULL)
            {
              GdkAtom atom = gdk_atom_intern ((const char*) type, TRUE);
              name = egg_toolbars_model_get_name (model, atom, (const char*) data, create);
            }

          xmlFree (type);
          xmlFree (data);
        }

      child = child->next;
    }

  return name;
}

static void
parse_item_list (EggToolbarsModel *model,
		 xmlNodePtr        child,
		 int               position)
{
  while (child)
    {
      if (xmlStrEqual (child->name, (const xmlChar*) "toolitem"))
	{
          char *name;

          /* Try to get the name using the data elements first,
             as they are more 'portable' or 'persistent'. */
          name = parse_data_list (model, child->children, FALSE);
          if (name == NULL)
            {
              name = parse_data_list (model, child->children, TRUE);
            }

          /* If that fails, try to use the name. */
          if (name == NULL)
            {
              xmlChar *type = xmlGetProp (child, (const xmlChar*) "type");
              xmlChar *data = xmlGetProp (child, (const xmlChar*) "name");
              GdkAtom  atom = type ? gdk_atom_intern ((const char*) type, TRUE) : GDK_NONE;

              /* If an old format, try to use it. */
              name = egg_toolbars_model_get_name (model, atom, (const char*) data, FALSE);
              if (name == NULL)
                {
                  name = egg_toolbars_model_get_name (model, atom, (const char*) data, TRUE);
                }

              xmlFree (type);
              xmlFree (data);
            }

          if (name != NULL)
            {
              egg_toolbars_model_add_item (model, position, -1, name);
              g_free (name);
            }
	}
      else if (xmlStrEqual (child->name, (const xmlChar*) "separator"))
	{
          egg_toolbars_model_add_item (model, position, -1, "_separator");
	}

      child = child->next;
    }
}

static void
parse_toolbars (EggToolbarsModel *model,
		xmlNodePtr        child)
{
  while (child)
    {
      if (xmlStrEqual (child->name, (const xmlChar*) "toolbar"))
	{
	  xmlChar *string;
	  int position;
          EggTbModelFlags flags;

	  string = xmlGetProp (child, (const xmlChar*) "name");
	  position = egg_toolbars_model_add_toolbar (model, -1, (const char*) string);
          flags = egg_toolbars_model_get_flags (model, position);
	  xmlFree (string);

	  string = xmlGetProp (child, (const xmlChar*) "editable");
          if (string && xmlStrEqual (string, (const xmlChar*) "false"))
            flags |= EGG_TB_MODEL_NOT_EDITABLE;
	  xmlFree (string);

	  string = xmlGetProp (child, (const xmlChar*) "hidden");
          if (string && xmlStrEqual (string, (const xmlChar*) "true"))
            flags |= EGG_TB_MODEL_HIDDEN;
	  xmlFree (string);

	  string = xmlGetProp (child, (const xmlChar*) "style");
	  if (string && xmlStrEqual (string, (const xmlChar*) "icons-only"))
            flags |= EGG_TB_MODEL_ICONS;
	  xmlFree (string);

          egg_toolbars_model_set_flags (model, position, flags);

	  parse_item_list (model, child->children, position);
	}

      child = child->next;
    }
}

gboolean
egg_toolbars_model_load_toolbars (EggToolbarsModel *model,
				  const char *xml_file)
{
  xmlDocPtr doc;
  xmlNodePtr root;

  g_return_val_if_fail (EGG_IS_TOOLBARS_MODEL (model), FALSE);

  if (!xml_file || !g_file_test (xml_file, G_FILE_TEST_EXISTS)) return FALSE;

  doc = xmlParseFile (xml_file);
  if (doc == NULL)
  {
    g_warning ("Failed to load XML data from %s", xml_file);
    return FALSE;
  }
  root = xmlDocGetRootElement (doc);

  parse_toolbars (model, root->children);

  xmlFreeDoc (doc);

  return TRUE;
}

static void
parse_available_list (EggToolbarsModel *model,
		      xmlNodePtr        child)
{
  gint flags;

  while (child)
    {
      if (xmlStrEqual (child->name, (const xmlChar*) "toolitem"))
	{
	  xmlChar *name;

	  name = xmlGetProp (child, (const xmlChar*) "name");
	  flags = egg_toolbars_model_get_name_flags
	    (model, (const char*)name);
	  egg_toolbars_model_set_name_flags
	    (model, (const char*)name, flags | EGG_TB_MODEL_NAME_KNOWN);
	  xmlFree (name);
	}
      child = child->next;
    }
}

static void
parse_names (EggToolbarsModel *model,
	     xmlNodePtr        child)
{
  while (child)
    {
      if (xmlStrEqual (child->name, (const xmlChar*) "available"))
	{
	  parse_available_list (model, child->children);
	}

      child = child->next;
    }
}

gboolean
egg_toolbars_model_load_names (EggToolbarsModel *model,
			       const char *xml_file)
{
  xmlDocPtr doc;
  xmlNodePtr root;

  g_return_val_if_fail (EGG_IS_TOOLBARS_MODEL (model), FALSE);

  if (!xml_file || !g_file_test (xml_file, G_FILE_TEST_EXISTS)) return FALSE;

  doc = xmlParseFile (xml_file);
  if (doc == NULL)
  {
    g_warning ("Failed to load XML data from %s", xml_file);
    return FALSE;
  }
  root = xmlDocGetRootElement (doc);

  parse_names (model, root->children);

  xmlFreeDoc (doc);

  return TRUE;
}

static void
egg_toolbars_model_class_init (EggToolbarsModelClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  /* make sure the flags type is known */
  g_type_ensure (EGG_TYPE_TB_MODEL_FLAGS);

  object_class->finalize = egg_toolbars_model_finalize;

  klass->add_item = impl_add_item;

  signals[ITEM_ADDED] =
    g_signal_new ("item_added",
		  G_OBJECT_CLASS_TYPE (object_class),
		  G_SIGNAL_RUN_LAST,
		  G_STRUCT_OFFSET (EggToolbarsModelClass, item_added),
		  NULL, NULL, _egg_marshal_VOID__INT_INT,
		  G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT);
  signals[TOOLBAR_ADDED] =
    g_signal_new ("toolbar_added",
		  G_OBJECT_CLASS_TYPE (object_class),
		  G_SIGNAL_RUN_LAST,
		  G_STRUCT_OFFSET (EggToolbarsModelClass, toolbar_added),
		  NULL, NULL, g_cclosure_marshal_VOID__INT,
		  G_TYPE_NONE, 1, G_TYPE_INT);
  signals[ITEM_REMOVED] =
    g_signal_new ("item_removed",
		  G_OBJECT_CLASS_TYPE (object_class),
		  G_SIGNAL_RUN_LAST,
		  G_STRUCT_OFFSET (EggToolbarsModelClass, item_removed),
		  NULL, NULL, _egg_marshal_VOID__INT_INT,
		  G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT);
  signals[TOOLBAR_REMOVED] =
    g_signal_new ("toolbar_removed",
		  G_OBJECT_CLASS_TYPE (object_class),
		  G_SIGNAL_RUN_LAST,
		  G_STRUCT_OFFSET (EggToolbarsModelClass, toolbar_removed),
		  NULL, NULL, g_cclosure_marshal_VOID__INT,
		  G_TYPE_NONE, 1, G_TYPE_INT);
  signals[TOOLBAR_CHANGED] =
    g_signal_new ("toolbar_changed",
		  G_OBJECT_CLASS_TYPE (object_class),
		  G_SIGNAL_RUN_LAST,
		  G_STRUCT_OFFSET (EggToolbarsModelClass, toolbar_changed),
		  NULL, NULL, g_cclosure_marshal_VOID__INT,
		  G_TYPE_NONE, 1, G_TYPE_INT);

  g_type_class_add_private (object_class, sizeof (EggToolbarsModelPrivate));
}

static void
egg_toolbars_model_init (EggToolbarsModel *model)
{
  model->priv =EGG_TOOLBARS_MODEL_GET_PRIVATE (model);

  model->priv->toolbars = g_node_new (NULL);
  model->priv->flags = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
  egg_toolbars_model_set_name_flags (model, "_separator",
				     EGG_TB_MODEL_NAME_KNOWN |
				     EGG_TB_MODEL_NAME_INFINITE);
}

static void
egg_toolbars_model_finalize (GObject *object)
{
  EggToolbarsModel *model = EGG_TOOLBARS_MODEL (object);

  g_node_children_foreach (model->priv->toolbars, G_TRAVERSE_ALL,
    			   (GNodeForeachFunc) toolbar_node_free, model);
  g_node_destroy (model->priv->toolbars);
  g_hash_table_destroy (model->priv->flags);

  G_OBJECT_CLASS (egg_toolbars_model_parent_class)->finalize (object);
}

EggToolbarsModel *
egg_toolbars_model_new (void)
{
  return EGG_TOOLBARS_MODEL (g_object_new (EGG_TYPE_TOOLBARS_MODEL, NULL));
}

void
egg_toolbars_model_remove_toolbar (EggToolbarsModel   *model,
				   int                 position)
{
  GNode *node;
  EggTbModelFlags flags;

  g_return_if_fail (EGG_IS_TOOLBARS_MODEL (model));

  flags = egg_toolbars_model_get_flags (model, position);

  if (!(flags & EGG_TB_MODEL_NOT_REMOVABLE))
    {
      node = g_node_nth_child (model->priv->toolbars, position);
      g_return_if_fail (node != NULL);

      toolbar_node_free (node, model);

      g_signal_emit (G_OBJECT (model), signals[TOOLBAR_REMOVED],
		     0, position);
    }
}

void
egg_toolbars_model_remove_item (EggToolbarsModel *model,
				int               toolbar_position,
				int               position)
{
  GNode *node, *toolbar;

  g_return_if_fail (EGG_IS_TOOLBARS_MODEL (model));

  toolbar = g_node_nth_child (model->priv->toolbars, toolbar_position);
  g_return_if_fail (toolbar != NULL);

  node = g_node_nth_child (toolbar, position);
  g_return_if_fail (node != NULL);

  item_node_free (node, model);

  g_signal_emit (G_OBJECT (model), signals[ITEM_REMOVED], 0,
		 toolbar_position, position);
}

void
egg_toolbars_model_move_item (EggToolbarsModel *model,
			      int               toolbar_position,
			      int               position,
			      int		new_toolbar_position,
			      int		new_position)
{
  GNode *node, *toolbar, *new_toolbar;

  g_return_if_fail (EGG_IS_TOOLBARS_MODEL (model));

  toolbar = g_node_nth_child (model->priv->toolbars, toolbar_position);
  g_return_if_fail (toolbar != NULL);

  new_toolbar = g_node_nth_child (model->priv->toolbars, new_toolbar_position);
  g_return_if_fail (new_toolbar != NULL);

  node = g_node_nth_child (toolbar, position);
  g_return_if_fail (node != NULL);

  g_node_unlink (node);

  g_signal_emit (G_OBJECT (model), signals[ITEM_REMOVED], 0,
		 toolbar_position, position);

  g_node_insert (new_toolbar, new_position, node);

  g_signal_emit (G_OBJECT (model), signals[ITEM_ADDED], 0,
		 new_toolbar_position, new_position);
}

void
egg_toolbars_model_delete_item (EggToolbarsModel *model,
				const char       *name)
{
  EggToolbarsItem *idata;
  EggToolbarsToolbar *tdata;
  GNode *toolbar, *item, *next;
  int tpos, ipos;

  g_return_if_fail (EGG_IS_TOOLBARS_MODEL (model));

  toolbar = g_node_first_child (model->priv->toolbars);
  tpos = 0;

  while (toolbar != NULL)
    {
      item = g_node_first_child (toolbar);
      ipos = 0;

      /* Don't delete toolbars that were already empty */
      if (item == NULL)
        {
	  toolbar = g_node_next_sibling (toolbar);
	  continue;
        }

      while (item != NULL)
        {
	  next = g_node_next_sibling (item);
	  idata = item->data;
	  if (strcmp (idata->name, name) == 0)
	    {
	      item_node_free (item, model);
	      g_signal_emit (G_OBJECT (model),
			     signals[ITEM_REMOVED],
			     0, tpos, ipos);
	    }
	  else
	    {
	      ipos++;
	    }

	  item = next;
        }

      next = g_node_next_sibling (toolbar);
      tdata = toolbar->data;
      if (!(tdata->flags & EGG_TB_MODEL_NOT_REMOVABLE) &&
	  g_node_first_child (toolbar) == NULL)
        {
	  toolbar_node_free (toolbar, model);

	  g_signal_emit (G_OBJECT (model),
			 signals[TOOLBAR_REMOVED],
			 0, tpos);
        }
      else
        {
	  tpos++;
        }

      toolbar = next;
    }
}

int
egg_toolbars_model_n_items (EggToolbarsModel *model,
			    int               toolbar_position)
{
  GNode *toolbar;

  toolbar = g_node_nth_child (model->priv->toolbars, toolbar_position);
  g_return_val_if_fail (toolbar != NULL, -1);

  return g_node_n_children (toolbar);
}

const char *
egg_toolbars_model_item_nth (EggToolbarsModel *model,
			     int	       toolbar_position,
			     int               position)
{
  GNode *toolbar;
  GNode *item;
  EggToolbarsItem *idata;

  toolbar = g_node_nth_child (model->priv->toolbars, toolbar_position);
  g_return_val_if_fail (toolbar != NULL, NULL);

  item = g_node_nth_child (toolbar, position);
  g_return_val_if_fail (item != NULL, NULL);

  idata = item->data;
  return idata->name;
}

int
egg_toolbars_model_n_toolbars (EggToolbarsModel *model)
{
  return g_node_n_children (model->priv->toolbars);
}

const char *
egg_toolbars_model_toolbar_nth (EggToolbarsModel *model,
				int               position)
{
  GNode *toolbar;
  EggToolbarsToolbar *tdata;

  toolbar = g_node_nth_child (model->priv->toolbars, position);
  g_return_val_if_fail (toolbar != NULL, NULL);

  tdata = toolbar->data;

  return tdata->name;
}

/**
 * egg_toolbars_model_get_types:
 * @model:
 *
 * Returns: (transfer none) (element-type EggToolbarsItemType):
 */
GList *
egg_toolbars_model_get_types (EggToolbarsModel *model)
{
  return model->priv->types;
}

/**
 * egg_toolbars_model_set_types:
 * @model:
 * @types: (element-type EggToolbarsItemType):
 *
 */
void
egg_toolbars_model_set_types (EggToolbarsModel *model, GList *types)
{
  model->priv->types = types;
}

static void
fill_avail_array (gpointer key, gpointer value, GPtrArray *array)
{
  int flags = GPOINTER_TO_INT (value);
  if ((flags & EGG_TB_MODEL_NAME_KNOWN) && !(flags & EGG_TB_MODEL_NAME_USED))
      g_ptr_array_add (array, key);
}

/**
 * egg_toolbars_model_get_name_avail:
 * @model:
 *
 * Returns: (element-type utf8) (transfer container):
 */
GPtrArray *
egg_toolbars_model_get_name_avail (EggToolbarsModel *model)
{
  GPtrArray *array = g_ptr_array_new ();
  g_hash_table_foreach (model->priv->flags, (GHFunc) fill_avail_array, array);
  return array;
}

gint
egg_toolbars_model_get_name_flags (EggToolbarsModel *model, const char *name)
{
  return GPOINTER_TO_INT (g_hash_table_lookup (model->priv->flags, name));
}

void
egg_toolbars_model_set_name_flags (EggToolbarsModel *model, const char *name, gint flags)
{
  g_hash_table_insert (model->priv->flags, g_strdup (name), GINT_TO_POINTER (flags));
}
