/*
 * glade-fixed.c - A GladeWidget derivative object wrapper designed to
 *                 handle free-form child placement for containers such as 
 *                 GtkFixed and GtkLayout.
 *
 * Copyright (C) 2006, 2013 Tristan Van Berkom.
 *
 * Author(s):
 *      Tristan Van Berkom <tvb@gnome.org>
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser 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.
 */

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

/**
 * SECTION:glade-fixed
 * @Short_Description: An object wrapper for free-form placement container widgets.
 *
 * #GladeFixed is a specialized #GladeWidget to handle free-form child
 * placements in containers that support this, it is designed with properties
 * and signals with flexable integration in mind.
 *
 * If you set the x-prop/y-prop/width-prop/height-prop properties and
 * leave the signals alone, #GladeFixed will assume you are like a 
 * GtkFixed/GtkLayout widget and will use pixel counts as units for
 * these properties.
 *
 * If you handle the configure-child/configure-end[/configure-begin] signals
 * and dont let them propagate to the GladeFixed, then the x-prop/y-prop/width-prop/height-prop
 * properties will be completely ignored and it is up to the implementor to play
 * with whatever child packing properties are available to make a closest match
 * for the values passed to configure-child via the #GdkRectangle.
 */

#include <glib-object.h>
#include <glib/gi18n-lib.h>
#include <gdk/gdkkeysyms.h>

#include <gladeui/glade.h>
#include <gladeui/glade-marshallers.h>
#include <gladeui/glade-popup.h>
#include "glade-gtk-marshallers.h"
#include "glade-fixed.h"

/* properties */
enum
{
  PROP_0,
  PROP_X_PROP,
  PROP_Y_PROP,
  PROP_WIDTH_PROP,
  PROP_HEIGHT_PROP,
  PROP_CAN_RESIZE
};

/* signals */
enum
{
  CONFIGURE_CHILD,
  CONFIGURE_BEGIN,
  CONFIGURE_END,
  FIXED_SIGNALS
};

typedef struct
{
  gulong press_id;
  gulong release_id;
  gulong motion_id;
} GFSigData;

#define CHILD_WIDTH_MIN    20
#define CHILD_HEIGHT_MIN   20
#define CHILD_WIDTH_DEF    100
#define CHILD_HEIGHT_DEF   80

#define GRAB_BORDER_WIDTH  7
#define GRAB_CORNER_WIDTH  7

static guint glade_fixed_signals[FIXED_SIGNALS];

G_DEFINE_TYPE (GladeFixed, glade_fixed, GLADE_TYPE_WIDGET);

/* From gtkmain.c */
static gboolean
glade_fixed_boolean_handled_accumulator (GSignalInvocationHint * ihint,
					 GValue * return_accu,
					 const GValue * handler_return,
					 gpointer dummy)
{
  gboolean continue_emission;
  gboolean signal_handled;

  signal_handled = g_value_get_boolean (handler_return);
  g_value_set_boolean (return_accu, signal_handled);
  continue_emission = !signal_handled;

  return continue_emission;
}

/*******************************************************************************
                             Static helper routines
 *******************************************************************************/
typedef enum {
  HEXPAND_SET_FLAG = (1 << 0),
  VEXPAND_SET_FLAG = (1 << 1),
  HEXPAND_FLAG = (1 << 2),
  VEXPAND_FLAG = (1 << 3),
} ExpandData;

static void
save_expand_data (GtkWidget *widget)
{
  guint    flags;
  gboolean h_expand;
  gboolean v_expand;
  gboolean h_expand_set;
  gboolean v_expand_set;

  g_object_get (G_OBJECT (widget),
		"hexpand-set", &h_expand_set,
		"vexpand-set", &v_expand_set,
		"hexpand", &h_expand,
		"vexpand", &v_expand,
		NULL);

  flags = 
    (h_expand_set ? HEXPAND_SET_FLAG : 0) |
    (v_expand_set ? VEXPAND_SET_FLAG : 0) |
    (h_expand ? HEXPAND_FLAG : 0) |
    (v_expand ? VEXPAND_FLAG : 0);

  g_object_set_data (G_OBJECT (widget), "glade-gtk-box-child-expand", GUINT_TO_POINTER (flags));
}

static void
restore_expand_data (GtkWidget *widget)
{
  guint    flags;

  flags = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (widget), "glade-gtk-box-child-expand"));

  g_object_set (G_OBJECT (widget),
		"hexpand",     ((flags & HEXPAND_FLAG) != 0),
		"vexpand",     ((flags & VEXPAND_FLAG) != 0),
		"hexpand-set", ((flags & HEXPAND_SET_FLAG) != 0),
		"vexpand-set", ((flags & VEXPAND_SET_FLAG) != 0),
		NULL);
}

static void
expand_all_children (GladeWidget *widget)
{
  GList *children, *l;

  children = glade_widget_get_children (widget);

  for (l = children; l; l = l->next)
    {
      GtkWidget *child = l->data;

      save_expand_data (child);

      gtk_widget_set_hexpand (child, TRUE);
      gtk_widget_set_vexpand (child, TRUE);
    }
}

static void
restore_all_children (GladeWidget *widget)
{
  GList *children, *l;

  children = glade_widget_get_children (widget);

  for (l = children; l; l = l->next)
    {
      GtkWidget *child = l->data;

      restore_expand_data (child);
    }
}


static GladeCursorType
glade_fixed_get_operation (GtkWidget *widget, gint x, gint y)
{
  GladeCursorType operation = GLADE_CURSOR_DRAG;
  GtkAllocation allocation;

#if 0
  g_print ("%s called (width %d height %d x %d y %d)\n",
           __FUNCTION__,
           widget->allocation.width, widget->allocation.height, x, y);
#endif

  gtk_widget_get_allocation (widget, &allocation);
  if (x < GRAB_BORDER_WIDTH)
    {
      if (y < GRAB_BORDER_WIDTH)
        operation = GLADE_CURSOR_RESIZE_TOP_LEFT;
      else if (y > allocation.height - GRAB_BORDER_WIDTH)
        operation = GLADE_CURSOR_RESIZE_BOTTOM_LEFT;
      else
        operation = GLADE_CURSOR_RESIZE_LEFT;
    }
  else if (x > allocation.width - GRAB_BORDER_WIDTH)
    {
      if (y < GRAB_BORDER_WIDTH)
        operation = GLADE_CURSOR_RESIZE_TOP_RIGHT;
      else if (y > allocation.height - GRAB_BORDER_WIDTH)
        operation = GLADE_CURSOR_RESIZE_BOTTOM_RIGHT;
      else
        operation = GLADE_CURSOR_RESIZE_RIGHT;
    }
  else if (y < GRAB_BORDER_WIDTH)
    {
      if (x < GRAB_BORDER_WIDTH)
        operation = GLADE_CURSOR_RESIZE_TOP_LEFT;
      else if (x > allocation.width - GRAB_BORDER_WIDTH)
        operation = GLADE_CURSOR_RESIZE_TOP_RIGHT;
      else
        operation = GLADE_CURSOR_RESIZE_TOP;
    }
  else if (y > allocation.height - GRAB_BORDER_WIDTH)
    {
      if (x < GRAB_BORDER_WIDTH)
        operation = GLADE_CURSOR_RESIZE_BOTTOM_LEFT;
      else if (x > allocation.width - GRAB_BORDER_WIDTH)
        operation = GLADE_CURSOR_RESIZE_BOTTOM_RIGHT;
      else
        operation = GLADE_CURSOR_RESIZE_BOTTOM;
    }
  return operation;
}

static void
glade_fixed_save_state (GladeFixed *fixed, GladeWidget *child, GdkDevice *device)
{
  GtkAllocation allocation;
  GtkWidget    *widget;
  GtkWidget    *child_widget;

  widget       = GTK_WIDGET (glade_widget_get_object (GLADE_WIDGET (fixed)));
  child_widget = GTK_WIDGET (glade_widget_get_object (child));

  glade_utils_get_pointer (widget, gtk_widget_get_window (widget), device,
			   &(GLADE_FIXED (fixed)->pointer_x_origin),
			   &(GLADE_FIXED (fixed)->pointer_y_origin));

  gtk_widget_translate_coordinates (child_widget, widget, 0, 0,
                                    &(fixed->child_x_origin),
                                    &(fixed->child_y_origin));

  gtk_widget_get_allocation (child_widget, &allocation);
  fixed->child_width_origin = allocation.width;
  fixed->child_height_origin = allocation.height;

  fixed->pointer_x_child_origin =
      fixed->pointer_x_origin - fixed->child_x_origin;
  fixed->pointer_y_child_origin =
      fixed->pointer_y_origin - fixed->child_y_origin;
}


static void
glade_fixed_filter_event (GladeFixed *fixed,
                          gint *x, gint *y,
                          gint left, gint right,
                          gint top, gint bottom)
{
  GtkAllocation allocation;
  gint cont_width, cont_height;
  GtkWidget    *widget;

  g_return_if_fail (x && y);

  widget = GTK_WIDGET (glade_widget_get_object (GLADE_WIDGET (fixed)));

  gtk_widget_get_allocation (widget, &allocation);
  cont_width = allocation.width;
  cont_height = allocation.height;

  /* Clip out mouse events that are outside the window.
   */
  if ((left || fixed->operation == GLADE_CURSOR_DRAG) &&
      *x - fixed->pointer_x_child_origin < 0)
    *x = fixed->pointer_x_child_origin;
  if ((top || fixed->operation == GLADE_CURSOR_DRAG) &&
      *y - fixed->pointer_y_child_origin < 0)
    *y = fixed->pointer_y_child_origin;

  if ((right || fixed->operation == GLADE_CURSOR_DRAG) &&
      *x + (fixed->child_width_origin -
            fixed->pointer_x_child_origin) > cont_width)
    *x = cont_width - (fixed->child_width_origin -
                       fixed->pointer_x_child_origin);
  if ((bottom || fixed->operation == GLADE_CURSOR_DRAG) &&
      *y + (fixed->child_height_origin -
            fixed->pointer_y_child_origin) > cont_height)
    *y = cont_height - (fixed->child_height_origin -
                        fixed->pointer_y_child_origin);

  /* Clip out mouse events that mean shrinking to less than 0.
   */
  if (left &&
      (*x - fixed->pointer_x_child_origin) >
      (fixed->child_x_origin + (fixed->child_width_origin - CHILD_WIDTH_MIN)))
    {
      *x = (fixed->child_x_origin +
            (fixed->child_width_origin - CHILD_WIDTH_MIN)) -
          fixed->pointer_x_child_origin;
    }
  else if (right &&
           (*x - fixed->pointer_x_child_origin) <
           fixed->child_x_origin - (fixed->child_width_origin +
                                    CHILD_WIDTH_MIN))
    {
      *x = (fixed->child_x_origin -
            (fixed->child_width_origin + CHILD_WIDTH_MIN)) +
          fixed->pointer_x_child_origin;
    }


  if (top &&
      (*y - fixed->pointer_y_child_origin) >
      (fixed->child_y_origin + (fixed->child_height_origin - CHILD_HEIGHT_MIN)))
    {
      *y = (fixed->child_y_origin +
            (fixed->child_height_origin - CHILD_HEIGHT_MIN)) -
          fixed->pointer_y_child_origin;
    }
  else if (bottom &&
           (*y - fixed->pointer_y_child_origin) <
           fixed->child_y_origin - (fixed->child_height_origin +
                                    CHILD_HEIGHT_MIN))
    {
      *y = (fixed->child_y_origin -
            (fixed->child_height_origin + CHILD_HEIGHT_MIN)) +
          fixed->pointer_y_child_origin;
    }
}

static void
glade_fixed_handle_swindow (GladeFixed *fixed, GdkRectangle *area)
{
  GtkWidget *swindow = NULL, *swindow_child = NULL, *widget;
  GtkAdjustment *hadj, *vadj;
  GtkAllocation child_allocation;
  GtkWidget *fixed_widget;
  GladeWidget *gwidget;
  gint x, y;

  widget = fixed_widget = GTK_WIDGET (glade_widget_get_object (GLADE_WIDGET (fixed)));

  while (widget && !GTK_IS_SCROLLED_WINDOW (widget) &&
         (
          ((gwidget = glade_widget_get_from_gobject (widget)) &&
           glade_widget_get_parent (gwidget)) ||
          !gwidget))
    {
      if (!GTK_IS_VIEWPORT (widget))
        swindow_child = widget;

      if (GTK_IS_SCROLLED_WINDOW (widget))
        {
          swindow = widget;
          break;
        }

      widget = gtk_widget_get_parent (widget);
    }

  if (swindow)
    {
      /* Set the adjustments to use appropriate pixel units and then
       * square in on the target drop area.
       */
      hadj =
          gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (swindow));
      vadj =
          gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (swindow));
      gtk_widget_get_allocation (swindow_child, &child_allocation);

      g_object_set (G_OBJECT (hadj),
                    "lower", 0.0F,
                    "upper", (gdouble) child_allocation.width + 0.0, NULL);

      g_object_set (G_OBJECT (vadj),
                    "lower", 0.0F,
                    "upper", (gdouble) child_allocation.height + 0.0, NULL);

      gtk_widget_translate_coordinates (fixed_widget,
                                        swindow_child,
                                        area->x, area->y, &x, &y);

      gtk_adjustment_clamp_page (hadj, x, x + area->width);
      gtk_adjustment_clamp_page (vadj, y, y + area->height);
    }
}

static void
glade_fixed_configure_widget (GladeFixed *fixed,
                              GladeWidget *child,
                              GdkDevice *device)
{
  GladeWidget *gwidget = GLADE_WIDGET (fixed);
  GtkWidget   *widget;
  GdkRectangle new_area;
  gboolean handled, right, left, top, bottom;
  gint x, y;

  widget = GTK_WIDGET (glade_widget_get_object (gwidget));

  glade_utils_get_pointer (widget, gtk_widget_get_window (widget), device, &x, &y);

  right = GLADE_FIXED_CURSOR_RIGHT (fixed->operation);
  left = GLADE_FIXED_CURSOR_LEFT (fixed->operation);
  top = GLADE_FIXED_CURSOR_TOP (fixed->operation);
  bottom = GLADE_FIXED_CURSOR_BOTTOM (fixed->operation);

  /* Filter out events that make your widget go out of bounds */
  glade_fixed_filter_event (fixed, &x, &y, left, right, top, bottom);

  new_area.x = fixed->child_x_origin;
  new_area.y = fixed->child_y_origin;
  new_area.width = fixed->child_width_origin;
  new_area.height = fixed->child_height_origin;

  /* Modify current size.
   */
  if (fixed->operation == GLADE_CURSOR_DRAG)
    {
      /* Move widget */
      new_area.x = fixed->child_x_origin + x - fixed->pointer_x_origin;
      new_area.y = fixed->child_y_origin + y - fixed->pointer_y_origin;

    }
  else
    {

      if (bottom)
        {
          new_area.height =
              fixed->child_height_origin + (y - fixed->pointer_y_origin);
        }
      else if (top)
        {

          new_area.height =
              fixed->child_height_origin - (y - fixed->pointer_y_origin);
          new_area.y = fixed->child_y_origin + (y - fixed->pointer_y_origin);
        }

      if (right)
        {
          new_area.width =
              fixed->child_width_origin + (x - fixed->pointer_x_origin);
        }
      else if (left)
        {
          new_area.width =
              fixed->child_width_origin - (x - fixed->pointer_x_origin);
          new_area.x = fixed->child_x_origin + (x - fixed->pointer_x_origin);
        }
    }

  /* Trim */
  if (new_area.width < CHILD_WIDTH_MIN)
    new_area.width = CHILD_WIDTH_MIN;
  if (new_area.height < CHILD_WIDTH_MIN)
    new_area.height = CHILD_HEIGHT_MIN;

  /* before configuring the child widget, make make sure the target child
   * widget area is visible if the GladeFixed is placed inside a scrolled 
   * window or a viewport inside a scrolled window.
   */
  glade_fixed_handle_swindow (fixed, &new_area);

  /* Apply new rectangle to the object */
  g_signal_emit (G_OBJECT (fixed),
                 glade_fixed_signals[CONFIGURE_CHILD],
                 0, child, &new_area, &handled);

  /* Correct glitches when some widgets are draged over others */
  gtk_widget_queue_draw (widget);
}

static void
glade_fixed_disconnect_child (GladeFixed *fixed, GladeWidget *child)
{
  GFSigData *data;

  if (GTK_IS_WIDGET (glade_widget_get_object (child)) == FALSE)
    return;

  if ((data =
       g_object_get_data (G_OBJECT (child), "glade-fixed-signal-data")) != NULL)
    {
      g_signal_handler_disconnect (child, data->press_id);
      g_signal_handler_disconnect (child, data->release_id);
      g_signal_handler_disconnect (child, data->motion_id);

      g_object_set_data (G_OBJECT (child), "glade-fixed-signal-data", NULL);
    }
}

static void
glade_fixed_connect_child (GladeFixed *fixed, GladeWidget *child)
{
  GFSigData *data;

  if (GTK_IS_WIDGET (glade_widget_get_object (child)) == FALSE)
    return;

  if ((data =
       g_object_get_data (G_OBJECT (child), "glade-fixed-signal-data")) != NULL)
    glade_fixed_disconnect_child (fixed, child);

  data = g_new (GFSigData, 1);

  /* Connect-after here... leave a chance for selection
   */
  data->press_id =
      g_signal_connect_after
      (child, "button-press-event", G_CALLBACK
       (GLADE_FIXED_GET_CLASS (fixed)->child_event), fixed);
  data->release_id =
      g_signal_connect
      (child, "button-release-event", G_CALLBACK
       (GLADE_FIXED_GET_CLASS (fixed)->child_event), fixed);
  data->motion_id =
      g_signal_connect
      (child, "motion-notify-event", G_CALLBACK
       (GLADE_FIXED_GET_CLASS (fixed)->child_event), fixed);


  g_object_set_data_full (G_OBJECT (child), "glade-fixed-signal-data",
                          data, g_free);
}

/*******************************************************************************
                               GladeFixedClass
 *******************************************************************************/
static gboolean
glade_fixed_configure_child_impl (GladeFixed *fixed,
                                  GladeWidget *child,
                                  GdkRectangle *rect)
{
  /* Make sure we can modify these properties
   *
   * FIXME: This is inappropriate how that enabled state of properties
   * is undoable... instead there should be some adaptor hooks to allow
   * adding commands to the command group which adds a child to
   * the given widget... where we can undoably override this.
   */
  glade_widget_pack_property_set_enabled (child, fixed->x_prop, TRUE);
  glade_widget_pack_property_set_enabled (child, fixed->y_prop, TRUE);
  glade_widget_property_set_enabled (child, fixed->width_prop, TRUE);
  glade_widget_property_set_enabled (child, fixed->height_prop, TRUE);

  glade_widget_pack_property_set (child, fixed->x_prop, rect->x);
  glade_widget_pack_property_set (child, fixed->y_prop, rect->y);
  glade_widget_property_set (child, fixed->width_prop, rect->width);
  glade_widget_property_set (child, fixed->height_prop, rect->height);
  return TRUE;
}


static gboolean
glade_fixed_configure_end_impl (GladeFixed *fixed, GladeWidget *child)
{
  GValue x_value = { 0, };
  GValue y_value = { 0, };
  GValue width_value = { 0, };
  GValue height_value = { 0, };
  GValue new_x_value = { 0, };
  GValue new_y_value = { 0, };
  GValue new_width_value = { 0, };
  GValue new_height_value = { 0, };
  GladeProperty *x_prop, *y_prop, *width_prop, *height_prop;

  x_prop = glade_widget_get_pack_property (child, fixed->x_prop);
  y_prop = glade_widget_get_pack_property (child, fixed->y_prop);
  width_prop = glade_widget_get_property (child, fixed->width_prop);
  height_prop = glade_widget_get_property (child, fixed->height_prop);

  g_return_val_if_fail (GLADE_IS_PROPERTY (x_prop), FALSE);
  g_return_val_if_fail (GLADE_IS_PROPERTY (y_prop), FALSE);
  g_return_val_if_fail (GLADE_IS_PROPERTY (width_prop), FALSE);
  g_return_val_if_fail (GLADE_IS_PROPERTY (height_prop), FALSE);

  g_value_init (&x_value, G_TYPE_INT);
  g_value_init (&y_value, G_TYPE_INT);
  g_value_init (&width_value, G_TYPE_INT);
  g_value_init (&height_value, G_TYPE_INT);

  glade_property_get_value (x_prop, &new_x_value);
  glade_property_get_value (y_prop, &new_y_value);
  glade_property_get_value (width_prop, &new_width_value);
  glade_property_get_value (height_prop, &new_height_value);

  g_value_set_int (&x_value, fixed->child_x_origin);
  g_value_set_int (&y_value, fixed->child_y_origin);
  g_value_set_int (&width_value, fixed->child_width_origin);
  g_value_set_int (&height_value, fixed->child_height_origin);

  glade_command_push_group (_("Placing %s inside %s"),
                            glade_widget_get_name (child), 
			    glade_widget_get_name (GLADE_WIDGET (fixed)));

  /* whew, all that for this call !
   */
  glade_command_set_properties (x_prop, &x_value, &new_x_value,
                                y_prop, &y_value, &new_y_value,
                                width_prop, &width_value, &new_width_value,
                                height_prop, &height_value, &new_height_value,
                                NULL);

  glade_command_pop_group ();

  g_value_unset (&x_value);
  g_value_unset (&y_value);
  g_value_unset (&width_value);
  g_value_unset (&height_value);
  g_value_unset (&new_x_value);
  g_value_unset (&new_y_value);
  g_value_unset (&new_width_value);
  g_value_unset (&new_height_value);

  return TRUE;
}


static void
glade_fixed_cancel_operation (GladeFixed *fixed, GladeCursorType new_operation)
{
  gboolean handled;

  g_signal_emit (G_OBJECT (fixed),
                 glade_fixed_signals[CONFIGURE_END],
                 0, fixed->configuring, &handled);

  restore_all_children (GLADE_WIDGET (fixed));

  /* Leave the machine state intact untill after
   * the user handled signal. 
   */
  fixed->operation = new_operation;
  fixed->configuring = NULL;
}

static gboolean
glade_fixed_handle_child_event (GladeFixed *fixed,
                                GladeWidget *child,
                                GtkWidget *event_widget,
                                GdkEvent *event)
{
  GladeCursorType operation;
  GdkModifierType event_state = 0;
  GladePointerMode pointer_mode;
  gboolean handled = FALSE, sig_handled;
  GladeProject *project = glade_widget_get_project (GLADE_WIDGET (fixed));
  GdkWindow *window = event->any.window;
  GtkWidget *fixed_widget, *child_widget;

  fixed_widget = GTK_WIDGET (glade_widget_get_object (GLADE_WIDGET (fixed)));
  child_widget = GTK_WIDGET (glade_widget_get_object (child));

  pointer_mode = glade_project_get_pointer_mode (project);

  if (fixed->can_resize)
    {
      gint fixed_x, fixed_y, child_x, child_y;
      GdkDevice *device;

      device = gdk_event_get_device (event);
      
      glade_utils_get_pointer (fixed_widget,
			       window,
			       device, &fixed_x, &fixed_y);

      /* Container widgets are trustable to have widget->window occupying
       * the entire widget allocation (gtk_widget_get_pointer broken on GtkEntry).
       */
      gtk_widget_translate_coordinates (fixed_widget,
                                        child_widget,
                                        fixed_x, fixed_y, &child_x, &child_y);
      
      operation = glade_fixed_get_operation (child_widget, child_x, child_y);
    }
  else
    operation = GLADE_CURSOR_DRAG;

  gdk_event_get_state (event, &event_state);

  switch (event->type)
    {
      case GDK_MOTION_NOTIFY:
        if (fixed->configuring == NULL)
          {
            if ((event_state & GDK_SHIFT_MASK) ||
                pointer_mode == GLADE_POINTER_DRAG_RESIZE)
              glade_cursor_set (project, window, operation);
            else if (pointer_mode == GLADE_POINTER_SELECT)
              glade_cursor_set (project, window, GLADE_CURSOR_SELECTOR);
          }
        else if (fixed->configuring && !(event_state & GDK_BUTTON1_MASK))
          {
            /* Cancel drags that no longer have mouse down */
            glade_cursor_set (project, window, operation);

            glade_fixed_cancel_operation (fixed, operation);
            handled = TRUE;
          }
        else if (fixed->configuring)
          {
            /* Need to update mouse for configures. */
	    glade_utils_get_pointer (fixed_widget,
				     window, gdk_event_get_device (event),
				     &fixed->mouse_x, &fixed->mouse_y);

            glade_fixed_configure_widget (fixed, child, event->motion.device);
            glade_cursor_set (project, window, fixed->operation);
            handled = TRUE;
          }
        break;
      case GDK_BUTTON_PRESS:
        /* We cant rely on GDK_BUTTON1_MASK since event->state isnt yet updated
         * by the current event itself 
         */
        if (event->button.button == 1 &&
            ((event_state & GDK_SHIFT_MASK) ||
             pointer_mode == GLADE_POINTER_DRAG_RESIZE))
          {


	    expand_all_children (GLADE_WIDGET (fixed));

	    /* Spin the main loop so that the GladeFixed
	     * widget gets reallocated before storing
	     * the allocation sizes
	     */
	    while (gtk_events_pending ())
	      gtk_main_iteration ();




            fixed->configuring = child;
            /* Save widget allocation and pointer pos */
            glade_fixed_save_state (fixed, child, event->button.device);

            fixed->operation = operation;
            glade_cursor_set (project, window, fixed->operation);

            g_signal_emit (G_OBJECT (fixed),
                           glade_fixed_signals[CONFIGURE_BEGIN],
                           0, child, &sig_handled);

            handled = TRUE;
          }
        break;
      case GDK_BUTTON_RELEASE:
        if (event->button.button == 1 && fixed->configuring)
          {
            if ((event_state & GDK_SHIFT_MASK) ||
                pointer_mode == GLADE_POINTER_DRAG_RESIZE)
              glade_cursor_set (project, window, operation);
            else
              glade_cursor_set (project, window, GLADE_CURSOR_SELECTOR);

            glade_fixed_cancel_operation (fixed, operation);
            handled = TRUE;
          }
        break;
      default:
        break;
    }
  return handled;
}

static gint
glade_fixed_child_event (GladeWidget *gwidget,
                         GdkEvent *event,
                         GladeFixed *fixed)
{
  GtkWidget *event_widget;
  GladeProject *project = glade_widget_get_project (gwidget);

  /* Get the basic event info... */
  gdk_window_get_user_data (((GdkEventAny *) event)->window,
                            (gpointer) & event_widget);

  /* Skip all this choosyness if we're already in a drag/resize
   */
  if (fixed->configuring)
    {
      return glade_fixed_handle_child_event
          (fixed, fixed->configuring, event_widget, event);
    }

  g_return_val_if_fail (GLADE_IS_WIDGET (gwidget), FALSE);

  /* Early return for fixed children with selection in
   * the palette.
   */
  if (GLADE_IS_FIXED (gwidget) &&
      glade_project_get_pointer_mode (project) == GLADE_POINTER_ADD_WIDGET)
    {
      glade_cursor_set (project, ((GdkEventAny *) event)->window,
                        GLADE_CURSOR_ADD_WIDGET);
      return FALSE;
    }

  return glade_fixed_handle_child_event (fixed, gwidget, event_widget, event);

}

/*******************************************************************************
                               GladeWidgetClass
 *******************************************************************************/
static void
glade_fixed_add_child_impl (GladeWidget *gwidget_fixed,
                            GladeWidget *child,
                            gboolean at_mouse)
{
  GladeFixed *fixed = GLADE_FIXED (gwidget_fixed);
  GtkAllocation allocation;
  GdkRectangle rect;
  gboolean handled;
  GtkWidget *widget;

  g_return_if_fail (GLADE_IS_FIXED (fixed));
  g_return_if_fail (GLADE_IS_WIDGET (child));

  /* Need to explicitly find the pointer location at drag_drop time (but check if the widget has
   * a window already first, if not it's because we're loading and widget's arent realized yet)
   */
  widget = GTK_WIDGET (glade_widget_get_object (gwidget_fixed));
  if (gtk_widget_get_window (widget))
    glade_utils_get_pointer (widget, NULL, NULL,
			     &fixed->mouse_x, &fixed->mouse_y);

  /* Chain up for the basic parenting */
  GLADE_WIDGET_CLASS (glade_fixed_parent_class)->add_child
      (GLADE_WIDGET (fixed), child, at_mouse);

  /* We only operate on widgets here */
  if (!GTK_IS_WIDGET (glade_widget_get_object (child)))
    return;

  gtk_widget_add_events (GTK_WIDGET (glade_widget_get_object (child)),
                         GDK_POINTER_MOTION_MASK |
                         GDK_POINTER_MOTION_HINT_MASK |
                         GDK_BUTTON_PRESS_MASK |
                         GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK);

  glade_fixed_connect_child (fixed, child);

  /* Setup rect and send configure
   */
  if (fixed->creating)
    {
      rect.x = fixed->mouse_x;
      rect.y = fixed->mouse_y;
      rect.width = CHILD_WIDTH_DEF;
      rect.height = CHILD_HEIGHT_DEF;

      g_signal_emit (G_OBJECT (fixed),
                     glade_fixed_signals[CONFIGURE_CHILD],
                     0, child, &rect, &handled);
    }
  else if (at_mouse)
    {
      gtk_widget_get_allocation (GTK_WIDGET (glade_widget_get_object (child)), &allocation);
      rect.x = fixed->mouse_x;
      rect.y = fixed->mouse_y;
      rect.width = allocation.width;
      rect.height = allocation.height;

      if (rect.width < CHILD_WIDTH_DEF)
        rect.width = CHILD_WIDTH_DEF;

      if (rect.height < CHILD_HEIGHT_DEF)
        rect.height = CHILD_HEIGHT_DEF;

      g_signal_emit (G_OBJECT (fixed),
                     glade_fixed_signals[CONFIGURE_CHILD],
                     0, child, &rect, &handled);
    }
  return;
}

static void
glade_fixed_remove_child_impl (GladeWidget *fixed, GladeWidget *child)
{
  glade_fixed_disconnect_child (GLADE_FIXED (fixed), child);

  /* Chain up for the basic unparenting */
  GLADE_WIDGET_CLASS (glade_fixed_parent_class)->remove_child (GLADE_WIDGET (fixed), child);
}

static void
glade_fixed_replace_child_impl (GladeWidget *fixed,
                                GObject *old_object,
                                GObject *new_object)
{
  GladeWidget *gnew_widget = glade_widget_get_from_gobject (new_object);
  GladeWidget *gold_widget = glade_widget_get_from_gobject (old_object);

  if (gold_widget)
    glade_fixed_disconnect_child (GLADE_FIXED (fixed), gold_widget);

  /* Chain up for the basic reparenting */
  GLADE_WIDGET_CLASS (glade_fixed_parent_class)->replace_child
      (GLADE_WIDGET (fixed), old_object, new_object);

  if (gnew_widget)
    glade_fixed_connect_child (GLADE_FIXED (fixed), gnew_widget);
}

static gint
glade_fixed_event (GladeWidget *gwidget_fixed, GdkEvent *event)
{
  GladeFixed *fixed = GLADE_FIXED (gwidget_fixed);
  GladeWidgetAdaptor *adaptor;
  GtkWidget *event_widget;
  GladeProject *project = glade_widget_get_project (gwidget_fixed);
  gboolean handled = FALSE;
  GdkWindow *window = event->any.window;
  GdkDevice *device;
  
  adaptor = glade_project_get_add_item (project);

  /* Get the event widget and the deep widget */
  gdk_window_get_user_data (event->any.window, (gpointer) &event_widget);

  /* If the GladeWidget used this event... let it slide.
   */
  if (GLADE_WIDGET_CLASS (glade_fixed_parent_class)->event (gwidget_fixed, event))
    return TRUE;

  if ((device = gdk_event_get_device (event)))
    {
      /* Need to update mouse for configures. */
      glade_utils_get_pointer (GTK_WIDGET (glade_widget_get_object (gwidget_fixed)),
			       window, device, &fixed->mouse_x, &fixed->mouse_y);

      if (fixed->configuring)
        {
          return glade_fixed_handle_child_event
            (fixed, fixed->configuring, event_widget, event);
        }
    }

  /* If the container uses placeholder adding widget is already taken care of */
  if (GWA_USE_PLACEHOLDERS (glade_widget_get_adaptor (gwidget_fixed)))
    return FALSE;
  
  switch (event->type)
    {
      case GDK_BUTTON_PRESS:   /* add widget */
        if (event->button.button == 1)
          {

            if (adaptor != NULL)
              {
                /* A widget type is selected in the palette.
                 * Add a new widget of that type.
                 */
                fixed->creating = TRUE;
                glade_command_create (adaptor,
                                      GLADE_WIDGET (fixed), NULL,
                                      glade_widget_get_project (GLADE_WIDGET (fixed)));
                fixed->creating = FALSE;

		glade_project_set_add_item (project, NULL);

                handled = TRUE;
              }
          }
        break;
      case GDK_MOTION_NOTIFY:
        if (glade_project_get_pointer_mode (project) == GLADE_POINTER_ADD_WIDGET)
          {
            glade_cursor_set (project, window, GLADE_CURSOR_ADD_WIDGET);

            handled = TRUE;
          }
        else if (GLADE_IS_FIXED (glade_widget_get_parent (gwidget_fixed)) == FALSE &&
                 glade_project_get_pointer_mode (project) == GLADE_POINTER_SELECT)
          glade_cursor_set (project, window, GLADE_CURSOR_SELECTOR);
        break;
      default:
        break;
    }
  return handled;
}

/*******************************************************************************
                                   GObjectClass
 *******************************************************************************/
static void
glade_fixed_finalize (GObject *object)
{
  GladeFixed *fixed = GLADE_FIXED (object);

  /* A GladeFixed should be finalized as a result of its
   * GtkContainer being destroyed, so we shouldn't need to bother
   * about disconnecting all the child signals.
   */
  g_free (fixed->x_prop);
  g_free (fixed->y_prop);
  g_free (fixed->width_prop);
  g_free (fixed->height_prop);

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

static void
glade_fixed_set_property (GObject *object,
                          guint prop_id,
                          const GValue *value,
                          GParamSpec *pspec)
{
  GladeFixed *fixed = GLADE_FIXED (object);

  switch (prop_id)
    {
      case PROP_X_PROP:
        g_free (fixed->x_prop);
        fixed->x_prop = g_value_dup_string (value);
        break;
      case PROP_Y_PROP:
        g_free (fixed->y_prop);
        fixed->y_prop = g_value_dup_string (value);
        break;
      case PROP_WIDTH_PROP:
        g_free (fixed->width_prop);
        fixed->width_prop = g_value_dup_string (value);
        break;
      case PROP_HEIGHT_PROP:
        g_free (fixed->height_prop);
        fixed->height_prop = g_value_dup_string (value);
        break;
      case PROP_CAN_RESIZE:
        fixed->can_resize = g_value_get_boolean (value);
        break;
      default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
        break;
    }
}

static void
glade_fixed_get_property (GObject *object,
                          guint prop_id,
                          GValue *value,
                          GParamSpec *pspec)
{
  GladeFixed *fixed = GLADE_FIXED (object);

  switch (prop_id)
    {
      case PROP_X_PROP:
        g_value_set_string (value, fixed->x_prop);
        break;
      case PROP_Y_PROP:
        g_value_set_string (value, fixed->y_prop);
        break;
      case PROP_WIDTH_PROP:
        g_value_set_string (value, fixed->width_prop);
        break;
      case PROP_HEIGHT_PROP:
        g_value_set_string (value, fixed->height_prop);
        break;
      case PROP_CAN_RESIZE:
        g_value_set_boolean (value, fixed->can_resize);
        break;
      default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
        break;
    }
}

static void
glade_fixed_init (GladeFixed *fixed)
{
  /* Set defaults */
  fixed->x_prop = g_strdup ("x");
  fixed->y_prop = g_strdup ("y");
  fixed->width_prop = g_strdup ("width");
  fixed->height_prop = g_strdup ("height");
  fixed->can_resize = TRUE;
}

static void
glade_fixed_class_init (GladeFixedClass *fixed_class)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (fixed_class);
  GladeWidgetClass *gwidget_class = GLADE_WIDGET_CLASS (fixed_class);

  gobject_class->finalize = glade_fixed_finalize;
  gobject_class->set_property = glade_fixed_set_property;
  gobject_class->get_property = glade_fixed_get_property;

  gwidget_class->event = glade_fixed_event;
  gwidget_class->add_child = glade_fixed_add_child_impl;
  gwidget_class->remove_child = glade_fixed_remove_child_impl;
  gwidget_class->replace_child = glade_fixed_replace_child_impl;

  fixed_class->configure_child = glade_fixed_configure_child_impl;
  fixed_class->configure_begin = NULL;
  fixed_class->configure_end = glade_fixed_configure_end_impl;
  fixed_class->child_event = glade_fixed_child_event;

  /* Properties */
  g_object_class_install_property
      (gobject_class, PROP_X_PROP,
       g_param_spec_string
       ("x_prop", _("X position property"),
        _("The property used to set the X position of a child object"),
        "x", G_PARAM_READWRITE | G_PARAM_CONSTRUCT));

  g_object_class_install_property
      (gobject_class, PROP_Y_PROP,
       g_param_spec_string
       ("y_prop", _("Y position property"),
        _("The property used to set the Y position of a child object"),
        "y", G_PARAM_READWRITE | G_PARAM_CONSTRUCT));

  g_object_class_install_property
      (gobject_class, PROP_WIDTH_PROP,
       g_param_spec_string
       ("width_prop", _("Width property"),
        _("The property used to set the width of a child object"),
        "width-request", G_PARAM_READWRITE | G_PARAM_CONSTRUCT));

  g_object_class_install_property
      (gobject_class, PROP_HEIGHT_PROP,
       g_param_spec_string
       ("height_prop", _("Height property"),
        _("The property used to set the height of a child object"),
        "height-request", G_PARAM_READWRITE | G_PARAM_CONSTRUCT));

  g_object_class_install_property
      (gobject_class, PROP_CAN_RESIZE,
       g_param_spec_boolean
       ("can_resize", _("Can resize"),
        _("Whether this container supports resizes of child widgets"),
        TRUE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT));

        /**
	 * GladeFixed::configure-child:
	 * @gladewidget: the #GladeFixed which received the signal.
	 * @arg1: the child #GladeWidget
	 * @arg2: a pointer to a #GdkRectange describing the new size.
	 *
	 * Delegates the Drag/Resize job.
	 *
	 * Returns: %TRUE means you have handled the event and cancels the
	 *          default handler from being triggered.
	 */
  glade_fixed_signals[CONFIGURE_CHILD] =
      g_signal_new ("configure-child",
                    G_TYPE_FROM_CLASS (gobject_class),
                    G_SIGNAL_RUN_LAST,
                    G_STRUCT_OFFSET
                    (GladeFixedClass, configure_child),
                    glade_fixed_boolean_handled_accumulator, NULL,
                    glade_gtk_marshal_BOOLEAN__OBJECT_POINTER,
                    G_TYPE_BOOLEAN, 2, G_TYPE_OBJECT, G_TYPE_POINTER);

        /**
	 * GladeFixed::configure-begin:
	 * @gladewidget: the #GladeFixed which received the signal.
	 * @arg1: the child #GladeWidget
	 *
	 * Signals the beginning of a Drag/Resize
	 *
	 * Returns: %TRUE means you have handled the event and cancels the
	 *          default handler from being triggered.
	 */
  glade_fixed_signals[CONFIGURE_BEGIN] =
      g_signal_new ("configure-begin",
                    G_TYPE_FROM_CLASS (gobject_class),
                    G_SIGNAL_RUN_LAST,
                    G_STRUCT_OFFSET
                    (GladeFixedClass, configure_begin),
                    glade_fixed_boolean_handled_accumulator, NULL,
                    glade_gtk_marshal_BOOLEAN__OBJECT,
                    G_TYPE_BOOLEAN, 1, G_TYPE_OBJECT);

        /**
	 * GladeFixed::configure-end:
	 * @gladewidget: the #GladeFixed which received the signal.
	 * @arg1: the child #GladeWidget
	 *
	 * Signals the end of a Drag/Resize
	 *
	 * Returns: %TRUE means you have handled the event and cancels the
	 *          default handler from being triggered.
	 */
  glade_fixed_signals[CONFIGURE_END] =
      g_signal_new ("configure-end",
                    G_TYPE_FROM_CLASS (gobject_class),
                    G_SIGNAL_RUN_LAST,
                    G_STRUCT_OFFSET
                    (GladeFixedClass, configure_end),
                    glade_fixed_boolean_handled_accumulator, NULL,
                    glade_gtk_marshal_BOOLEAN__OBJECT,
                    G_TYPE_BOOLEAN, 1, G_TYPE_OBJECT);
}


/*******************************************************************************
                                      API
*******************************************************************************/

/* This is called from the catalog for a few widgets */
GladeWidget *
glade_gtk_create_fixed_widget (GladeWidgetAdaptor * adaptor,
                               const gchar * first_property_name,
                               va_list var_args)
{
  return (GladeWidget *) g_object_new_valist (GLADE_TYPE_FIXED,
                                              first_property_name, var_args);
}
