/*
 * Copyright (C) 2002 Joaquín Cuenca Abela
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * Authors:
 *   Joaquín Cuenca Abela <e98cuenc@yahoo.com>
 *   Archit Baweja <bighead@users.sourceforge.net>
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

/**
 * SECTION:glade-command
 * @Short_Description: An event filter to implement the Undo/Redo stack.
 *
 * The Glade Command api allows us to view user actions as items and execute 
 * and undo those items; each #GladeProject has its own Undo/Redo stack.
 */

#include <gtk/gtk.h>
#include <string.h>
#include <glib/gi18n-lib.h>
#include "glade.h"
#include "glade-project.h"
#include "glade-xml-utils.h"
#include "glade-widget.h"
#include "glade-palette.h"
#include "glade-command.h"
#include "glade-property.h"
#include "glade-property-def.h"
#include "glade-debug.h"
#include "glade-placeholder.h"
#include "glade-clipboard.h"
#include "glade-signal.h"
#include "glade-app.h"
#include "glade-name-context.h"

typedef struct _GladeCommandPrivate
{
  GladeProject *project; /* The project this command is created for */

  gchar *description; /* a string describing the command.
                       * It's used in the undo/redo menu entry.
                       */

  gint   group_id;    /* If this is part of a command group, this is
                       * the group id (id is needed only to ensure that
                       * consecutive groups dont get merged).
                       */
} GladeCommandPrivate;

/* Concerning placeholders: we do not hold any reference to placeholders,
 * placeholders that are supplied by the backend are not reffed, placeholders
 * that are created by glade-command are temporarily owned by glade-command
 * untill they are added to a container; in which case it belongs to GTK+
 * and the backend (but I prefer to think of it as the backend).
 */
typedef struct
{
  GladeWidget *widget;
  GladeWidget *parent;
  GList *reffed;
  GladePlaceholder *placeholder;
  gboolean props_recorded;
  GList *pack_props;
  gchar *special_type;
  gulong handler_id;
} CommandData;

/* Group description used for the current group
 */
static gchar *gc_group_description = NULL;

/* Use an id to avoid grouping together consecutive groups
 */
static gint gc_group_id = 1;

/* Groups can be grouped together, push/pop must balance (duh!)
 */
static gint gc_group_depth = 0;


G_DEFINE_TYPE_WITH_PRIVATE (GladeCommand, glade_command, G_TYPE_OBJECT)

static void
glade_command_finalize (GObject *obj)
{
  GladeCommand *cmd = (GladeCommand *) obj;
  GladeCommandPrivate *priv = glade_command_get_instance_private (cmd);

  g_clear_pointer (&priv->description, g_free);

  G_OBJECT_CLASS (glade_command_parent_class)->finalize (obj);
}

static gboolean
glade_command_unifies_impl (GladeCommand *this_cmd, GladeCommand *other_cmd)
{
  return FALSE;
}

static void
glade_command_collapse_impl (GladeCommand *this_cmd, GladeCommand *other_cmd)
{
  g_return_if_reached ();
}

static void
glade_command_init (GladeCommand *command)
{
}

static void
glade_command_class_init (GladeCommandClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  object_class->finalize = glade_command_finalize;

  klass->undo = NULL;
  klass->execute = NULL;
  klass->unifies = glade_command_unifies_impl;
  klass->collapse = glade_command_collapse_impl;
}

/* Macros for defining the derived command types */
#define GLADE_MAKE_COMMAND(type, func, upper_type)                   \
G_DECLARE_FINAL_TYPE (type, func, GLADE, upper_type, GladeCommand)   \
G_DEFINE_TYPE (type, func, GLADE_TYPE_COMMAND)                       \
static gboolean                                                      \
func ## _undo (GladeCommand *me);                                    \
static gboolean                                                      \
func ## _execute (GladeCommand *me);                                 \
static void                                                          \
func ## _finalize (GObject *object);                                 \
static gboolean                                                      \
func ## _unifies (GladeCommand *this_cmd, GladeCommand *other_cmd);  \
static void                                                          \
func ## _collapse (GladeCommand *this_cmd, GladeCommand *other_cmd); \
static void                                                          \
func ## _class_init (type ## Class *klass)                           \
{                                                                    \
  GladeCommandClass *command_class = GLADE_COMMAND_CLASS (klass);    \
  GObjectClass* object_class = G_OBJECT_CLASS (klass);               \
  command_class->undo =  func ## _undo;                              \
  command_class->execute =  func ## _execute;                        \
  command_class->unifies =  func ## _unifies;                        \
  command_class->collapse =  func ## _collapse;                      \
  object_class->finalize = func ## _finalize;                        \
}                                                                    \
static void                                                          \
func ## _init (type *self)                                           \
{                                                                    \
}


const gchar *
glade_command_description (GladeCommand *command)
{
  GladeCommandPrivate *priv = glade_command_get_instance_private (command);

  g_return_val_if_fail (GLADE_IS_COMMAND (command), NULL);

  return priv->description;
}

gint
glade_command_group_id (GladeCommand *command)
{
  GladeCommandPrivate *priv = glade_command_get_instance_private (command);

  g_return_val_if_fail (GLADE_IS_COMMAND (command), -1);

  return priv->group_id;
}


/**
 * glade_command_execute:
 * @command: A #GladeCommand
 *
 * Executes @command
 *
 * Returns: whether the command was successfully executed
 */
gboolean
glade_command_execute (GladeCommand *command)
{
  g_return_val_if_fail (GLADE_IS_COMMAND (command), FALSE);
  return GLADE_COMMAND_GET_CLASS (command)->execute (command);
}


/**
 * glade_command_undo:
 * @command: A #GladeCommand
 *
 * Undo the effects of @command
 *
 * Returns: whether the command was successfully reversed
 */
gboolean
glade_command_undo (GladeCommand *command)
{
  g_return_val_if_fail (GLADE_IS_COMMAND (command), FALSE);
  return GLADE_COMMAND_GET_CLASS (command)->undo (command);
}

/**
 * glade_command_unifies:
 * @command: A #GladeCommand
 * @other: another #GladeCommand
 *
 * Checks whether @command and @other can be unified
 * to make one single command.
 *
 * Returns: whether they can be unified.
 */
gboolean
glade_command_unifies (GladeCommand *command, GladeCommand *other)
{
  GladeCommandPrivate *priv = glade_command_get_instance_private (command);
  GladeCommandPrivate *other_priv = glade_command_get_instance_private (other);

  g_return_val_if_fail (command, FALSE);

  /* Cannot unify with a part of a command group.
   * Unify atomic commands only
   */
  if (priv->group_id != 0 || (other && other_priv->group_id != 0))
    return FALSE;

  return GLADE_COMMAND_GET_CLASS (command)->unifies (command, other);
}

/**
 * glade_command_collapse:
 * @command: A #GladeCommand
 * @other: another #GladeCommand
 *
 * Merges @other into @command, so that @command now
 * covers both commands and @other can be dispensed with.
 */
void
glade_command_collapse (GladeCommand *command, GladeCommand *other)
{
  g_return_if_fail (command);
  GLADE_COMMAND_GET_CLASS (command)->collapse (command, other);
}

/**
 * glade_command_push_group:
 * @fmt:         The collective description of the command group.
 *               only the description of the first group on the 
 *               stack is used when embedding groups.
 * @...: args to the format string.
 *
 * Marks the beginning of a group.
 */
void
glade_command_push_group (const gchar *fmt, ...)
{
  va_list args;

  g_return_if_fail (fmt);

  /* Only use the description for the first group.
   */
  if (gc_group_depth++ == 0)
    {
      va_start (args, fmt);
      gc_group_description = g_strdup_vprintf (fmt, args);
      va_end (args);
    }
}

/**
 * glade_command_pop_group:
 *
 * Mark the end of a command group.
 */
void
glade_command_pop_group (void)
{
  if (gc_group_depth-- == 1)
    {
      gc_group_description = (g_free (gc_group_description), NULL);
      gc_group_id++;
    }

  if (gc_group_depth < 0)
    g_critical ("Unbalanced group stack detected in %s\n", G_STRFUNC);
}

gint
glade_command_get_group_depth (void)
{
  return gc_group_depth;
}

static void
glade_command_check_group (GladeCommand *cmd)
{
  GladeCommandPrivate *priv = glade_command_get_instance_private (cmd);

  g_return_if_fail (GLADE_IS_COMMAND (cmd));

  if (gc_group_description)
    {
      priv->description =
          (g_free (priv->description), g_strdup (gc_group_description));
      priv->group_id = gc_group_id;
    }
}


/****************************************************/
/*******  GLADE_COMMAND_PROPERTY_ENABLED      *******/
/****************************************************/
struct _GladeCommandPropertyEnabled
{
  GladeCommand parent;

  GladeProperty *property;
  gboolean old_enabled;
  gboolean new_enabled;
};

/* standard macros */
#define GLADE_TYPE_COMMAND_PROPERTY_ENABLED glade_command_property_enabled_get_type ()
GLADE_MAKE_COMMAND (GladeCommandPropertyEnabled, glade_command_property_enabled, COMMAND_PROPERTY_ENABLED)

static gboolean
glade_command_property_enabled_execute (GladeCommand *cmd)
{
  GladeCommandPropertyEnabled *me = GLADE_COMMAND_PROPERTY_ENABLED (cmd);

  glade_property_set_enabled (me->property, me->new_enabled);

  return TRUE;
}

static gboolean
glade_command_property_enabled_undo (GladeCommand *cmd)
{
  GladeCommandPropertyEnabled *me = GLADE_COMMAND_PROPERTY_ENABLED (cmd);

  glade_property_set_enabled (me->property, me->old_enabled);

  return TRUE;
}

static void
glade_command_property_enabled_finalize (GObject *obj)
{
  GladeCommandPropertyEnabled *me;

  g_return_if_fail (GLADE_IS_COMMAND_PROPERTY_ENABLED (obj));

  me = GLADE_COMMAND_PROPERTY_ENABLED (obj);

  g_clear_object (&me->property);
  glade_command_finalize (obj);
}

static gboolean
glade_command_property_enabled_unifies (GladeCommand *this_cmd, GladeCommand *other_cmd)
{
  GladeCommandPropertyEnabled *cmd1;
  GladeCommandPropertyEnabled *cmd2;

  if (!other_cmd)
    {
      if (GLADE_IS_COMMAND_PROPERTY_ENABLED (this_cmd))
        {
          cmd1 = (GladeCommandPropertyEnabled *) this_cmd;

          return (cmd1->old_enabled == cmd1->new_enabled);
        }
      return FALSE;
    }

  if (GLADE_IS_COMMAND_PROPERTY_ENABLED (this_cmd) &&
      GLADE_IS_COMMAND_PROPERTY_ENABLED (other_cmd))
    {
      cmd1 = GLADE_COMMAND_PROPERTY_ENABLED (this_cmd);
      cmd2 = GLADE_COMMAND_PROPERTY_ENABLED (other_cmd);

      return (cmd1->property == cmd2->property);
    }

  return FALSE;
}

static void
glade_command_property_enabled_collapse (GladeCommand *this_cmd,
                                         GladeCommand *other_cmd)
{
  GladeCommandPropertyEnabled *this = GLADE_COMMAND_PROPERTY_ENABLED (this_cmd);
  GladeCommandPrivate *this_priv = glade_command_get_instance_private (this_cmd);
  GladeCommandPropertyEnabled *other = GLADE_COMMAND_PROPERTY_ENABLED (other_cmd);
  GladeWidget *widget;
  GladePropertyDef *pdef;

  this->new_enabled = other->new_enabled;

  widget = glade_property_get_widget (this->property);
  pdef   = glade_property_get_def (this->property);

  g_free (this_priv->description);
  if (this->new_enabled)
    this_priv->description =
      g_strdup_printf (_("Enabling property %s on widget %s"),
                       glade_property_def_get_name (pdef),
                       glade_widget_get_display_name (widget));
  else
    this_priv->description =
      g_strdup_printf (_("Disabling property %s on widget %s"),
                       glade_property_def_get_name (pdef),
                       glade_widget_get_display_name (widget));
}

/**
 * glade_command_set_property_enabled:
 * @property: An optional #GladeProperty
 * @enabled: Whether the property should be enabled
 *
 * Enables or disables @property.
 *
 * @property must be an optional property.
 */
void
glade_command_set_property_enabled (GladeProperty *property,
                                    gboolean       enabled)
{
  GladeCommandPropertyEnabled *me;
  GladeCommand *cmd;
  GladeCommandPrivate *cmd_priv;
  GladeWidget *widget;
  GladePropertyDef *pdef;
  gboolean old_enabled;

  /* Sanity checks */
  g_return_if_fail (GLADE_IS_PROPERTY (property));

  widget = glade_property_get_widget (property);
  g_return_if_fail (GLADE_IS_WIDGET (widget));

  /* Only applies to optional properties */
  pdef = glade_property_get_def (property);
  g_return_if_fail (glade_property_def_optional (pdef));

  /* Fetch current state */
  old_enabled = glade_property_get_enabled (property);

  /* Avoid useless command */
  if (old_enabled == enabled)
    return;

  me = g_object_new (GLADE_TYPE_COMMAND_PROPERTY_ENABLED, NULL);
  cmd = GLADE_COMMAND (me);
  cmd_priv = glade_command_get_instance_private (cmd);
  cmd_priv->project = glade_widget_get_project (widget);

  me->property = g_object_ref (property);
  me->new_enabled = enabled;
  me->old_enabled = old_enabled;

  if (enabled)
    cmd_priv->description =
      g_strdup_printf (_("Enabling property %s on widget %s"),
                       glade_property_def_get_name (pdef),
                       glade_widget_get_display_name (widget));
  else
    cmd_priv->description =
      g_strdup_printf (_("Disabling property %s on widget %s"),
                       glade_property_def_get_name (pdef),
                       glade_widget_get_display_name (widget));

  glade_command_check_group (GLADE_COMMAND (me));

  if (glade_command_property_enabled_execute (cmd))
    glade_project_push_undo (cmd_priv->project, cmd);
  else
    g_object_unref (G_OBJECT (me));
}

/**************************************************/
/*******     GLADE_COMMAND_SET_PROPERTY     *******/
/**************************************************/

/* create a new GladeCommandSetProperty class.  Objects of this class will
 * encapsulate a "set property" operation */

struct _GladeCommandSetProperty
{
  GladeCommand parent;

  gboolean set_once;
  gboolean undo;
  GList *sdata;
};

/* standard macros */
#define GLADE_TYPE_COMMAND_SET_PROPERTY glade_command_set_property_get_type ()
GLADE_MAKE_COMMAND (GladeCommandSetProperty, glade_command_set_property, COMMAND_SET_PROPERTY)

/* Undo the last "set property command" */
static gboolean
glade_command_set_property_undo (GladeCommand *cmd)
{
  return glade_command_set_property_execute (cmd);
}

/*
 * Execute the set property command and revert it. IE, after the execution of 
 * this function cmd will point to the undo action
 */
static gboolean
glade_command_set_property_execute (GladeCommand *cmd)
{
  GladeCommandPrivate *priv = glade_command_get_instance_private (cmd);
  GladeCommandSetProperty *me = (GladeCommandSetProperty *) cmd;
  GList *l;
  gboolean success;
  gboolean retval = FALSE;

  g_return_val_if_fail (me != NULL, FALSE);

  if (me->set_once != FALSE)
    glade_property_push_superuser ();

  for (l = me->sdata; l; l = l->next)
    {
      GValue            new_value = { 0, };
      GladeCommandSetPropData    *sdata = l->data;
      GladePropertyDef *pdef = glade_property_get_def (sdata->property);
      GladeWidget      *widget = glade_property_get_widget (sdata->property);

      g_value_init (&new_value, G_VALUE_TYPE (sdata->new_value));

      if (me->undo)
        g_value_copy (sdata->old_value, &new_value);
      else
        g_value_copy (sdata->new_value, &new_value);

#ifdef GLADE_ENABLE_DEBUG
      if (glade_get_debug_flags () & GLADE_DEBUG_COMMANDS)
        {
          gchar *str =
            glade_widget_adaptor_string_from_value
            (glade_property_def_get_adaptor (pdef), pdef, &new_value);

          g_print ("Setting %s property of %s to %s (sumode: %d)\n",
                   glade_property_def_id (pdef),
                   glade_widget_get_display_name (widget),
                   str, glade_property_superuser ());

          g_free (str);
        }
#endif

      /* Packing properties need to be refreshed here since
       * they are reset when they get added to containers.
       */
      if (glade_property_def_get_is_packing (pdef))
        {
          GladeProperty *tmp_prop;

          tmp_prop = glade_widget_get_pack_property (widget, glade_property_def_id (pdef));

          if (sdata->property != tmp_prop)
            {
              g_object_unref (sdata->property);
              sdata->property = g_object_ref (tmp_prop);
            }
        }

      /* Make sure the target object has a name for object properties */
      if (glade_property_def_is_object (pdef))
        {
          GObject *pobject = g_value_get_object (&new_value);
          GladeWidget *pwidget;

          if (pobject && (pwidget = glade_widget_get_from_gobject (pobject)))
            glade_widget_ensure_name (pwidget, priv->project, TRUE);
        }

      success = glade_property_set_value (sdata->property, &new_value);
      retval  = retval || success;

      if (!me->set_once && success)
        {
          /* If some verify functions didnt pass on 
           * the first go.. we need to record the actual
           * properties here. XXX should be able to use glade_property_get_value() here
           */
          g_value_copy (glade_property_inline_value (sdata->property), sdata->new_value);
        }

      g_value_unset (&new_value);
    }

  if (me->set_once != FALSE)
    glade_property_pop_superuser ();

  me->set_once = TRUE;
  me->undo = !me->undo;

  return retval;
}

static void
glade_command_set_property_finalize (GObject *obj)
{
  GladeCommandSetProperty *me;
  GList *l;

  me = GLADE_COMMAND_SET_PROPERTY (obj);

  for (l = me->sdata; l; l = l->next)
    {
      GladeCommandSetPropData *sdata = l->data;

      if (sdata->property)
        g_object_unref (G_OBJECT (sdata->property));

      if (sdata->old_value)
        {
          if (G_VALUE_TYPE (sdata->old_value) != 0)
            g_value_unset (sdata->old_value);
          g_free (sdata->old_value);
        }
      if (G_VALUE_TYPE (sdata->new_value) != 0)
        g_value_unset (sdata->new_value);
      g_free (sdata->new_value);

    }
  glade_command_finalize (obj);
}

static gboolean
glade_command_set_property_unifies (GladeCommand *this_cmd,
                                    GladeCommand *other_cmd)
{
  GladeCommandSetProperty *cmd1, *cmd2;
  GladePropertyDef *pdef1, *pdef2;
  GladeCommandSetPropData *pdata1, *pdata2;
  GladeWidget *widget1, *widget2;
  GList *list, *l;

  if (!other_cmd)
    {
      if (GLADE_IS_COMMAND_SET_PROPERTY (this_cmd))
        {
          cmd1 = (GladeCommandSetProperty *) this_cmd;

          for (list = cmd1->sdata; list; list = list->next)
            {
              pdata1  = list->data;
              pdef1 = glade_property_get_def (pdata1->property);

              if (glade_property_def_compare (pdef1,
                                              pdata1->old_value,
                                              pdata1->new_value))
                return FALSE;
            }
          return TRUE;

        }
      return FALSE;
    }

  if (GLADE_IS_COMMAND_SET_PROPERTY (this_cmd) &&
      GLADE_IS_COMMAND_SET_PROPERTY (other_cmd))
    {
      cmd1 = (GladeCommandSetProperty *) this_cmd;
      cmd2 = (GladeCommandSetProperty *) other_cmd;

      if (g_list_length (cmd1->sdata) != g_list_length (cmd2->sdata))
        return FALSE;

      for (list = cmd1->sdata; list; list = list->next)
        {
          pdata1  = list->data;
          pdef1 = glade_property_get_def (pdata1->property);
          widget1 = glade_property_get_widget (pdata1->property);

          for (l = cmd2->sdata; l; l = l->next)
            {
              pdata2  = l->data;
              pdef2 = glade_property_get_def (pdata2->property);
              widget2 = glade_property_get_widget (pdata2->property);

              if (widget1 == widget2 &&
                  glade_property_def_match (pdef1, pdef2))
                break;
            }

          /* If both lists are the same length, and one class type
           * is not found in the other list, then we cant unify.
           */
          if (l == NULL)
            return FALSE;
        }

      return TRUE;
    }
  return FALSE;
}

static void
glade_command_set_property_collapse (GladeCommand *this_cmd,
                                     GladeCommand *other_cmd)
{
  GladeCommandSetProperty *cmd1, *cmd2;
  GladeCommandPrivate *this_priv = glade_command_get_instance_private (this_cmd);
  GladeCommandPrivate *other_priv = glade_command_get_instance_private (other_cmd);
  GladeCommandSetPropData *pdata1, *pdata2;
  GladePropertyDef *pdef1, *pdef2;
  GList *list, *l;

  g_return_if_fail (GLADE_IS_COMMAND_SET_PROPERTY (this_cmd) &&
                    GLADE_IS_COMMAND_SET_PROPERTY (other_cmd));

  cmd1 = (GladeCommandSetProperty *) this_cmd;
  cmd2 = (GladeCommandSetProperty *) other_cmd;


  for (list = cmd1->sdata; list; list = list->next)
    {
      pdata1  = list->data;
      pdef1 = glade_property_get_def (pdata1->property);

      for (l = cmd2->sdata; l; l = l->next)
        {
          pdata2  = l->data;
          pdef2 = glade_property_get_def (pdata2->property);

          if (glade_property_def_match (pdef1, pdef2))
            {
              /* Merge the GladeCommandSetPropData structs manually here
               */
              g_value_copy (pdata2->new_value, pdata1->new_value);
              break;
            }
        }
    }

  /* Set the description
   */
  g_free (this_priv->description);
  this_priv->description = other_priv->description;
  other_priv->description = NULL;
}


#define MAX_UNDO_MENU_ITEM_VALUE_LEN 10
static gchar *
glade_command_set_property_description (GladeCommandSetProperty *me)
{
  GladeCommandSetPropData *sdata;
  gchar *description = NULL;
  gchar *value_name;
  GladePropertyDef *pdef;
  GladeWidget *widget;

  g_assert (me->sdata);

  if (g_list_length (me->sdata) > 1)
    description = g_strdup_printf (_("Setting multiple properties"));
  else
    {
      sdata  = me->sdata->data;
      pdef = glade_property_get_def (sdata->property);
      widget = glade_property_get_widget (sdata->property);
      value_name = glade_widget_adaptor_string_from_value
        (glade_property_def_get_adaptor (pdef), pdef, sdata->new_value);

      if (!value_name || strlen (value_name) > MAX_UNDO_MENU_ITEM_VALUE_LEN
          || strchr (value_name, '_'))
        {
          description = g_strdup_printf (_("Setting %s of %s"),
                                         glade_property_def_get_name (pdef),
                                         glade_widget_get_display_name (widget));
        }
      else
        {
          description = g_strdup_printf (_("Setting %s of %s to %s"),
                                         glade_property_def_get_name (pdef),
                                         glade_widget_get_display_name (widget),
                                         value_name);
        }
      g_free (value_name);
    }

  return description;
}

/**
 * glade_command_set_properties_list:
 * @project: a #GladeProject
 * @props: (element-type GladeProperty): List of #GladeProperty
 */
void
glade_command_set_properties_list (GladeProject *project, GList *props)
{
  GladeCommandSetProperty *me;
  GladeCommand *cmd;
  GladeCommandPrivate *priv;
  GladeCommandSetPropData *sdata;
  GList *list;
  gboolean success;
  gboolean multiple;

  g_return_if_fail (GLADE_IS_PROJECT (project));
  g_return_if_fail (props);

  me = (GladeCommandSetProperty *)
      g_object_new (GLADE_TYPE_COMMAND_SET_PROPERTY, NULL);
  cmd = GLADE_COMMAND (me);
  priv = glade_command_get_instance_private (cmd);
  priv->project = project;

  /* Ref all props */
  for (list = props; list; list = list->next)
    {
      sdata = list->data;
      g_object_ref (G_OBJECT (sdata->property));
    }

  me->sdata = props;
  priv->description = glade_command_set_property_description (me);

  multiple = g_list_length (me->sdata) > 1;
  if (multiple)
    glade_command_push_group ("%s", priv->description);

  glade_command_check_group (cmd);

  /* Push onto undo stack only if it executes successfully. */
  success = glade_command_set_property_execute (cmd);

  if (success)
    glade_project_push_undo (priv->project, cmd);
  else
    g_object_unref (G_OBJECT (me));

  if (multiple)
    glade_command_pop_group ();
}


void
glade_command_set_properties (GladeProperty *property,
                              const GValue *old_value,
                              const GValue *new_value,
                              ...)
{
  GladeCommandSetPropData *sdata;
  GladeProperty *prop;
  GladeWidget   *widget;
  GladeProject  *project;
  GValue *ovalue, *nvalue;
  GList *list = NULL;
  va_list vl;

  g_return_if_fail (GLADE_IS_PROPERTY (property));

  /* Add first set */
  sdata = g_new (GladeCommandSetPropData, 1);
  sdata->property = property;
  sdata->old_value = g_new0 (GValue, 1);
  sdata->new_value = g_new0 (GValue, 1);
  g_value_init (sdata->old_value, G_VALUE_TYPE (old_value));
  g_value_init (sdata->new_value, G_VALUE_TYPE (new_value));
  g_value_copy (old_value, sdata->old_value);
  g_value_copy (new_value, sdata->new_value);
  list = g_list_prepend (list, sdata);

  va_start (vl, new_value);
  while ((prop = va_arg (vl, GladeProperty *)) != NULL)
    {
      ovalue = va_arg (vl, GValue *);
      nvalue = va_arg (vl, GValue *);

      g_assert (GLADE_IS_PROPERTY (prop));
      g_assert (G_IS_VALUE (ovalue));
      g_assert (G_IS_VALUE (nvalue));

      sdata = g_new (GladeCommandSetPropData, 1);
      sdata->property = g_object_ref (GLADE_PROPERTY (prop));
      sdata->old_value = g_new0 (GValue, 1);
      sdata->new_value = g_new0 (GValue, 1);
      g_value_init (sdata->old_value, G_VALUE_TYPE (ovalue));
      g_value_init (sdata->new_value, G_VALUE_TYPE (nvalue));
      g_value_copy (ovalue, sdata->old_value);
      g_value_copy (nvalue, sdata->new_value);
      list = g_list_prepend (list, sdata);
    }
  va_end (vl);

  widget  = glade_property_get_widget (property);
  project = glade_widget_get_project (widget);
  glade_command_set_properties_list (project, list);
}

void
glade_command_set_property_value (GladeProperty *property, const GValue *pvalue)
{

  /* Dont generate undo/redo items for unchanging property values.
   */
  if (glade_property_equals_value (property, pvalue))
    return;

  glade_command_set_properties (property, glade_property_inline_value (property), pvalue, NULL);
}

void
glade_command_set_property (GladeProperty * property, ...)
{
  GValue *value;
  va_list args;

  g_return_if_fail (GLADE_IS_PROPERTY (property));

  va_start (args, property);
  value = glade_property_def_make_gvalue_from_vl (glade_property_get_def (property), args);
  va_end (args);

  glade_command_set_property_value (property, value);
}

/**************************************************/
/*******       GLADE_COMMAND_SET_NAME       *******/
/**************************************************/

/* create a new GladeCommandSetName class.  Objects of this class will
 * encapsulate a "set name" operation */
struct _GladeCommandSetName
{
  GladeCommand parent;

  GladeWidget *widget;
  gchar *old_name;
  gchar *name;
};

/* standard macros */
#define GLADE_TYPE_COMMAND_SET_NAME glade_command_set_name_get_type ()
GLADE_MAKE_COMMAND (GladeCommandSetName, glade_command_set_name, COMMAND_SET_NAME)

/* Undo the last "set name command" */
static gboolean
glade_command_set_name_undo (GladeCommand *cmd)
{
  return glade_command_set_name_execute (cmd);
}

/*
 * Execute the set name command and revert it.  Ie, after the execution of this
 * function cmd will point to the undo action
 */
static gboolean
glade_command_set_name_execute (GladeCommand *cmd)
{
  GladeCommandPrivate *priv = glade_command_get_instance_private (cmd);
  GladeCommandSetName *me = GLADE_COMMAND_SET_NAME (cmd);
  char *tmp;

  g_return_val_if_fail (me != NULL, TRUE);
  g_return_val_if_fail (me->widget != NULL, TRUE);
  g_return_val_if_fail (me->name != NULL, TRUE);

  glade_project_set_widget_name (priv->project, me->widget, me->name);

  tmp = me->old_name;
  me->old_name = me->name;
  me->name = tmp;

  return TRUE;
}

static void
glade_command_set_name_finalize (GObject *obj)
{
  GladeCommandSetName *me;

  g_return_if_fail (GLADE_IS_COMMAND_SET_NAME (obj));

  me = GLADE_COMMAND_SET_NAME (obj);

  g_free (me->old_name);
  g_free (me->name);

  glade_command_finalize (obj);
}

static gboolean
glade_command_set_name_unifies (GladeCommand *this_cmd, GladeCommand *other_cmd)
{
  GladeCommandSetName *cmd1;
  GladeCommandSetName *cmd2;

  if (!other_cmd)
    {
      if (GLADE_IS_COMMAND_SET_NAME (this_cmd))
        {
          cmd1 = (GladeCommandSetName *) this_cmd;

          return (g_strcmp0 (cmd1->old_name, cmd1->name) == 0);
        }
      return FALSE;
    }

  if (GLADE_IS_COMMAND_SET_NAME (this_cmd) &&
      GLADE_IS_COMMAND_SET_NAME (other_cmd))
    {
      cmd1 = GLADE_COMMAND_SET_NAME (this_cmd);
      cmd2 = GLADE_COMMAND_SET_NAME (other_cmd);

      return (cmd1->widget == cmd2->widget);
    }

  return FALSE;
}

static void
glade_command_set_name_collapse (GladeCommand *this_cmd,
                                 GladeCommand *other_cmd)
{
  GladeCommandPrivate *this_priv = glade_command_get_instance_private (this_cmd);
  GladeCommandSetName *nthis = GLADE_COMMAND_SET_NAME (this_cmd);
  GladeCommandSetName *nother = GLADE_COMMAND_SET_NAME (other_cmd);

  g_return_if_fail (GLADE_IS_COMMAND_SET_NAME (this_cmd) &&
                    GLADE_IS_COMMAND_SET_NAME (other_cmd));

  g_free (nthis->old_name);
  nthis->old_name = nother->old_name;
  nother->old_name = NULL;

  g_free (this_priv->description);
  this_priv->description =
      g_strdup_printf (_("Renaming %s to %s"), nthis->name, nthis->old_name);
}

/* this function takes the ownership of name */
void
glade_command_set_name (GladeWidget *widget, const gchar *name)
{
  GladeCommandSetName *me;
  GladeCommand *cmd;
  GladeCommandPrivate *priv;

  g_return_if_fail (GLADE_IS_WIDGET (widget));
  g_return_if_fail (name && name[0]);

  /* Dont spam the queue with false name changes.
   */
  if (!strcmp (glade_widget_get_name (widget), name))
    return;

  me = g_object_new (GLADE_TYPE_COMMAND_SET_NAME, NULL);
  cmd = GLADE_COMMAND (me);
  priv = glade_command_get_instance_private (cmd);
  priv->project = glade_widget_get_project (widget);

  me->widget = widget;
  me->name = g_strdup (name);
  me->old_name = g_strdup (glade_widget_get_name (widget));

  priv->description =
      g_strdup_printf (_("Renaming %s to %s"), me->old_name, me->name);

  glade_command_check_group (cmd);

  if (glade_command_set_name_execute (cmd))
    glade_project_push_undo (priv->project, cmd);
  else
    g_object_unref (G_OBJECT (me));
}

/******************************************************************************
 * 
 * add/remove
 * 
 * These canonical commands add/remove a widget list to/from the project.
 * 
 *****************************************************************************/

struct _GladeCommandAddRemove
{
  GladeCommand parent;

  GList *widgets;
  gboolean add;
  gboolean from_clipboard;
};

#define GLADE_TYPE_COMMAND_ADD_REMOVE glade_command_add_remove_get_type ()
GLADE_MAKE_COMMAND (GladeCommandAddRemove, glade_command_add_remove, COMMAND_ADD_REMOVE)

static void
glade_command_placeholder_destroyed (GtkWidget *object, CommandData *cdata)
{
  if (GTK_WIDGET (cdata->placeholder) == object)
    {
      cdata->placeholder = NULL;
      cdata->handler_id = 0;
    }
}

static void
glade_command_placeholder_connect (CommandData *cdata,
                                   GladePlaceholder *placeholder)
{
  g_assert (cdata && cdata->placeholder == NULL);

  /* Something like a no-op with no placeholder */
  if ((cdata->placeholder = placeholder) == NULL)
    return;

  cdata->handler_id = g_signal_connect
      (placeholder, "destroy",
       G_CALLBACK (glade_command_placeholder_destroyed), cdata);
}

/**
 * get_all_parentless_reffed_widgetst:
 *
 * @props (element-type GladeWidget) : List of #GladeWidget
 * @return (element-type GladeWidget) : List of #GladeWidget
 */
static GList *
get_all_parentless_reffed_widgets (GList *reffed, GladeWidget *widget)
{
  GList *children, *l, *list;
  GladeWidget *child;

  if ((list = glade_widget_get_parentless_reffed_widgets (widget)) != NULL)
    reffed = g_list_concat (reffed, list);

  children = glade_widget_get_children (widget);

  for (l = children; l; l = l->next)
    {
      child  = glade_widget_get_from_gobject (l->data);
      reffed = get_all_parentless_reffed_widgets (reffed, child);
    }

  g_list_free (children);

  return reffed;
}

/**
 * glade_command_add:
 * @widgets: (element-type GladeWidget): a #GList
 * @parent: a #GladeWidget
 * @placeholder: a #GladePlaceholder
 * @project: a #GladeProject
 * @pasting: whether we are pasting an existing widget or creating a new one.
 *
 * Performs an add command on all widgets in @widgets to @parent, possibly
 * replacing @placeholder (note toplevels don't need a parent; the active project
 * will be used when pasting toplevel objects).
 * Pasted widgets will persist packing properties from their cut/copy source
 * while newly added widgets will prefer packing defaults.
 *
 */
void
glade_command_add (GList            *widgets,
                   GladeWidget      *parent,
                   GladePlaceholder *placeholder, 
                   GladeProject     *project,
                   gboolean          pasting)
{
  GladeCommandAddRemove *me;
  GladeCommand *cmd;
  GladeCommandPrivate *priv;
  CommandData *cdata;
  GladeWidget *widget = NULL;
  GladeWidgetAdaptor *adaptor;
  GList *l, *list, *children, *placeholders = NULL;
  GtkWidget *child;

  g_return_if_fail (widgets && widgets->data);
  g_return_if_fail (parent == NULL || GLADE_IS_WIDGET (parent));

  me = g_object_new (GLADE_TYPE_COMMAND_ADD_REMOVE, NULL);
  cmd = GLADE_COMMAND (me);
  priv = glade_command_get_instance_private (cmd);
  me->add = TRUE;
  me->from_clipboard = pasting;

  /* Things can go wrong in this function if the dataset is inaccurate,
   * we make no real attempt here to recover, just g_critical() and
   * fix the bugs as they pop up.
   */
  widget  = GLADE_WIDGET (widgets->data);
  adaptor = glade_widget_get_adaptor (widget);

  if (placeholder && GLADE_WIDGET_ADAPTOR_IS_TOPLEVEL (adaptor) == FALSE)
    priv->project = glade_placeholder_get_project (placeholder);
  else
    priv->project = project;

  priv->description =
      g_strdup_printf (_("Add %s"), g_list_length (widgets) == 1 ?
                       glade_widget_get_display_name (widget) : _("multiple"));

  for (list = widgets; list && list->data; list = list->next)
    {
      widget = list->data;
      cdata = g_new0 (CommandData, 1);
      if (glade_widget_get_internal (widget))
        g_critical ("Internal widget in Add");

      adaptor = glade_widget_get_adaptor (widget);

      /* Widget */
      cdata->widget = g_object_ref (GLADE_WIDGET (widget));

      /* Parentless ref */
      if ((cdata->reffed =
           get_all_parentless_reffed_widgets (cdata->reffed, widget)) != NULL)
        glade_util_list_objects_ref (cdata->reffed);

      /* Parent */
      if (parent == NULL)
        cdata->parent = glade_widget_get_parent (widget);
      else if (placeholder && GLADE_WIDGET_ADAPTOR_IS_TOPLEVEL (adaptor) == FALSE)
        cdata->parent = glade_placeholder_get_parent (placeholder);
      else
        cdata->parent = parent;

      /* Placeholder */
      if (placeholder != NULL && g_list_length (widgets) == 1)
        {
          glade_command_placeholder_connect (cdata, placeholder);
        }
      else if (cdata->parent &&
               glade_widget_placeholder_relation (cdata->parent, widget))
        {
          if ((children =
               glade_widget_adaptor_get_children (glade_widget_get_adaptor (cdata->parent), 
                                                  glade_widget_get_object (cdata->parent))) != NULL)
            {
              for (l = children; l && l->data; l = l->next)
                {
                  child = l->data;

                  /* Find a placeholder for this child, ignore special child types */
                  if (GLADE_IS_PLACEHOLDER (child) &&
                      g_object_get_data (G_OBJECT (child), "special-child-type") == NULL &&
                      g_list_find (placeholders, child) == NULL)
                    {
                      placeholders = g_list_append (placeholders, child);
                      glade_command_placeholder_connect (cdata, GLADE_PLACEHOLDER (child));
                      break;
                    }
                }
              g_list_free (children);
            }
        }

      me->widgets = g_list_prepend (me->widgets, cdata);
    }

  glade_command_check_group (cmd);

  /*
   * Push it onto the undo stack only on success
   */
  if (glade_command_add_remove_execute (cmd))
    glade_project_push_undo (priv->project, cmd);
  else
    g_object_unref (G_OBJECT (me));

  if (placeholders)
    g_list_free (placeholders);

}

static void
glade_command_delete_prop_refs (GladeWidget *widget)
{
  GladeProperty *property;
  GList         *refs, *l;

  refs = glade_widget_list_prop_refs (widget);
  for (l = refs; l; l = l->next)
    {
      property = l->data;
      glade_command_set_property (property, NULL);
    }
  g_list_free (refs);
}

static void glade_command_remove (GList *widgets);

static void
glade_command_remove_locked (GladeWidget *widget, GList *reffed)
{
  GList list = { 0, }, *widgets, *l;
  GladeWidget *locked;

  widgets = glade_widget_list_locked_widgets (widget);

  for (l = widgets; l; l = l->next)
    {
      locked = l->data;
      list.data = locked;

      if (g_list_find (reffed, locked))
        continue;

      glade_command_unlock_widget (locked);
      glade_command_remove (&list);
    }

  g_list_free (widgets);
}

/**
 * glade_command_remove:
 * @widgets: (element-type GladeWidget): a #GList of #GladeWidgets
 * @return_placeholders: whether or not to return a list of placehodlers
 *
 * Performs a remove command on all widgets in @widgets from @parent.
 */
static void
glade_command_remove (GList *widgets)
{
  GladeCommandAddRemove *me;
  GladeCommand *cmd;
  GladeCommandPrivate *priv;
  GladeWidget *widget = NULL;
  GladeWidget *lock;
  CommandData *cdata;
  GtkWidget *placeholder;
  GList *list, *l;

  g_return_if_fail (widgets != NULL);

  /* internal children cannot be deleted. Notify the user. */
  for (list = widgets; list && list->data; list = list->next)
    {
      widget = list->data;
      lock   = glade_widget_get_locker (widget);

      if (glade_widget_get_internal (widget))
        {
          glade_util_ui_message (glade_app_get_window (),
                                 GLADE_UI_WARN, NULL,
                                 _
                                 ("You cannot remove a widget internal to a composite widget."));
          return;
        }
      else if (lock)
        {
          glade_util_ui_message (glade_app_get_window (),
                                 GLADE_UI_WARN, NULL,
                                 _("%s is locked by %s, edit %s first."),
                                 glade_widget_get_display_name (widget),
                                 glade_widget_get_display_name (lock),
                                 glade_widget_get_display_name (lock));
          return;
        }
    }

  me = g_object_new (GLADE_TYPE_COMMAND_ADD_REMOVE, NULL);
  me->add = FALSE;
  me->from_clipboard = FALSE;
  cmd = GLADE_COMMAND (me);
  priv = glade_command_get_instance_private (cmd);

  priv->project = glade_widget_get_project (widget);
  priv->description = g_strdup ("dummy");

  if (g_list_length (widgets) == 1)
    glade_command_push_group (_("Remove %s"),
                              glade_widget_get_display_name (GLADE_WIDGET (widgets->data)));
  else
    glade_command_push_group (_("Remove multiple"));

  for (list = widgets; list && list->data; list = list->next)
    {
      widget = list->data;

      cdata = g_new0 (CommandData, 1);
      cdata->widget = g_object_ref (GLADE_WIDGET (widget));
      cdata->parent = glade_widget_get_parent (widget);

      if ((cdata->reffed =
           get_all_parentless_reffed_widgets (cdata->reffed, widget)) != NULL)
        glade_util_list_objects_ref (cdata->reffed);

      /* If we're removing the template widget, then we need to unset it as template */
      if (glade_project_get_template (priv->project) == widget)
        glade_command_set_project_template (priv->project, NULL);

      /* Undoably unset any object properties that may point to the removed object */
      glade_command_delete_prop_refs (widget);

      /* Undoably unlock and remove any widgets locked by this widget */
      glade_command_remove_locked (widget, cdata->reffed);

      if (cdata->parent != NULL &&
          glade_widget_placeholder_relation (cdata->parent, cdata->widget))
        {
          placeholder = glade_placeholder_new ();
          glade_command_placeholder_connect
              (cdata, GLADE_PLACEHOLDER (placeholder));
        }
      me->widgets = g_list_prepend (me->widgets, cdata);

      /* Record packing props if not deleted from the clipboard */
      if (me->from_clipboard == FALSE)
        {
          for (l = glade_widget_get_packing_properties (widget); l; l = l->next)
            cdata->pack_props =
              g_list_prepend (cdata->pack_props,
                              glade_property_dup (GLADE_PROPERTY (l->data),
                                                  cdata->widget));
        }
    }

  g_assert (widget);

  glade_command_check_group (cmd);

  if (glade_command_add_remove_execute (cmd))
    glade_project_push_undo (priv->project, cmd);
  else
    g_object_unref (G_OBJECT (me));

  glade_command_pop_group ();
}                               /* end of glade_command_remove() */

static void
glade_command_transfer_props (GladeWidget *gnew, GList *saved_props)
{
  GList *l;

  for (l = saved_props; l; l = l->next)
    {
      GladeProperty *prop, *sprop = l->data;
      GladePropertyDef *pdef = glade_property_get_def (sprop);

      prop = glade_widget_get_pack_property (gnew, glade_property_def_id (pdef));

      if (prop && glade_property_def_transfer_on_paste (pdef) &&
          glade_property_def_match (glade_property_get_def (prop), pdef))
        glade_property_set_value (prop, glade_property_inline_value (sprop));
    }
}

static gboolean
glade_command_add_execute (GladeCommandAddRemove *me)
{
  GladeCommandPrivate *priv = glade_command_get_instance_private ((GladeCommand *) me);
  CommandData *cdata;
  GList *list, *l, *saved_props;
  gchar *special_child_type;

  if (me->widgets)
    {
      glade_project_selection_clear (priv->project, FALSE);

      for (list = me->widgets; list && list->data; list = list->next)
        {
          cdata = list->data;
          saved_props = NULL;

          GLADE_NOTE (COMMANDS,
                      g_print ("Adding widget '%s' to parent '%s' "
                               "(from clipboard: %s, props recorded: %s, have placeholder: %s, child_type: %s)\n",
                               glade_widget_get_display_name (cdata->widget),
                               cdata->parent ? glade_widget_get_display_name (cdata->parent) : "(none)",
                               me->from_clipboard ? "yes" : "no",
                               cdata->props_recorded ? "yes" : "no",
                               cdata->placeholder ? "yes" : "no",
                               cdata->special_type));

          if (cdata->parent != NULL)
            {
              /* Prepare special-child-type for the paste. */
              if (me->from_clipboard)
                {
                  /* Only transfer properties when they are from the clipboard,
                   * otherwise prioritize packing defaults. 
                   */
                  saved_props =
                      glade_widget_dup_properties (cdata->widget,
                                                   glade_widget_get_packing_properties (cdata->widget), 
                                                   FALSE, FALSE, FALSE);

                  glade_widget_set_packing_properties (cdata->widget, cdata->parent);
                }

              /* Clear it the first time around, ensure we record it after adding */
              if (cdata->props_recorded == FALSE)
                g_object_set_data (glade_widget_get_object (cdata->widget),
                                   "special-child-type", NULL);
              else
                g_object_set_data_full (glade_widget_get_object (cdata->widget),
                                        "special-child-type",
                                        g_strdup (cdata->special_type),
                                        g_free);

              /* glade_command_paste ganauntees that if 
               * there we are pasting to a placeholder, 
               * there is only one widget.
               */
              if (cdata->placeholder)
                glade_widget_replace (cdata->parent,
                                      G_OBJECT (cdata->placeholder), 
                                      glade_widget_get_object (cdata->widget));
              else
                glade_widget_add_child (cdata->parent,
                                        cdata->widget,
                                        cdata->props_recorded == FALSE);

              glade_command_transfer_props (cdata->widget, saved_props);

              if (saved_props)
                g_list_free_full (saved_props, g_object_unref);

              /* Now that we've added, apply any packing props if nescisary. */
              for (l = cdata->pack_props; l; l = l->next)
                {
                  GValue            value = { 0, };
                  GladeProperty    *saved_prop = l->data;
                  GladePropertyDef *pdef = glade_property_get_def (saved_prop);
                  GladeProperty    *widget_prop =
                    glade_widget_get_pack_property (cdata->widget, glade_property_def_id (pdef));

                  glade_property_get_value (saved_prop, &value);
                  glade_property_set_value (widget_prop, &value);
                  glade_property_sync (widget_prop);
                  g_value_unset (&value);
                }

              if (cdata->props_recorded == FALSE)
                {

                  /* Save the packing properties after the initial paste.
                   * (this will be the defaults returned by the container
                   * implementation after initially adding them).
                   *
                   * Otherwise this recorded marker was set when cutting
                   */
                  g_assert (cdata->pack_props == NULL);
                  for (l = glade_widget_get_packing_properties (cdata->widget); l; l = l->next)
                    cdata->pack_props = 
                      g_list_prepend (cdata->pack_props,
                                      glade_property_dup (GLADE_PROPERTY (l->data), 
                                                          cdata->widget));

                  /* Record the special-type here after replacing */
                  if ((special_child_type =
                       g_object_get_data (glade_widget_get_object (cdata->widget),
                                          "special-child-type")) != NULL)
                    {
                      g_free (cdata->special_type);
                      cdata->special_type = g_strdup (special_child_type);
                    }

                  GLADE_NOTE (COMMANDS,
                              g_print ("Recorded properties for adding widget '%s' to parent '%s' (special child: %s)\n",
                                       glade_widget_get_display_name (cdata->widget),
                                       cdata->parent ? glade_widget_get_display_name (cdata->parent) : "(none)",
                                       cdata->special_type));

                  /* Mark the properties as recorded */
                  cdata->props_recorded = TRUE;
                }
            }

          glade_project_add_object (priv->project,
                                    glade_widget_get_object (cdata->widget));

          for (l = cdata->reffed; l; l = l->next)
            {
              GladeWidget *reffed = l->data;
              glade_project_add_object (priv->project,
                                        glade_widget_get_object (reffed));
            }

          glade_project_selection_add (priv->project, 
                                       glade_widget_get_object (cdata->widget), FALSE);

          glade_widget_show (cdata->widget);
        }

      glade_project_queue_selection_changed (priv->project);
    }
  return TRUE;
}                               /* end of glade_command_add_execute() */

static gboolean
glade_command_remove_execute (GladeCommandAddRemove *me)
{
  GladeCommandPrivate *priv = glade_command_get_instance_private ((GladeCommand *) me);
  CommandData *cdata;
  GladeWidget *reffed;
  GList *list, *l;
  gchar *special_child_type;

  for (list = me->widgets; list && list->data; list = list->next)
    {
      cdata = list->data;

      GLADE_NOTE (COMMANDS,
                  g_print ("Removing widget '%s' from parent '%s' "
                           "(from clipboard: %s, props recorded: %s, have placeholder: %s, child_type: %s)\n",
                           glade_widget_get_display_name (cdata->widget),
                           cdata->parent ? glade_widget_get_display_name (cdata->parent) : "(none)",
                           me->from_clipboard ? "yes" : "no",
                           cdata->props_recorded ? "yes" : "no",
                           cdata->placeholder ? "yes" : "no",
                           cdata->special_type));

      glade_widget_hide (cdata->widget);

      if (cdata->props_recorded == FALSE)
        {
          /* Record the special-type here after replacing */
          if ((special_child_type =
               g_object_get_data (glade_widget_get_object (cdata->widget),
                                  "special-child-type")) != NULL)
            {
              g_free (cdata->special_type);
              cdata->special_type = g_strdup (special_child_type);
            }

          GLADE_NOTE (COMMANDS,
                      g_print ("Recorded properties for removing widget '%s' from parent '%s' (special child: %s)\n",
                               glade_widget_get_display_name (cdata->widget),
                               cdata->parent ? glade_widget_get_display_name (cdata->parent) : "(none)",
                               cdata->special_type));

          /* Mark the properties as recorded */
          cdata->props_recorded = TRUE;
        }

      glade_project_remove_object (priv->project,
                                   glade_widget_get_object (cdata->widget));

      for (l = cdata->reffed; l; l = l->next)
        {
          reffed = l->data;
          glade_project_remove_object (priv->project,
                                       glade_widget_get_object (reffed));
        }

      if (cdata->parent)
        {
          if (cdata->placeholder)
            glade_widget_replace (cdata->parent, glade_widget_get_object (cdata->widget),
                                  G_OBJECT (cdata->placeholder));
          else
            glade_widget_remove_child (cdata->parent, cdata->widget);
        }
    }

  return TRUE;
}

/*
 * Execute the cmd and revert it.  Ie, after the execution of this
 * function cmd will point to the undo action
 */
static gboolean
glade_command_add_remove_execute (GladeCommand *cmd)
{
  GladeCommandAddRemove *me = (GladeCommandAddRemove *) cmd;
  gboolean retval;

  if (me->add)
    retval = glade_command_add_execute (me);
  else
    retval = glade_command_remove_execute (me);

  me->add = !me->add;

  return retval;
}

static gboolean
glade_command_add_remove_undo (GladeCommand *cmd)
{
  return glade_command_add_remove_execute (cmd);
}

static void
glade_command_data_free (gpointer data)
{
  CommandData *cdata = data;

  if (cdata->placeholder)
    {
      g_clear_signal_handler (&cdata->handler_id, cdata->placeholder);

      if (g_object_is_floating (G_OBJECT (cdata->placeholder)))
        gtk_widget_destroy (GTK_WIDGET (cdata->placeholder));
    }

  g_clear_object (&cdata->widget);
  g_list_free_full (cdata->reffed, g_object_unref);

  g_free (cdata);
}

static void
glade_command_add_remove_finalize (GObject *obj)
{
  GladeCommandAddRemove *cmd;

  g_return_if_fail (GLADE_IS_COMMAND_ADD_REMOVE (obj));

  cmd = GLADE_COMMAND_ADD_REMOVE (obj);

  g_list_free_full (cmd->widgets, glade_command_data_free);

  glade_command_finalize (obj);
}

static gboolean
glade_command_add_remove_unifies (GladeCommand *this_cmd,
                                  GladeCommand *other_cmd)
{
  return FALSE;
}

static void
glade_command_add_remove_collapse (GladeCommand *this_cmd,
                                   GladeCommand *other_cmd)
{
  g_return_if_reached ();
}

static void
adjust_container_size (GladeWidget *parent, gint children)
{
  gint placeholders;
  GObject *gparent;

  if (!parent)
    return;

  placeholders = glade_util_count_placeholders (parent);
  gparent = glade_widget_get_object (parent);

  if ((GTK_IS_BOX (gparent) || GTK_IS_GRID (gparent)) && placeholders < children)
    {
      children -= placeholders;

      if (GTK_IS_BOX (gparent))
        {
          GladeProperty *prop = glade_widget_get_property (parent, "size");
          gint size;

          glade_property_get (prop, &size);
          glade_command_set_property (prop, size + children);
        }
      else
        {
          GladeProperty *row = glade_widget_get_property (parent, "n-rows");
          gint ncol, nrow;

          glade_widget_property_get (parent, "n-columns", &ncol);
          glade_property_get (row, &nrow);
          glade_command_set_property (row, nrow + (children / ncol) + ((children % ncol) ? 1 : 0));
        }
    }
}

/******************************************************************************
 * 
 * The following are command aliases.  Their implementations are the actual 
 * glade commands.
 * 
 *****************************************************************************/

/**
 * glade_command_create:
 * @adaptor: A #GladeWidgetAdaptor
 * @parent: (nullable): the parent #GladeWidget to add the new widget to.
 * @placeholder: (nullable): the placeholder which will be substituted by the widget
 * @project: the project his widget belongs to.
 *
 * Creates a new widget using @adaptor and put in place of the @placeholder
 * in the @project
 *
 * Returns: (transfer full): the newly created widget.
 */
GladeWidget *
glade_command_create (GladeWidgetAdaptor *adaptor,
                      GladeWidget *parent,
                      GladePlaceholder *placeholder,
                      GladeProject *project)
{
  GList widgets = { NULL, };
  GladeWidget *widget;

  g_return_val_if_fail (GLADE_IS_WIDGET_ADAPTOR (adaptor), NULL);
  g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL);

  /* attempt to create the widget -- widget may be null, e.g. the user clicked cancel on a query */
  widget = glade_widget_adaptor_create_widget (adaptor, TRUE,
                                               "parent", parent,
                                               "project", project, NULL);
  if (widget == NULL)
    {
      return NULL;
    }

  if (parent && !glade_widget_add_verify (parent, widget, TRUE))
    {
      g_object_ref_sink (widget);
      g_object_unref (widget);
      return NULL;
    }

  widgets.data = widget;
  glade_command_push_group (_("Create %s"), glade_widget_get_display_name (widget));
  adjust_container_size (parent, 1);
  glade_command_add (&widgets, parent, placeholder, project, FALSE);
  glade_command_pop_group ();

  /* Make selection change immediately when a widget is created */
  glade_project_selection_changed (project);

  return widget;
}

/**
 * glade_command_delete:
 * @widgets: (element-type GladeWidget): a #GList of #GladeWidgets
 *
 * Performs a delete command on the list of widgets.
 */
void
glade_command_delete (GList *widgets)
{
  GladeWidget *widget;

  g_return_if_fail (widgets != NULL);

  widget = widgets->data;
  glade_command_push_group (_("Delete %s"),
                            g_list_length (widgets) == 1 ? 
                            glade_widget_get_display_name (widget) : _("multiple"));
  glade_command_remove (widgets);
  glade_command_pop_group ();
}

/**
 * glade_command_cut:
 * @widgets: (element-type GladeWidget): a #GList of #GladeWidgets
 *
 * Removes the list of widgets and adds them to the clipboard.
 */
void
glade_command_cut (GList *widgets)
{
  GladeWidget *widget;
  GList *l;

  g_return_if_fail (widgets != NULL);

  for (l = widgets; l; l = l->next)
    g_object_set_data (G_OBJECT (l->data), "glade-command-was-cut",
                       GINT_TO_POINTER (TRUE));

  widget = widgets->data;
  glade_command_push_group (_("Cut %s"),
                            g_list_length (widgets) == 1 ? 
                            glade_widget_get_display_name (widget) : _("multiple"));
  glade_command_remove (widgets);
  glade_command_pop_group ();

  glade_clipboard_add (glade_app_get_clipboard (), widgets);
}

#if 0
static void
glade_command_break_references_for_widget (GladeWidget *widget, GList *widgets)
{
  GList *l, *children;

  for (l = widget->properties; l; l = l->next)
    {
      property = l->data;

      if (glade_property_def_is_object (property->klass) &&
          property->klass->parentless_widget == FALSE)
        {
          GList *obj_list;
          GObject *reffed_obj = NULL;
          GladeWidget *reffed_widget;

          if (GLADE_IS_PARAM_SPEC_OBJECTS (klass->pspec))
            {
              glade_property_get (property, &obj_list);

            }
          else
            {
              glade_property_get (property, &reffed_obj);
            }
        }
    }

  children = glade_widget_adaptor_get_children (widget->adaptor,
                                                widget->object);

  for (l = children; l; l = l->next)
    {
      if ((child = glade_widget_get_from_gobject (l->data)) != NULL)
        glade_command_break_references_for_widget (child, widgets);
    }

  g_list_free (children);
}

static void
glade_command_break_references (GladeProject *project, GList *widgets)
{
  GList *list;
  GladeWidget *widget;

  for (list = widgets; list && list->data; list = list->next)
    {
      widget = l->data;

      if (project == widget->project)
        continue;

      glade_command_break_references_for_widget (widget, widgets);
    }


}
#endif

/**
 * glade_command_paste:
 * @widgets: (element-type GladeWidget): a #GList of #GladeWidget
 * @parent: (allow-none): a #GladeWidget
 * @placeholder: (allow-none): a #GladePlaceholder
 * @project: a #GladeProject
 *
 * Performs a paste command on all widgets in @widgets to @parent, possibly
 * replacing @placeholder (note toplevels dont need a parent; the active project
 * will be used when pasting toplevel objects).
 */
void
glade_command_paste (GList *widgets,
                     GladeWidget *parent,
                     GladePlaceholder *placeholder,
                     GladeProject *project)
{
  GList *list, *copied_widgets = NULL;
  GladeWidget *copied_widget = NULL;
  gboolean exact;
  gint len;

  g_return_if_fail (widgets != NULL);

  for (list = widgets; list && list->data; list = list->next)
    {
      exact =
          GPOINTER_TO_INT (g_object_get_data
                           (G_OBJECT (list->data), "glade-command-was-cut"));

      copied_widget = glade_widget_dup (list->data, exact);
      copied_widgets = g_list_prepend (copied_widgets, copied_widget);
    }

  len = g_list_length (widgets);
  glade_command_push_group (_("Paste %s"), len == 1 ? glade_widget_get_display_name (copied_widget) : _("multiple"));
  adjust_container_size (parent, len);
  glade_command_add (copied_widgets, parent, placeholder, project, TRUE);
  glade_command_pop_group ();

  if (copied_widgets)
    g_list_free (copied_widgets);
}

/**
 * glade_command_dnd:
 * @widgets: (element-type GladeWidget): a #GList of #GladeWidget
 * @parent: (allow-none): a #GladeWidget
 * @placeholder: (allow-none): a #GladePlaceholder
 *
 * Performs a drag-n-drop command, i.e. removes the list of widgets and adds them 
 * to the new parent, possibly replacing @placeholder (note toplevels dont need a 
 * parent; the active project will be used when pasting toplevel objects).
 */
void
glade_command_dnd (GList *widgets,
                   GladeWidget *parent,
                   GladePlaceholder *placeholder)
{
  GladeWidget *widget;
  GladeProject *project;

  g_return_if_fail (widgets != NULL);

  widget = widgets->data;
  
  if (parent)
    project = glade_widget_get_project (parent);
  else if (placeholder)
    project = glade_placeholder_get_project (placeholder);
  else
    project = glade_widget_get_project (widget);

  g_return_if_fail (project);
  
  glade_command_push_group (_("Drag %s and Drop to %s"),
                            g_list_length (widgets) == 1 ? 
                            glade_widget_get_display_name (widget) : _("multiple"),
                            parent ? glade_widget_get_display_name (parent) : _("root"));
  glade_command_remove (widgets);
  glade_command_add (widgets, parent, placeholder, project, TRUE);
  glade_command_pop_group ();
}

/*********************************************************/
/*******     GLADE_COMMAND_ADD_SIGNAL     *******/
/*********************************************************/

/* create a new GladeCommandAddRemoveChangeSignal class.  Objects of this class will
 * encapsulate an "add or remove signal handler" operation */
typedef enum
{
  GLADE_ADD,
  GLADE_REMOVE,
  GLADE_CHANGE
} GladeAddType;

struct _GladeCommandAddSignal
{
  GladeCommand parent;

  GladeWidget *widget;

  GladeSignal *signal;
  GladeSignal *new_signal;

  GladeAddType type;
};

/* standard macros */
#define GLADE_TYPE_COMMAND_ADD_SIGNAL glade_command_add_signal_get_type ()
GLADE_MAKE_COMMAND (GladeCommandAddSignal, glade_command_add_signal, COMMAND_ADD_SIGNAL)

static void
glade_command_add_signal_finalize (GObject *obj)
{
  GladeCommandAddSignal *cmd = GLADE_COMMAND_ADD_SIGNAL (obj);

  g_clear_object (&cmd->widget);
  g_clear_object (&cmd->signal);
  g_clear_object (&cmd->new_signal);

  glade_command_finalize (obj);
}

static gboolean
glade_command_add_signal_undo (GladeCommand *this_cmd)
{
  return glade_command_add_signal_execute (this_cmd);
}

static gboolean
glade_command_add_signal_execute (GladeCommand *this_cmd)
{
  GladeCommandAddSignal *cmd = GLADE_COMMAND_ADD_SIGNAL (this_cmd);
  GladeSignal *temp;

  switch (cmd->type)
    {
      case GLADE_ADD:
        glade_widget_add_signal_handler (cmd->widget, cmd->signal);
        cmd->type = GLADE_REMOVE;
        break;
      case GLADE_REMOVE:
        glade_widget_remove_signal_handler (cmd->widget, cmd->signal);
        cmd->type = GLADE_ADD;
        break;
      case GLADE_CHANGE:
        glade_widget_change_signal_handler (cmd->widget,
                                            cmd->signal, cmd->new_signal);
        temp = cmd->signal;
        cmd->signal = cmd->new_signal;
        cmd->new_signal = temp;
        break;
      default:
        break;
    }
  return TRUE;
}

static gboolean
glade_command_add_signal_unifies (GladeCommand *this_cmd,
                                  GladeCommand *other_cmd)
{
  return FALSE;
}

static void
glade_command_add_signal_collapse (GladeCommand *this_cmd,
                                   GladeCommand *other_cmd)
{
  g_return_if_reached ();
}

static void
glade_command_add_remove_change_signal (GladeWidget *glade_widget,
                                        const GladeSignal *signal,
                                        const GladeSignal *new_signal,
                                        GladeAddType type)
{
  GladeCommandAddSignal *me = GLADE_COMMAND_ADD_SIGNAL
      (g_object_new (GLADE_TYPE_COMMAND_ADD_SIGNAL, NULL));
  GladeCommand *cmd = GLADE_COMMAND (me);
  GladeCommandPrivate *priv = glade_command_get_instance_private (cmd);

  /* we can only add/remove a signal to a widget that has been wrapped by a GladeWidget */
  g_assert (glade_widget != NULL);
  g_assert (glade_widget_get_project (glade_widget) != NULL);

  me->widget = g_object_ref (glade_widget);
  me->type = type;
  me->signal = glade_signal_clone (signal);
  me->new_signal = new_signal ? glade_signal_clone (new_signal) : NULL;

  priv->project = glade_widget_get_project (glade_widget);
  priv->description =
      g_strdup_printf (type == GLADE_ADD ? _("Add signal handler %s") :
                       type == GLADE_REMOVE ? _("Remove signal handler %s") :
                       _("Change signal handler %s"), 
                       glade_signal_get_handler ((GladeSignal *)signal));

  glade_command_check_group (cmd);

  if (glade_command_add_signal_execute (cmd))
    glade_project_push_undo (priv->project, cmd);
  else
    g_object_unref (G_OBJECT (me));
}

/**
 * glade_command_add_signal:
 * @glade_widget: a #GladeWidget
 * @signal: a #GladeSignal
 *
 * TODO: write me
 */
void
glade_command_add_signal (GladeWidget *glade_widget, const GladeSignal *signal)
{
  glade_command_add_remove_change_signal
      (glade_widget, signal, NULL, GLADE_ADD);
}

/**
 * glade_command_remove_signal:
 * @glade_widget: a #GladeWidget
 * @signal: a #GladeSignal
 *
 * TODO: write me
 */
void
glade_command_remove_signal (GladeWidget *glade_widget,
                             const GladeSignal *signal)
{
  glade_command_add_remove_change_signal
      (glade_widget, signal, NULL, GLADE_REMOVE);
}

/**
 * glade_command_change_signal:
 * @glade_widget: a #GladeWidget
 * @old_signal: a #GladeSignal
 * @new_signal: a #GladeSignal
 *
 * TODO: write me
 */
void
glade_command_change_signal (GladeWidget *glade_widget,
                             const GladeSignal *old_signal,
                             const GladeSignal *new_signal)
{
  glade_command_add_remove_change_signal
      (glade_widget, old_signal, new_signal, GLADE_CHANGE);
}

/******************************************************************************
 * 
 * set i18n metadata
 * 
 * This command sets the i18n metadata on a label property.
 * 
 *****************************************************************************/

struct _GladeCommandSetI18n
{
  GladeCommand parent;

  GladeProperty *property;
  gboolean translatable;
  gchar *context;
  gchar *comment;
  gboolean old_translatable;
  gchar *old_context;
  gchar *old_comment;
};

#define GLADE_TYPE_COMMAND_SET_I18N glade_command_set_i18n_get_type ()
GLADE_MAKE_COMMAND (GladeCommandSetI18n, glade_command_set_i18n, COMMAND_SET_I18N)

static gboolean
glade_command_set_i18n_execute (GladeCommand *cmd)
{
  GladeCommandSetI18n *me = (GladeCommandSetI18n *) cmd;
  gboolean temp_translatable;
  gchar *temp_context;
  gchar *temp_comment;

  /* sanity check */
  g_return_val_if_fail (me != NULL, TRUE);
  g_return_val_if_fail (me->property != NULL, TRUE);

  /* set the new values in the property */
  glade_property_i18n_set_translatable (me->property, me->translatable);
  glade_property_i18n_set_context (me->property, me->context);
  glade_property_i18n_set_comment (me->property, me->comment);

  /* swap the current values with the old values to prepare for undo */
  temp_translatable = me->translatable;
  temp_context = me->context;
  temp_comment = me->comment;
  me->translatable = me->old_translatable;
  me->context = me->old_context;
  me->comment = me->old_comment;
  me->old_translatable = temp_translatable;
  me->old_context = temp_context;
  me->old_comment = temp_comment;

  return TRUE;
}

static gboolean
glade_command_set_i18n_undo (GladeCommand *cmd)
{
  return glade_command_set_i18n_execute (cmd);
}

static void
glade_command_set_i18n_finalize (GObject *obj)
{
  GladeCommandSetI18n *me;

  g_return_if_fail (GLADE_IS_COMMAND_SET_I18N (obj));

  me = GLADE_COMMAND_SET_I18N (obj);
  g_free (me->context);
  g_free (me->comment);
  g_free (me->old_context);
  g_free (me->old_comment);

  glade_command_finalize (obj);
}

static gboolean
glade_command_set_i18n_unifies (GladeCommand *this_cmd, GladeCommand *other_cmd)
{
  GladeCommandSetI18n *cmd1;
  GladeCommandSetI18n *cmd2;

  if (GLADE_IS_COMMAND_SET_I18N (this_cmd) &&
      GLADE_IS_COMMAND_SET_I18N (other_cmd))
    {
      cmd1 = GLADE_COMMAND_SET_I18N (this_cmd);
      cmd2 = GLADE_COMMAND_SET_I18N (other_cmd);

      return (cmd1->property == cmd2->property);
    }

  return FALSE;
}

static void
glade_command_set_i18n_collapse (GladeCommand *this_cmd,
                                 GladeCommand *other_cmd)
{
  /* this command is the one that will be used for an undo of the sequence of like commands */
  GladeCommandSetI18n *this = GLADE_COMMAND_SET_I18N (this_cmd);

  /* the other command contains the values that will be used for a redo */
  GladeCommandSetI18n *other = GLADE_COMMAND_SET_I18N (other_cmd);

  g_return_if_fail (GLADE_IS_COMMAND_SET_I18N (this_cmd) &&
                    GLADE_IS_COMMAND_SET_I18N (other_cmd));

  /* adjust this command to contain, as its old values, the other command's current values */
  this->old_translatable = other->old_translatable;
  g_free (this->old_context);
  g_free (this->old_comment);
  this->old_context = other->old_context;
  this->old_comment = other->old_comment;
  other->old_context = NULL;
  other->old_comment = NULL;
}

/**
 * glade_command_set_i18n:
 * @property: a #GladeProperty
 * @translatable: a #gboolean
 * @context: a #const gchar *
 * @comment: a #const gchar *
 *
 * Sets the i18n data on the property.
 */
void
glade_command_set_i18n (GladeProperty *property,
                        gboolean translatable,
                        const gchar *context,
                        const gchar *comment)
{
  GladeCommandSetI18n *me;
  GladeCommand *cmd;
  GladeCommandPrivate *priv;

  g_return_if_fail (property);

  /* check that something changed before continuing with the command */
  if (translatable == glade_property_i18n_get_translatable (property) &&
      !g_strcmp0 (glade_property_i18n_get_context (property), context) &&
      !g_strcmp0 (glade_property_i18n_get_comment (property), comment))
    return;

  /* load up the command */
  me = g_object_new (GLADE_TYPE_COMMAND_SET_I18N, NULL);
  me->property = property;
  me->translatable = translatable;
  me->context = g_strdup (context);
  me->comment = g_strdup (comment);
  me->old_translatable = glade_property_i18n_get_translatable (property);
  me->old_context = g_strdup (glade_property_i18n_get_context (property));
  me->old_comment = g_strdup (glade_property_i18n_get_comment (property));

  cmd = GLADE_COMMAND (me);
  priv = glade_command_get_instance_private (cmd);
  priv->project = glade_widget_get_project (glade_property_get_widget (property));
  priv->description = g_strdup_printf (_("Setting i18n metadata"));

  glade_command_check_group (cmd);

  /* execute the command and push it on the stack if successful */
  if (glade_command_set_i18n_execute (cmd))
    glade_project_push_undo (priv->project, cmd);
  else
    g_object_unref (G_OBJECT (me));
}

/******************************************************************************
 * 
 * This command sets protection warnings on widgets
 * 
 *****************************************************************************/

struct _GladeCommandLock
{
  GladeCommand parent;

  GladeWidget *widget;
  GladeWidget *locked;
  gboolean locking;
};

#define GLADE_TYPE_COMMAND_LOCK glade_command_lock_get_type ()
GLADE_MAKE_COMMAND (GladeCommandLock, glade_command_lock, COMMAND_LOCK);

static gboolean
glade_command_lock_execute (GladeCommand *cmd)
{
  GladeCommandLock *me = (GladeCommandLock *) cmd;

  /* set the new policy */
  if (me->locking)
    glade_widget_lock (me->widget, me->locked);
  else
    glade_widget_unlock (me->locked);

  /* swap the current values with the old values to prepare for undo */
  me->locking = !me->locking;

  return TRUE;
}

static gboolean
glade_command_lock_undo (GladeCommand *cmd)
{
  return glade_command_lock_execute (cmd);
}

static void
glade_command_lock_finalize (GObject *obj)
{
  GladeCommandLock *me = (GladeCommandLock *) obj;

  g_object_unref (me->widget);
  g_object_unref (me->locked);

  glade_command_finalize (obj);
}

static gboolean
glade_command_lock_unifies (GladeCommand *this_cmd, GladeCommand *other_cmd)
{
/* GladeCommandLock *cmd1; */
/* GladeCommandLock *cmd2; */
  /* No point here, this command undoubtedly always runs in groups */
  return FALSE;
}

static void
glade_command_lock_collapse (GladeCommand *this_cmd, GladeCommand *other_cmd)
{
  /* this command is the one that will be used for an undo of the sequence of like commands */
  //GladeCommandLock *this = GLADE_COMMAND_LOCK (this_cmd);

  /* the other command contains the values that will be used for a redo */
  //GladeCommandLock *other = GLADE_COMMAND_LOCK (other_cmd);

  g_return_if_fail (GLADE_IS_COMMAND_LOCK (this_cmd) &&
                    GLADE_IS_COMMAND_LOCK (other_cmd));

  /* no unify/collapse */
}

/**
 * glade_command_lock_widget:
 * @widget: A #GladeWidget
 * @locked: The #GladeWidget to lock
 *
 * Sets @locked to be in a locked up state
 * spoken for by @widget, locked widgets cannot
 * be removed from the project until unlocked.
 */
void
glade_command_lock_widget (GladeWidget *widget, GladeWidget *locked)
{
  GladeCommandLock *me;
  GladeCommand *cmd;
  GladeCommandPrivate *priv;

  g_return_if_fail (GLADE_IS_WIDGET (widget));
  g_return_if_fail (GLADE_IS_WIDGET (locked));
  g_return_if_fail (glade_widget_get_locker (locked) == NULL);

  /* load up the command */
  me = g_object_new (GLADE_TYPE_COMMAND_LOCK, NULL);
  me->widget = g_object_ref (widget);
  me->locked = g_object_ref (locked);
  me->locking = TRUE;

  cmd = GLADE_COMMAND (me);
  priv = glade_command_get_instance_private (cmd);
  priv->project = glade_widget_get_project (widget);
  priv->description =
    g_strdup_printf (_("Locking %s by widget %s"), 
                     glade_widget_get_display_name (locked),
                     glade_widget_get_display_name (widget));

  glade_command_check_group (cmd);

  /* execute the command and push it on the stack if successful 
   * this sets the actual policy
   */
  if (glade_command_lock_execute (cmd))
    glade_project_push_undo (priv->project, cmd);
  else
    g_object_unref (G_OBJECT (me));

}


/**
 * glade_command_unlock_widget:
 * @widget: A #GladeWidget
 *
 * Unlocks @widget so that it can be removed
 * from the project again
 *
 */
void
glade_command_unlock_widget (GladeWidget *widget)
{
  GladeCommandLock *me;
  GladeCommand *cmd;
  GladeCommandPrivate *priv;

  g_return_if_fail (GLADE_IS_WIDGET (widget));
  g_return_if_fail (GLADE_IS_WIDGET (glade_widget_get_locker (widget)));

  /* load up the command */
  me = g_object_new (GLADE_TYPE_COMMAND_LOCK, NULL);
  me->widget = g_object_ref (glade_widget_get_locker (widget));
  me->locked = g_object_ref (widget);
  me->locking = FALSE;

  cmd = GLADE_COMMAND (me);
  priv = glade_command_get_instance_private (cmd);
  priv->project = glade_widget_get_project (widget);
  priv->description =
    g_strdup_printf (_("Unlocking %s"), glade_widget_get_display_name (widget));

  glade_command_check_group (cmd);

  /* execute the command and push it on the stack if successful 
   * this sets the actual policy
   */
  if (glade_command_lock_execute (cmd))
    glade_project_push_undo (priv->project, cmd);
  else
    g_object_unref (G_OBJECT (me));

}


/******************************************************************************
 * 
 * This command sets the target version of a GladeProject
 * 
 *****************************************************************************/
struct _GladeCommandTarget
{
  GladeCommand parent;

  gchar       *catalog;
  gint         old_major;
  gint         old_minor;
  gint         new_major;
  gint         new_minor;
};

#define GLADE_TYPE_COMMAND_TARGET glade_command_target_get_type ()
GLADE_MAKE_COMMAND (GladeCommandTarget, glade_command_target, COMMAND_TARGET);

static gboolean
glade_command_target_execute (GladeCommand *cmd)
{
  GladeCommandPrivate *priv = glade_command_get_instance_private (cmd);
  GladeCommandTarget *me = (GladeCommandTarget *) cmd;

  glade_project_set_target_version (priv->project,
                                    me->catalog,
                                    me->new_major,
                                    me->new_minor);

  return TRUE;
}

static gboolean
glade_command_target_undo (GladeCommand *cmd)
{
  GladeCommandPrivate *priv = glade_command_get_instance_private (cmd);
  GladeCommandTarget *me = (GladeCommandTarget *) cmd;

  glade_project_set_target_version (priv->project,
                                    me->catalog,
                                    me->old_major,
                                    me->old_minor);

  return TRUE;
}

static void
glade_command_target_finalize (GObject *obj)
{
  GladeCommandTarget *me = (GladeCommandTarget *) obj;

  g_free (me->catalog);

  glade_command_finalize (obj);
}

static gboolean
glade_command_target_unifies (GladeCommand *this_cmd, GladeCommand *other_cmd)
{
  GladeCommandTarget *me;

  /* Do we unify with self ? */
  if (!other_cmd)
    {
      if (GLADE_IS_COMMAND_TARGET (this_cmd))
        {
          me = (GladeCommandTarget *) this_cmd;

          return (me->old_major == me->new_major &&
                  me->old_minor == me->new_minor);
        }
      return FALSE;
    }

  if (GLADE_IS_COMMAND_TARGET (this_cmd) &&
      GLADE_IS_COMMAND_TARGET (other_cmd))
    {
      GladeCommandTarget *other;

      me = (GladeCommandTarget *) this_cmd;
      other = (GladeCommandTarget *) other_cmd;

      return g_strcmp0 (me->catalog, other->catalog) == 0;
    }

  return FALSE;
}

static void
glade_command_target_collapse (GladeCommand *this_cmd, GladeCommand *other_cmd)
{
  GladeCommandPrivate *this_priv = glade_command_get_instance_private (this_cmd);
  GladeCommandTarget *this;
  GladeCommandTarget *other;

  g_return_if_fail (GLADE_IS_COMMAND_TARGET (this_cmd) &&
                    GLADE_IS_COMMAND_TARGET (other_cmd));

  this = GLADE_COMMAND_TARGET (this_cmd);
  other = GLADE_COMMAND_TARGET (other_cmd);

  this->new_major = other->new_major;
  this->new_minor = other->new_minor;

  g_free (this_priv->description);
  this_priv->description =
    g_strdup_printf (_("Setting target version of '%s' to %d.%d"), 
                     this->catalog, this->new_major, this->new_minor);

}

/**
 * glade_command_set_project_target:
 * @project: A #GladeProject
 * @catalog: The name of the catalog to set the project's target for
 * @major: The new major version of @catalog to target
 * @minor: The new minor version of @catalog to target
 *
 * Sets the target of @catalog to @major.@minor in @project.
 */
void
glade_command_set_project_target  (GladeProject *project,
                                   const gchar  *catalog,
                                   gint          major,
                                   gint          minor)
{
  GladeCommandTarget *me;
  GladeCommand *cmd;
  GladeCommandPrivate *priv;
  gint old_major = 0;
  gint old_minor = 0;

  g_return_if_fail (GLADE_IS_PROJECT (project));
  g_return_if_fail (catalog && catalog[0]);
  g_return_if_fail (major >= 0);
  g_return_if_fail (minor >= 0);

  /* load up the command */
  me = g_object_new (GLADE_TYPE_COMMAND_TARGET, NULL);
  me->catalog = g_strdup (catalog);
  
  cmd = GLADE_COMMAND (me);
  priv = glade_command_get_instance_private (cmd);
  priv->project = project;

  glade_project_get_target_version (project, me->catalog, &old_major, &old_minor);

  me->new_major = major;
  me->new_minor = minor;
  me->old_major = old_major;
  me->old_minor = old_minor;

  priv->description =
    g_strdup_printf (_("Setting target version of '%s' to %d.%d"), 
                     me->catalog, me->new_major, me->new_minor);

  glade_command_check_group (cmd);

  /* execute the command and push it on the stack if successful 
   * this sets the actual policy
   */
  if (glade_command_target_execute (cmd))
    glade_project_push_undo (priv->project, cmd);
  else
    g_object_unref (G_OBJECT (me));
}

/******************************************************************************
 * 
 * This command sets can set different properties of a GladeProject
 * 
 *****************************************************************************/
typedef gchar *(*DescriptionNewFunc) (GladeCommand *);

struct _GladeCommandProperty
{
  GladeCommand parent;

  const gchar *property_id;            /* Intern string */
  DescriptionNewFunc description_new;  /* Used to update command description */
  GValue old_value;
  GValue new_value;
};

#define GLADE_TYPE_COMMAND_PROPERTY glade_command_property_get_type ()
GLADE_MAKE_COMMAND (GladeCommandProperty, glade_command_property, COMMAND_PROPERTY)

/* Return true if a == b, this could be exported in glade_utils */
static gboolean
glade_command_property_compare (GValue *a, GValue *b)
{
  if (G_VALUE_TYPE (a) != G_VALUE_TYPE (b))
    {
      g_warning ("Comparing a %s with a %s type is not supported",
                 G_VALUE_TYPE_NAME (a), G_VALUE_TYPE_NAME (b));
      return FALSE;
    }

  if (G_VALUE_HOLDS_STRING (a))
    return g_strcmp0 (g_value_get_string (a), g_value_get_string (b)) == 0;
  else if (G_VALUE_HOLDS_OBJECT (a))
    return g_value_get_object (a) == g_value_get_object (b);
  else if (G_VALUE_HOLDS_BOOLEAN (a))
    return g_value_get_boolean (a) == g_value_get_boolean (b);
  else if (G_VALUE_HOLDS_CHAR (a))
    return g_value_get_schar (a) == g_value_get_schar (b);
  else if (G_VALUE_HOLDS_DOUBLE (a))
    return g_value_get_double (a) == g_value_get_double (b);
  else if (G_VALUE_HOLDS_ENUM (a))
    return g_value_get_enum (a) == g_value_get_enum (b);
  else if (G_VALUE_HOLDS_FLAGS (a))
    return g_value_get_flags (a) == g_value_get_flags (b);
  else if (G_VALUE_HOLDS_FLOAT (a))
    return g_value_get_float (a) == g_value_get_float (b);
  else if (G_VALUE_HOLDS_GTYPE (a))
    return g_value_get_gtype (a) == g_value_get_gtype (b);
  else if (G_VALUE_HOLDS_INT (a))
    return g_value_get_int (a) == g_value_get_int (b);
  else if (G_VALUE_HOLDS_INT64 (a))
    return g_value_get_int64 (a) == g_value_get_int64 (b);
  else if (G_VALUE_HOLDS_LONG (a))
    return g_value_get_long (a) == g_value_get_long (b);
  else if (G_VALUE_HOLDS_POINTER (a))
    return g_value_get_pointer (a) == g_value_get_pointer (b);
  else if (G_VALUE_HOLDS_UCHAR (a))
    return g_value_get_uchar (a) == g_value_get_uchar (b);
  else if (G_VALUE_HOLDS_UINT (a))
    return g_value_get_uint (a) == g_value_get_uint (b);
  else if (G_VALUE_HOLDS_UINT64 (a))
    return g_value_get_uint64 (a) == g_value_get_uint64 (b);
  else if (G_VALUE_HOLDS_ULONG (a))
    return g_value_get_ulong (a) == g_value_get_ulong (b);

  g_warning ("%s type not supported", G_VALUE_TYPE_NAME (a));
  return FALSE;
}   

static gboolean
glade_command_property_execute (GladeCommand *cmd)
{
  GladeCommandPrivate *priv = glade_command_get_instance_private (cmd);
  GladeCommandProperty *me = (GladeCommandProperty *) cmd;

  g_object_set_property (G_OBJECT (priv->project), me->property_id, &me->new_value);

  return TRUE;
}

static gboolean
glade_command_property_undo (GladeCommand *cmd)
{
  GladeCommandPrivate *priv = glade_command_get_instance_private (cmd);
  GladeCommandProperty *me = (GladeCommandProperty *) cmd;

  g_object_set_property (G_OBJECT (priv->project), me->property_id, &me->old_value);

  return TRUE;
}

static void
glade_command_property_finalize (GObject *obj)
{
  GladeCommandProperty *me = (GladeCommandProperty *) obj;

  /* NOTE: we do not free me->property_id because it is an intern string */
  g_value_unset (&me->new_value);
  g_value_unset (&me->old_value);

  glade_command_finalize (obj);
}

static gboolean
glade_command_property_unifies (GladeCommand *this_cmd, GladeCommand *other_cmd)
{
  /* Do we unify with self ? */
  if (!other_cmd)
    {
      if (GLADE_IS_COMMAND_PROPERTY (this_cmd))
        {
          GladeCommandProperty *me = (GladeCommandProperty *) this_cmd;
          return glade_command_property_compare (&me->new_value, &me->old_value);
        }
      else
        return FALSE;
    }

  if (GLADE_IS_COMMAND_PROPERTY (this_cmd) && GLADE_IS_COMMAND_PROPERTY (other_cmd))
    {
      GladeCommandProperty *this = (GladeCommandProperty *) this_cmd;
      GladeCommandProperty *other = (GladeCommandProperty *) other_cmd;

      /* Intern strings can be compared by comparing the pointers */
      return this->property_id == other->property_id;
    }

  return FALSE;
}

static void
glade_command_property_update_description (GladeCommand *cmd)
{
  GladeCommandPrivate *priv = glade_command_get_instance_private (cmd);
  GladeCommandProperty *me = (GladeCommandProperty *) cmd;

  g_free (priv->description);
  
  if (me->description_new)
    priv->description = me->description_new (cmd);
  else
    priv->description = g_strdup_printf (_("Setting project's %s property"),
                                         me->property_id);
}

static void
glade_command_property_collapse (GladeCommand *this_cmd, GladeCommand *other_cmd)
{
  GladeCommandProperty *this;
  GladeCommandProperty *other;

  g_return_if_fail (GLADE_IS_COMMAND_PROPERTY (this_cmd) &&
                    GLADE_IS_COMMAND_PROPERTY (other_cmd));

  this = GLADE_COMMAND_PROPERTY (this_cmd);
  other = GLADE_COMMAND_PROPERTY (other_cmd);

  g_return_if_fail (this->property_id == other->property_id);

  g_value_copy (&other->new_value, &this->new_value);

  glade_command_property_update_description (this_cmd);
}

/**
 * glade_command_set_project_property:
 * @project: A #GladeProject
 * @description_new: function to create the command description.
 * @property_id: property this command should use
 * @new_value: the value to set @property_id
 *
 * Sets @new_value as the @property_id property for @project.
 */
static void
glade_command_set_project_property (GladeProject       *project,
                                    DescriptionNewFunc  description_new,
                                    const gchar        *property_id,
                                    GValue             *new_value)
{
  GladeCommandProperty *me;
  GladeCommand *cmd;
  GladeCommandPrivate *priv;
  GValue old_value = G_VALUE_INIT;

  g_value_init (&old_value, G_VALUE_TYPE (new_value));
  g_object_get_property (G_OBJECT (project), property_id, &old_value);
  
  if (glade_command_property_compare (&old_value, new_value))
    {
      g_value_unset (&old_value);
      return;
    }

  me = g_object_new (GLADE_TYPE_COMMAND_PROPERTY, NULL);
  cmd = GLADE_COMMAND (me);
  priv = glade_command_get_instance_private (cmd);
  priv->project = project;

  me->description_new = description_new;
  me->property_id = g_intern_static_string (property_id);

  /* move the old value to the command struct */
  me->old_value = old_value;

  /* set new value */
  g_value_init (&me->new_value, G_VALUE_TYPE (new_value));
  g_value_copy (new_value, &me->new_value);

  glade_command_property_update_description (cmd);
    
  glade_command_check_group (cmd);

  /* execute the command and push it on the stack if successful 
   * this sets the actual policy
   */
  if (glade_command_property_execute (cmd))
    glade_project_push_undo (priv->project, cmd);
  else
    g_object_unref (G_OBJECT (me));
}

/**
 * glade_command_set_project_license:
 * @project: A #GladeProject
 * @license: License of @project
 *
 * Sets the license agreement for @project. It will be saved in the xml as comment.
 */
void
glade_command_set_project_license (GladeProject *project, const gchar *license)
{
  GValue new_value = G_VALUE_INIT;
  
  g_return_if_fail (GLADE_IS_PROJECT (project));
  
  g_value_init (&new_value, G_TYPE_STRING);
  g_value_set_string (&new_value, license);
  
  glade_command_set_project_property (project, NULL, "license", &new_value);

  g_value_unset (&new_value);
}

static gchar *
gcp_resource_path_description_new (GladeCommand *cmd)
{
  GladeCommandProperty *me = (GladeCommandProperty *) cmd;

  return g_strdup_printf (_("Setting resource path to '%s'"),
                          g_value_get_string (&me->new_value));
}

/**
 * glade_command_set_project_resource_path:
 * @project: A #GladeProject
 * @path: path to load resources from. 
 *
 * Sets a resource path @project.
 */
void
glade_command_set_project_resource_path (GladeProject *project, const gchar *path)
{
  GValue new_value = G_VALUE_INIT;
  
  g_return_if_fail (GLADE_IS_PROJECT (project));
  
  g_value_init (&new_value, G_TYPE_STRING);
  g_value_set_string (&new_value, path);
  
  glade_command_set_project_property (project, gcp_resource_path_description_new,
                                      "resource-path", &new_value);
  g_value_unset (&new_value);
}

static gchar *
gcp_domain_description_new (GladeCommand *cmd)
{
  GladeCommandProperty *me = (GladeCommandProperty *) cmd;

  return g_strdup_printf (_("Setting translation domain to '%s'"),
                          g_value_get_string (&me->new_value));
}

/**
 * glade_command_set_project_domain:
 * @project: A #GladeProject
 * @domain: The translation domain for @project
 *
 * Sets @domain as the translation domain for @project.
 */
void
glade_command_set_project_domain  (GladeProject *project,
                                   const gchar  *domain)
{
  GValue new_value = G_VALUE_INIT;
  
  g_return_if_fail (GLADE_IS_PROJECT (project));
  
  g_value_init (&new_value, G_TYPE_STRING);
  g_value_set_string (&new_value, domain);
  
  glade_command_set_project_property (project, gcp_domain_description_new,
                                      "translation-domain", &new_value);
  g_value_unset (&new_value);
}

static gchar *
gcp_template_description_new (GladeCommand *cmd)
{
  GladeCommandProperty *me = (GladeCommandProperty *) cmd;
  GObject *new_template = g_value_get_object (&me->new_value);
  GObject *old_template = g_value_get_object (&me->old_value);

  if (new_template == NULL && old_template != NULL)
    return g_strdup_printf (_("Unsetting widget '%s' as template"),
                            glade_widget_get_display_name (GLADE_WIDGET (old_template)));
  else if (new_template != NULL)
    return g_strdup_printf (_("Setting widget '%s' as template"),
                            glade_widget_get_display_name (GLADE_WIDGET (new_template)));
  else
    return g_strdup (_("Unsetting template"));
}

/**
 * glade_command_set_project_template:
 * @project: A #GladeProject
 * @widget: The #GladeWidget to make template
 *
 * Sets @widget to be the template widget in @project.
 */
void
glade_command_set_project_template (GladeProject *project,
                                    GladeWidget  *widget)
{
  GValue new_value = G_VALUE_INIT;
  
  g_return_if_fail (GLADE_IS_PROJECT (project));
  
  g_value_init (&new_value, G_TYPE_OBJECT);
  g_value_set_object (&new_value, widget);
  
  glade_command_set_project_property (project, gcp_template_description_new,
                                      "template", &new_value);
  g_value_unset (&new_value);
}
