/* GIMP - The GNU Image Manipulation Program
 * Copyright (C) 1995 Spencer Kimball and Peter Mattis
 *
 * gimptoolrectangle.c
 * Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
 *
 * Based on GimpRectangleTool
 * Copyright (C) 2007 Martin Nordholts
 *
 * 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 3 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, see <https://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <gegl.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>

#include "libgimpbase/gimpbase.h"
#include "libgimpmath/gimpmath.h"

#include "display-types.h"

#include "core/gimp.h"
#include "core/gimpcontext.h"
#include "core/gimpimage.h"
#include "core/gimpitem.h"
#include "core/gimpmarshal.h"
#include "core/gimppickable.h"
#include "core/gimppickable-auto-shrink.h"

#include "widgets/gimpwidgets-utils.h"

#include "gimpcanvasarc.h"
#include "gimpcanvascorner.h"
#include "gimpcanvashandle.h"
#include "gimpcanvasitem-utils.h"
#include "gimpcanvasrectangle.h"
#include "gimpcanvasrectangleguides.h"
#include "gimpdisplay.h"
#include "gimpdisplayshell.h"
#include "gimpdisplayshell-scroll.h"
#include "gimptoolrectangle.h"

#include "gimp-intl.h"


/*  speed of key movement  */
#define ARROW_VELOCITY   25

#define MAX_HANDLE_SIZE         50
#define MIN_HANDLE_SIZE         15
#define NARROW_MODE_HANDLE_SIZE 15
#define NARROW_MODE_THRESHOLD   45


enum
{
  PROP_0,
  PROP_X1,
  PROP_Y1,
  PROP_X2,
  PROP_Y2,
  PROP_CONSTRAINT,
  PROP_PRECISION,
  PROP_NARROW_MODE,
  PROP_FORCE_NARROW_MODE,
  PROP_DRAW_ELLIPSE,
  PROP_ROUND_CORNERS,
  PROP_CORNER_RADIUS,
  PROP_STATUS_TITLE,

  PROP_HIGHLIGHT,
  PROP_HIGHLIGHT_OPACITY,
  PROP_GUIDE,
  PROP_X,
  PROP_Y,
  PROP_WIDTH,
  PROP_HEIGHT,
  PROP_FIXED_RULE_ACTIVE,
  PROP_FIXED_RULE,
  PROP_DESIRED_FIXED_WIDTH,
  PROP_DESIRED_FIXED_HEIGHT,
  PROP_DESIRED_FIXED_SIZE_WIDTH,
  PROP_DESIRED_FIXED_SIZE_HEIGHT,
  PROP_ASPECT_NUMERATOR,
  PROP_ASPECT_DENOMINATOR,
  PROP_FIXED_CENTER
};

enum
{
  CHANGE_COMPLETE,
  LAST_SIGNAL
};

typedef enum
{
  CLAMPED_NONE   = 0,
  CLAMPED_LEFT   = 1 << 0,
  CLAMPED_RIGHT  = 1 << 1,
  CLAMPED_TOP    = 1 << 2,
  CLAMPED_BOTTOM = 1 << 3
} ClampedSide;

typedef enum
{
  SIDE_TO_RESIZE_NONE,
  SIDE_TO_RESIZE_LEFT,
  SIDE_TO_RESIZE_RIGHT,
  SIDE_TO_RESIZE_TOP,
  SIDE_TO_RESIZE_BOTTOM,
  SIDE_TO_RESIZE_LEFT_AND_RIGHT_SYMMETRICALLY,
  SIDE_TO_RESIZE_TOP_AND_BOTTOM_SYMMETRICALLY,
} SideToResize;


#define FEQUAL(a,b)       (fabs ((a) - (b)) < 0.0001)
#define PIXEL_FEQUAL(a,b) (fabs ((a) - (b)) < 0.5)


struct _GimpToolRectanglePrivate
{
  /* The following members are "constants", that is, variables that are setup
   * during gimp_tool_rectangle_button_press and then only read.
   */

  /* Whether or not the rectangle currently being rubber-banded is the
   * first one created with this instance, this determines if we can
   * undo it on button_release.
   */
  gboolean                is_first;

  /* Whether or not the rectangle currently being rubber-banded was
   * created from scratch.
   */
  gboolean                is_new;

  /* Holds the coordinate that should be used as the "other side" when
   * fixed-center is turned off.
   */
  gdouble                 other_side_x;
  gdouble                 other_side_y;

  /* Holds the coordinate to be used as center when fixed-center is used. */
  gdouble                 center_x_on_fixed_center;
  gdouble                 center_y_on_fixed_center;

  /* True when the rectangle is being adjusted (moved or
   * rubber-banded).
   */
  gboolean                rect_adjusting;


  /* The rest of the members are internal state variables, that is, variables
   * that might change during the manipulation session of the rectangle. Make
   * sure these variables are in consistent states.
   */

  /* Coordinates of upper left and lower right rectangle corners. */
  gdouble                 x1, y1;
  gdouble                 x2, y2;

  /* Integer coordinats of upper left corner and size. We must
   * calculate this separately from the gdouble ones because sometimes
   * we don't want to affect the integer size (e.g. when moving the
   * rectangle), but that will be the case if we always calculate the
   * integer coordinates based on rounded values of the gdouble
   * coordinates even if the gdouble width remains constant.
   *
   * TODO: Change the internal double-representation of the rectangle
   * to x,y width,height instead of x1,y1 x2,y2. That way we don't
   * need to keep a separate representation of the integer version of
   * the rectangle; rounding width an height will yield consistent
   * results and not depend on position of the rectangle.
   */
  gint                    x1_int,    y1_int;
  gint                    width_int, height_int;

  /* How to constrain the rectangle. */
  GimpRectangleConstraint constraint;

  /* What precision the rectangle will appear to have externally (it
   * will always be double internally)
   */
  GimpRectanglePrecision  precision;

  /* Previous coordinate applied to the rectangle. */
  gdouble                 lastx;
  gdouble                 lasty;

  /* Width and height of corner handles. */
  gint                    corner_handle_w;
  gint                    corner_handle_h;

  /* Width and height of side handles. */
  gint                    top_and_bottom_handle_w;
  gint                    left_and_right_handle_h;

  /* Whether or not the rectangle is in a 'narrow situation' i.e. it is
   * too small for reasonable sized handle to be inside. In this case
   * we put handles on the outside.
   */
  gboolean                narrow_mode;

  /* This boolean allows to always set narrow mode */
  gboolean                force_narrow_mode;

  /* Whether or not to draw an ellipse inside the rectangle */
  gboolean                draw_ellipse;

  /* Whether to draw round corners */
  gboolean                round_corners;
  gdouble                 corner_radius;

  /* The title for the statusbar coords */
  gchar                  *status_title;

  /* For saving in case of cancellation. */
  gdouble                 saved_x1;
  gdouble                 saved_y1;
  gdouble                 saved_x2;
  gdouble                 saved_y2;

  gint                    suppress_updates;

  GimpRectangleFunction   function;

  /* The following values are externally synced with GimpRectangleOptions */

  gboolean                highlight;
  gdouble                 highlight_opacity;
  GimpGuidesType          guide;

  gdouble                 x;
  gdouble                 y;
  gdouble                 width;
  gdouble                 height;

  gboolean                fixed_rule_active;
  GimpRectangleFixedRule  fixed_rule;
  gdouble                 desired_fixed_width;
  gdouble                 desired_fixed_height;
  gdouble                 desired_fixed_size_width;
  gdouble                 desired_fixed_size_height;
  gdouble                 aspect_numerator;
  gdouble                 aspect_denominator;
  gboolean                fixed_center;

  /* Canvas items for drawing the GUI */

  GimpCanvasItem         *guides;
  GimpCanvasItem         *rectangle;
  GimpCanvasItem         *ellipse;
  GimpCanvasItem         *corners[4];
  GimpCanvasItem         *center;
  GimpCanvasItem         *creating_corners[4];
  GimpCanvasItem         *handles[GIMP_N_TOOL_RECTANGLE_FUNCTIONS];
  GimpCanvasItem         *highlight_handles[GIMP_N_TOOL_RECTANGLE_FUNCTIONS];

  /* Flags to prevent temporary changes to the Expand from center and Fixed
     options for the rectangle select tool, ellipse select tool, and the crop
     tool due to use of the modifier keys becoming latched. See issue #7954 and
     MR !779. */
  gboolean                fixed_center_copy;
  gboolean                fixed_rule_active_copy;
  gboolean                modifier_toggle_allowed;
};


/*  local function prototypes  */

static void     gimp_tool_rectangle_constructed     (GObject               *object);
static void     gimp_tool_rectangle_finalize        (GObject               *object);
static void     gimp_tool_rectangle_set_property    (GObject               *object,
                                                     guint                  property_id,
                                                     const GValue          *value,
                                                     GParamSpec            *pspec);
static void     gimp_tool_rectangle_get_property    (GObject               *object,
                                                     guint                  property_id,
                                                     GValue                *value,
                                                     GParamSpec            *pspec);
static void     gimp_tool_rectangle_notify          (GObject               *object,
                                                     GParamSpec            *pspec);

static void     gimp_tool_rectangle_changed         (GimpToolWidget        *widget);
static gint     gimp_tool_rectangle_button_press    (GimpToolWidget        *widget,
                                                     const GimpCoords      *coords,
                                                     guint32                time,
                                                     GdkModifierType        state,
                                                     GimpButtonPressType    press_type);
static void     gimp_tool_rectangle_button_release  (GimpToolWidget        *widget,
                                                     const GimpCoords      *coords,
                                                     guint32                time,
                                                     GdkModifierType        state,
                                                     GimpButtonReleaseType  release_type);
static void     gimp_tool_rectangle_motion          (GimpToolWidget        *widget,
                                                     const GimpCoords      *coords,
                                                     guint32                time,
                                                     GdkModifierType        state);
static GimpHit  gimp_tool_rectangle_hit             (GimpToolWidget        *widget,
                                                     const GimpCoords      *coords,
                                                     GdkModifierType        state,
                                                     gboolean               proximity);
static void     gimp_tool_rectangle_hover           (GimpToolWidget        *widget,
                                                     const GimpCoords      *coords,
                                                     GdkModifierType        state,
                                                     gboolean               proximity);
static void     gimp_tool_rectangle_leave_notify    (GimpToolWidget        *widget);
static gboolean gimp_tool_rectangle_key_press       (GimpToolWidget        *widget,
                                                     GdkEventKey           *kevent);
static void     gimp_tool_rectangle_motion_modifier (GimpToolWidget        *widget,
                                                     GdkModifierType        key,
                                                     gboolean               press,
                                                     GdkModifierType        state);
static gboolean gimp_tool_rectangle_get_cursor      (GimpToolWidget        *widget,
                                                     const GimpCoords      *coords,
                                                     GdkModifierType        state,
                                                     GimpCursorType        *cursor,
                                                     GimpToolCursorType    *tool_cursor,
                                                     GimpCursorModifier    *modifier);

static void     gimp_tool_rectangle_change_complete (GimpToolRectangle     *rectangle);

static void     gimp_tool_rectangle_update_options  (GimpToolRectangle     *rectangle);
static void     gimp_tool_rectangle_update_handle_sizes
                                                    (GimpToolRectangle     *rectangle);
static void     gimp_tool_rectangle_update_status   (GimpToolRectangle     *rectangle);

static void     gimp_tool_rectangle_synthesize_motion
                                                    (GimpToolRectangle     *rectangle,
                                                     gint               function,
                                                     gdouble            new_x,
                                                     gdouble            new_y);

static GimpRectangleFunction
                gimp_tool_rectangle_calc_function   (GimpToolRectangle     *rectangle,
                                                     const GimpCoords      *coords,
                                                     gboolean               proximity);
static void     gimp_tool_rectangle_check_function  (GimpToolRectangle     *rectangle);

static gboolean gimp_tool_rectangle_coord_outside   (GimpToolRectangle     *rectangle,
                                                     const GimpCoords      *coords);

static gboolean gimp_tool_rectangle_coord_on_handle (GimpToolRectangle     *rectangle,
                                                     const GimpCoords      *coords,
                                                     GimpHandleAnchor       anchor);

static GimpHandleAnchor gimp_tool_rectangle_get_anchor
                                                    (GimpRectangleFunction  function);
static gboolean gimp_tool_rectangle_rect_rubber_banding_func
                                                    (GimpToolRectangle     *rectangle);
static gboolean gimp_tool_rectangle_rect_adjusting_func
                                                    (GimpToolRectangle     *rectangle);

static void     gimp_tool_rectangle_get_other_side  (GimpToolRectangle     *rectangle,
                                                     gdouble              **other_x,
                                                     gdouble              **other_y);
static void     gimp_tool_rectangle_get_other_side_coord
                                                    (GimpToolRectangle     *rectangle,
                                                     gdouble               *other_side_x,
                                                     gdouble               *other_side_y);
static void     gimp_tool_rectangle_set_other_side_coord
                                                    (GimpToolRectangle     *rectangle,
                                                     gdouble                other_side_x,
                                                     gdouble                other_side_y);

static void     gimp_tool_rectangle_apply_coord     (GimpToolRectangle     *rectangle,
                                                     gdouble                coord_x,
                                                     gdouble                coord_y);
static void     gimp_tool_rectangle_setup_snap_offsets
                                                    (GimpToolRectangle     *rectangle,
                                                     const GimpCoords      *coords);

static void     gimp_tool_rectangle_clamp           (GimpToolRectangle     *rectangle,
                                                     ClampedSide           *clamped_sides,
                                                     GimpRectangleConstraint constraint,
                                                     gboolean               symmetrically);
static void     gimp_tool_rectangle_clamp_width     (GimpToolRectangle     *rectangle,
                                                     ClampedSide           *clamped_sides,
                                                     GimpRectangleConstraint constraint,
                                                     gboolean               symmetrically);
static void     gimp_tool_rectangle_clamp_height    (GimpToolRectangle     *rectangle,
                                                     ClampedSide           *clamped_sides,
                                                     GimpRectangleConstraint constraint,
                                                     gboolean               symmetrically);

static void     gimp_tool_rectangle_keep_inside     (GimpToolRectangle     *rectangle,
                                                     GimpRectangleConstraint constraint);
static void     gimp_tool_rectangle_keep_inside_horizontally
                                                    (GimpToolRectangle     *rectangle,
                                                     GimpRectangleConstraint constraint);
static void     gimp_tool_rectangle_keep_inside_vertically
                                                    (GimpToolRectangle     *rectangle,
                                                     GimpRectangleConstraint constraint);

static void     gimp_tool_rectangle_apply_fixed_width
                                                    (GimpToolRectangle     *rectangle,
                                                     GimpRectangleConstraint constraint,
                                                     gdouble                width);
static void     gimp_tool_rectangle_apply_fixed_height
                                                    (GimpToolRectangle     *rectangle,
                                                     GimpRectangleConstraint constraint,
                                                     gdouble                height);

static void     gimp_tool_rectangle_apply_aspect    (GimpToolRectangle     *rectangle,
                                                     gdouble                aspect,
                                                     gint                   clamped_sides);

static void     gimp_tool_rectangle_update_with_coord
                                                    (GimpToolRectangle     *rectangle,
                                                     gdouble                new_x,
                                                     gdouble                new_y);
static void     gimp_tool_rectangle_apply_fixed_rule(GimpToolRectangle     *rectangle);

static void     gimp_tool_rectangle_get_constraints (GimpToolRectangle     *rectangle,
                                                     gint                  *min_x,
                                                     gint                  *min_y,
                                                     gint                  *max_x,
                                                     gint                  *max_y,
                                                     GimpRectangleConstraint constraint);

static void     gimp_tool_rectangle_handle_general_clamping
                                                    (GimpToolRectangle     *rectangle);
static void     gimp_tool_rectangle_update_int_rect (GimpToolRectangle     *rectangle);
static void     gimp_tool_rectangle_adjust_coord    (GimpToolRectangle     *rectangle,
                                                     gdouble                coord_x_input,
                                                     gdouble                coord_y_input,
                                                     gdouble               *coord_x_output,
                                                     gdouble               *coord_y_output);
static void     gimp_tool_rectangle_recalculate_center_xy
                                                    (GimpToolRectangle     *rectangle);


G_DEFINE_TYPE_WITH_PRIVATE (GimpToolRectangle, gimp_tool_rectangle,
                            GIMP_TYPE_TOOL_WIDGET)

#define parent_class gimp_tool_rectangle_parent_class

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


static void
gimp_tool_rectangle_class_init (GimpToolRectangleClass *klass)
{
  GObjectClass        *object_class = G_OBJECT_CLASS (klass);
  GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass);

  object_class->constructed     = gimp_tool_rectangle_constructed;
  object_class->finalize        = gimp_tool_rectangle_finalize;
  object_class->set_property    = gimp_tool_rectangle_set_property;
  object_class->get_property    = gimp_tool_rectangle_get_property;
  object_class->notify          = gimp_tool_rectangle_notify;

  widget_class->changed         = gimp_tool_rectangle_changed;
  widget_class->button_press    = gimp_tool_rectangle_button_press;
  widget_class->button_release  = gimp_tool_rectangle_button_release;
  widget_class->motion          = gimp_tool_rectangle_motion;
  widget_class->hit             = gimp_tool_rectangle_hit;
  widget_class->hover           = gimp_tool_rectangle_hover;
  widget_class->leave_notify    = gimp_tool_rectangle_leave_notify;
  widget_class->key_press       = gimp_tool_rectangle_key_press;
  widget_class->motion_modifier = gimp_tool_rectangle_motion_modifier;
  widget_class->get_cursor      = gimp_tool_rectangle_get_cursor;
  widget_class->update_on_scale = TRUE;

  rectangle_signals[CHANGE_COMPLETE] =
    g_signal_new ("change-complete",
                  G_TYPE_FROM_CLASS (klass),
                  G_SIGNAL_RUN_FIRST,
                  G_STRUCT_OFFSET (GimpToolRectangleClass, change_complete),
                  NULL, NULL,
                  g_cclosure_marshal_VOID__VOID,
                  G_TYPE_NONE, 0);

  g_object_class_install_property (object_class, PROP_X1,
                                   g_param_spec_double ("x1",
                                                        NULL, NULL,
                                                        -GIMP_MAX_IMAGE_SIZE,
                                                        GIMP_MAX_IMAGE_SIZE,
                                                        0.0,
                                                        GIMP_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_Y1,
                                   g_param_spec_double ("y1",
                                                        NULL, NULL,
                                                        -GIMP_MAX_IMAGE_SIZE,
                                                        GIMP_MAX_IMAGE_SIZE,
                                                        0.0,
                                                        GIMP_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_X2,
                                   g_param_spec_double ("x2",
                                                        NULL, NULL,
                                                        -GIMP_MAX_IMAGE_SIZE,
                                                        GIMP_MAX_IMAGE_SIZE,
                                                        0.0,
                                                        GIMP_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_Y2,
                                   g_param_spec_double ("y2",
                                                        NULL, NULL,
                                                        -GIMP_MAX_IMAGE_SIZE,
                                                        GIMP_MAX_IMAGE_SIZE,
                                                        0.0,
                                                        GIMP_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_CONSTRAINT,
                                   g_param_spec_enum ("constraint",
                                                      NULL, NULL,
                                                      GIMP_TYPE_RECTANGLE_CONSTRAINT,
                                                      GIMP_RECTANGLE_CONSTRAIN_NONE,
                                                      GIMP_PARAM_READWRITE |
                                                      G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_PRECISION,
                                   g_param_spec_enum ("precision",
                                                      NULL, NULL,
                                                      GIMP_TYPE_RECTANGLE_PRECISION,
                                                      GIMP_RECTANGLE_PRECISION_INT,
                                                      GIMP_PARAM_READWRITE |
                                                      G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_NARROW_MODE,
                                   g_param_spec_boolean ("narrow-mode",
                                                         NULL, NULL,
                                                         FALSE,
                                                         GIMP_PARAM_READWRITE |
                                                         G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_FORCE_NARROW_MODE,
                                   g_param_spec_boolean ("force-narrow-mode",
                                                         NULL, NULL,
                                                         FALSE,
                                                         GIMP_PARAM_READWRITE |
                                                         G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_DRAW_ELLIPSE,
                                   g_param_spec_boolean ("draw-ellipse",
                                                         NULL, NULL,
                                                         FALSE,
                                                         GIMP_PARAM_READWRITE |
                                                         G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_ROUND_CORNERS,
                                   g_param_spec_boolean ("round-corners",
                                                         NULL, NULL,
                                                         FALSE,
                                                         GIMP_PARAM_READWRITE |
                                                         G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_CORNER_RADIUS,
                                   g_param_spec_double ("corner-radius",
                                                        NULL, NULL,
                                                        0.0, 10000.0, 10.0,
                                                        GIMP_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_STATUS_TITLE,
                                   g_param_spec_string ("status-title",
                                                        NULL, NULL,
                                                        _("Rectangle: "),
                                                        GIMP_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_HIGHLIGHT,
                                   g_param_spec_boolean ("highlight",
                                                         NULL, NULL,
                                                         FALSE,
                                                         GIMP_PARAM_READWRITE |
                                                         G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_HIGHLIGHT_OPACITY,
                                   g_param_spec_double ("highlight-opacity",
                                                        NULL, NULL,
                                                        0.0, 1.0, 0.5,
                                                        GIMP_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_GUIDE,
                                   g_param_spec_enum ("guide",
                                                      NULL, NULL,
                                                      GIMP_TYPE_GUIDES_TYPE,
                                                      GIMP_GUIDES_NONE,
                                                      GIMP_PARAM_READWRITE |
                                                      G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_X,
                                   g_param_spec_double ("x",
                                                        NULL, NULL,
                                                        -GIMP_MAX_IMAGE_SIZE,
                                                        GIMP_MAX_IMAGE_SIZE,
                                                        0.0,
                                                        GIMP_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_Y,
                                   g_param_spec_double ("y",
                                                        NULL, NULL,
                                                        -GIMP_MAX_IMAGE_SIZE,
                                                        GIMP_MAX_IMAGE_SIZE,
                                                        0.0,
                                                        GIMP_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_WIDTH,
                                   g_param_spec_double ("width",
                                                        NULL, NULL,
                                                        0.0,
                                                        GIMP_MAX_IMAGE_SIZE,
                                                        0.0,
                                                        GIMP_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_HEIGHT,
                                   g_param_spec_double ("height",
                                                        NULL, NULL,
                                                        0.0,
                                                        GIMP_MAX_IMAGE_SIZE,
                                                        0.0,
                                                        GIMP_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_FIXED_RULE_ACTIVE,
                                   g_param_spec_boolean ("fixed-rule-active",
                                                         NULL, NULL,
                                                         FALSE,
                                                         GIMP_PARAM_READWRITE |
                                                         G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_FIXED_RULE,
                                   g_param_spec_enum ("fixed-rule",
                                                      NULL, NULL,
                                                      GIMP_TYPE_RECTANGLE_FIXED_RULE,
                                                      GIMP_RECTANGLE_FIXED_ASPECT,
                                                      GIMP_PARAM_READWRITE |
                                                      G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_DESIRED_FIXED_WIDTH,
                                   g_param_spec_double ("desired-fixed-width",
                                                        NULL, NULL,
                                                        0.0, GIMP_MAX_IMAGE_SIZE,
                                                        100.0,
                                                        GIMP_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_DESIRED_FIXED_HEIGHT,
                                   g_param_spec_double ("desired-fixed-height",
                                                        NULL, NULL,
                                                        0.0, GIMP_MAX_IMAGE_SIZE,
                                                        100.0,
                                                        GIMP_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_DESIRED_FIXED_SIZE_WIDTH,
                                   g_param_spec_double ("desired-fixed-size-width",
                                                        NULL, NULL,
                                                        0.0, GIMP_MAX_IMAGE_SIZE,
                                                        100.0,
                                                        GIMP_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_DESIRED_FIXED_SIZE_HEIGHT,
                                   g_param_spec_double ("desired-fixed-size-height",
                                                        NULL, NULL,
                                                        0.0, GIMP_MAX_IMAGE_SIZE,
                                                        100.0,
                                                        GIMP_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_ASPECT_NUMERATOR,
                                   g_param_spec_double ("aspect-numerator",
                                                        NULL, NULL,
                                                        0.0, GIMP_MAX_IMAGE_SIZE,
                                                        1.0,
                                                        GIMP_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_ASPECT_DENOMINATOR,
                                   g_param_spec_double ("aspect-denominator",
                                                        NULL, NULL,
                                                        0.0, GIMP_MAX_IMAGE_SIZE,
                                                        1.0,
                                                        GIMP_PARAM_READWRITE |
                                                        G_PARAM_CONSTRUCT));

  g_object_class_install_property (object_class, PROP_FIXED_CENTER,
                                   g_param_spec_boolean ("fixed-center",
                                                         NULL, NULL,
                                                         FALSE,
                                                         GIMP_PARAM_READWRITE |
                                                         G_PARAM_CONSTRUCT));
}

static void
gimp_tool_rectangle_init (GimpToolRectangle *rectangle)
{
  rectangle->private = gimp_tool_rectangle_get_instance_private (rectangle);

  rectangle->private->function                = GIMP_TOOL_RECTANGLE_CREATING;
  rectangle->private->is_first                = TRUE;
  rectangle->private->modifier_toggle_allowed = FALSE;
}

static void
gimp_tool_rectangle_constructed (GObject *object)
{
  GimpToolRectangle        *rectangle = GIMP_TOOL_RECTANGLE (object);
  GimpToolWidget           *widget    = GIMP_TOOL_WIDGET (object);
  GimpToolRectanglePrivate *private   = rectangle->private;
  GimpCanvasGroup          *stroke_group;
  gint                      i;

  G_OBJECT_CLASS (parent_class)->constructed (object);

  stroke_group = gimp_tool_widget_add_stroke_group (widget);

  gimp_tool_widget_push_group (widget, stroke_group);

  private->guides = gimp_tool_widget_add_rectangle_guides (widget,
                                                           0, 0, 10, 10,
                                                           GIMP_GUIDES_NONE);

  private->rectangle = gimp_tool_widget_add_rectangle (widget,
                                                       0, 0, 10, 10,
                                                       FALSE);

  private->ellipse = gimp_tool_widget_add_arc (widget,
                                               0, 0, 10, 10,
                                               0.0, 2 * G_PI,
                                               FALSE);

  for (i = 0; i < 4; i++)
    private->corners[i] = gimp_tool_widget_add_arc (widget,
                                                    0, 0, 10, 10,
                                                    0.0, 2 * G_PI,
                                                    FALSE);

  gimp_tool_widget_pop_group (widget);

  private->center = gimp_tool_widget_add_handle (widget,
                                                 GIMP_HANDLE_CROSS,
                                                 0, 0,
                                                 GIMP_CANVAS_HANDLE_SIZE_SMALL,
                                                 GIMP_CANVAS_HANDLE_SIZE_SMALL,
                                                 GIMP_HANDLE_ANCHOR_CENTER);

  gimp_tool_widget_push_group (widget, stroke_group);

  private->creating_corners[0] =
    gimp_tool_widget_add_corner (widget,
                                 0, 0, 10, 10,
                                 GIMP_HANDLE_ANCHOR_NORTH_WEST,
                                 10, 10,
                                 FALSE);

  private->creating_corners[1] =
    gimp_tool_widget_add_corner (widget,
                                 0, 0, 10, 10,
                                 GIMP_HANDLE_ANCHOR_NORTH_EAST,
                                 10, 10,
                                 FALSE);

  private->creating_corners[2] =
    gimp_tool_widget_add_corner (widget,
                                 0, 0, 10, 10,
                                 GIMP_HANDLE_ANCHOR_SOUTH_WEST,
                                 10, 10,
                                 FALSE);

  private->creating_corners[3] =
    gimp_tool_widget_add_corner (widget,
                                 0, 0, 10, 10,
                                 GIMP_HANDLE_ANCHOR_SOUTH_EAST,
                                 10, 10,
                                 FALSE);

  gimp_tool_widget_pop_group (widget);

  for (i = GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT;
       i <= GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM;
       i++)
    {
      GimpHandleAnchor anchor;

      anchor = gimp_tool_rectangle_get_anchor (i);

      gimp_tool_widget_push_group (widget, stroke_group);

      private->handles[i] = gimp_tool_widget_add_corner (widget,
                                                         0, 0, 10, 10,
                                                         anchor,
                                                         10, 10,
                                                         FALSE);

      gimp_tool_widget_pop_group (widget);

      private->highlight_handles[i] = gimp_tool_widget_add_corner (widget,
                                                                   0, 0, 10, 10,
                                                                   anchor,
                                                                   10, 10,
                                                                   FALSE);
      gimp_canvas_item_set_highlight (private->highlight_handles[i], TRUE);
    }

  gimp_tool_rectangle_changed (widget);
}

static void
gimp_tool_rectangle_finalize (GObject *object)
{
  GimpToolRectangle        *rectangle = GIMP_TOOL_RECTANGLE (object);
  GimpToolRectanglePrivate *private   = rectangle->private;

  g_clear_pointer (&private->status_title, g_free);

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

static void
gimp_tool_rectangle_set_property (GObject      *object,
                                  guint         property_id,
                                  const GValue *value,
                                  GParamSpec   *pspec)
{
  GimpToolRectangle        *rectangle = GIMP_TOOL_RECTANGLE (object);
  GimpToolRectanglePrivate *private   = rectangle->private;

  switch (property_id)
    {
    case PROP_X1:
      private->x1 = g_value_get_double (value);
      break;
    case PROP_Y1:
      private->y1 = g_value_get_double (value);
      break;
    case PROP_X2:
      private->x2 = g_value_get_double (value);
      break;
    case PROP_Y2:
      private->y2 = g_value_get_double (value);
      break;

    case PROP_CONSTRAINT:
      private->constraint = g_value_get_enum (value);
      break;
    case PROP_PRECISION:
      private->precision = g_value_get_enum (value);
      break;

    case PROP_NARROW_MODE:
      private->narrow_mode = g_value_get_boolean (value);
      break;
    case PROP_FORCE_NARROW_MODE:
      private->force_narrow_mode = g_value_get_boolean (value);
      break;
    case PROP_DRAW_ELLIPSE:
      private->draw_ellipse = g_value_get_boolean (value);
      break;
    case PROP_ROUND_CORNERS:
      private->round_corners = g_value_get_boolean (value);
      break;
    case PROP_CORNER_RADIUS:
      private->corner_radius = g_value_get_double (value);
      break;

    case PROP_STATUS_TITLE:
      g_free (private->status_title);
      private->status_title = g_value_dup_string (value);
      if (! private->status_title)
        private->status_title = g_strdup (_("Rectangle: "));
      break;

    case PROP_HIGHLIGHT:
      private->highlight = g_value_get_boolean (value);
      break;
    case PROP_HIGHLIGHT_OPACITY:
      private->highlight_opacity = g_value_get_double (value);
      break;
    case PROP_GUIDE:
      private->guide = g_value_get_enum (value);
      break;

    case PROP_X:
      private->x = g_value_get_double (value);
      break;
    case PROP_Y:
      private->y = g_value_get_double (value);
      break;
    case PROP_WIDTH:
      private->width = g_value_get_double (value);
      break;
    case PROP_HEIGHT:
      private->height = g_value_get_double (value);
      break;

    case PROP_FIXED_RULE_ACTIVE:
      private->fixed_rule_active = g_value_get_boolean (value);
      break;
    case PROP_FIXED_RULE:
      private->fixed_rule = g_value_get_enum (value);
      break;
    case PROP_DESIRED_FIXED_WIDTH:
      private->desired_fixed_width = g_value_get_double (value);
      break;
    case PROP_DESIRED_FIXED_HEIGHT:
      private->desired_fixed_height = g_value_get_double (value);
      break;
    case PROP_DESIRED_FIXED_SIZE_WIDTH:
      private->desired_fixed_size_width = g_value_get_double (value);
      break;
    case PROP_DESIRED_FIXED_SIZE_HEIGHT:
      private->desired_fixed_size_height = g_value_get_double (value);
      break;
    case PROP_ASPECT_NUMERATOR:
      private->aspect_numerator = g_value_get_double (value);
      break;
    case PROP_ASPECT_DENOMINATOR:
      private->aspect_denominator = g_value_get_double (value);
      break;

    case PROP_FIXED_CENTER:
      private->fixed_center = g_value_get_boolean (value);
      break;

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

static void
gimp_tool_rectangle_get_property (GObject    *object,
                                  guint       property_id,
                                  GValue     *value,
                                  GParamSpec *pspec)
{
  GimpToolRectangle        *rectangle = GIMP_TOOL_RECTANGLE (object);
  GimpToolRectanglePrivate *private   = rectangle->private;

  switch (property_id)
    {
    case PROP_X1:
      g_value_set_double (value, private->x1);
      break;
    case PROP_Y1:
      g_value_set_double (value, private->y1);
      break;
    case PROP_X2:
      g_value_set_double (value, private->x2);
      break;
    case PROP_Y2:
      g_value_set_double (value, private->y2);
      break;

    case PROP_CONSTRAINT:
      g_value_set_enum (value, private->constraint);
      break;
    case PROP_PRECISION:
      g_value_set_enum (value, private->precision);
      break;

    case PROP_NARROW_MODE:
      g_value_set_boolean (value, private->narrow_mode);
      break;
    case PROP_FORCE_NARROW_MODE:
      g_value_set_boolean (value, private->force_narrow_mode);
      break;
    case PROP_DRAW_ELLIPSE:
      g_value_set_boolean (value, private->draw_ellipse);
      break;
    case PROP_ROUND_CORNERS:
      g_value_set_boolean (value, private->round_corners);
      break;
    case PROP_CORNER_RADIUS:
      g_value_set_double (value, private->corner_radius);
      break;

    case PROP_STATUS_TITLE:
      g_value_set_string (value, private->status_title);
      break;

    case PROP_HIGHLIGHT:
      g_value_set_boolean (value, private->highlight);
      break;
    case PROP_HIGHLIGHT_OPACITY:
      g_value_set_double (value, private->highlight_opacity);
      break;
    case PROP_GUIDE:
      g_value_set_enum (value, private->guide);
      break;

    case PROP_X:
      g_value_set_double (value, private->x);
      break;
    case PROP_Y:
      g_value_set_double (value, private->y);
      break;
    case PROP_WIDTH:
      g_value_set_double (value, private->width);
      break;
    case PROP_HEIGHT:
      g_value_set_double (value, private->height);
      break;

    case PROP_FIXED_RULE_ACTIVE:
      g_value_set_boolean (value, private->fixed_rule_active);
      break;
    case PROP_FIXED_RULE:
      g_value_set_enum (value, private->fixed_rule);
      break;
    case PROP_DESIRED_FIXED_WIDTH:
      g_value_set_double (value, private->desired_fixed_width);
      break;
    case PROP_DESIRED_FIXED_HEIGHT:
      g_value_set_double (value, private->desired_fixed_height);
      break;
    case PROP_DESIRED_FIXED_SIZE_WIDTH:
      g_value_set_double (value, private->desired_fixed_size_width);
      break;
    case PROP_DESIRED_FIXED_SIZE_HEIGHT:
      g_value_set_double (value, private->desired_fixed_size_height);
      break;
    case PROP_ASPECT_NUMERATOR:
      g_value_set_double (value, private->aspect_numerator);
      break;
    case PROP_ASPECT_DENOMINATOR:
      g_value_set_double (value, private->aspect_denominator);
      break;

    case PROP_FIXED_CENTER:
      g_value_set_boolean (value, private->fixed_center);
      break;

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

static void
gimp_tool_rectangle_notify (GObject    *object,
                            GParamSpec *pspec)
{
  GimpToolRectangle        *rectangle = GIMP_TOOL_RECTANGLE (object);
  GimpToolRectanglePrivate *private   = rectangle->private;

  if (G_OBJECT_CLASS (parent_class)->notify)
    G_OBJECT_CLASS (parent_class)->notify (object, pspec);

  if (! strcmp (pspec->name, "x1") ||
      ! strcmp (pspec->name, "y1") ||
      ! strcmp (pspec->name, "x2") ||
      ! strcmp (pspec->name, "y2"))
    {
      gimp_tool_rectangle_update_int_rect (rectangle);

      gimp_tool_rectangle_recalculate_center_xy (rectangle);

      gimp_tool_rectangle_update_options (rectangle);
    }
  else if (! strcmp  (pspec->name, "x") &&
           ! PIXEL_FEQUAL (private->x1, private->x))
    {
      gimp_tool_rectangle_synthesize_motion (rectangle,
                                             GIMP_TOOL_RECTANGLE_MOVING,
                                             private->x,
                                             private->y1);
    }
  else if (! strcmp  (pspec->name, "y") &&
           ! PIXEL_FEQUAL (private->y1, private->y))
    {
      gimp_tool_rectangle_synthesize_motion (rectangle,
                                             GIMP_TOOL_RECTANGLE_MOVING,
                                             private->x1,
                                             private->y);
    }
  else if (! strcmp  (pspec->name, "width") &&
           ! PIXEL_FEQUAL (private->x2 - private->x1, private->width))
    {
      /* Calculate x2, y2 that will create a rectangle of given width,
       * for the current options.
       */
      gdouble x2;

      if (private->fixed_center)
        {
          x2 = private->center_x_on_fixed_center +
               private->width / 2;
        }
      else
        {
          x2 = private->x1 + private->width;
        }

      gimp_tool_rectangle_synthesize_motion (rectangle,
                                             GIMP_TOOL_RECTANGLE_RESIZING_RIGHT,
                                             x2,
                                             private->y2);
    }
  else if (! strcmp  (pspec->name, "height") &&
           ! PIXEL_FEQUAL (private->y2 - private->y1, private->height))
    {
      /* Calculate x2, y2 that will create a rectangle of given
       * height, for the current options.
       */
      gdouble y2;

      if (private->fixed_center)
        {
          y2 = private->center_y_on_fixed_center +
               private->height / 2;
        }
      else
        {
          y2 = private->y1 + private->height;
        }

      gimp_tool_rectangle_synthesize_motion (rectangle,
                                             GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM,
                                             private->x2,
                                             y2);
    }
  else if (! strcmp (pspec->name, "desired-fixed-size-width"))
    {
      /* We are only interested in when width and height swaps, so
       * it's enough to only check e.g. for width.
       */

      gdouble width  = private->x2 - private->x1;
      gdouble height = private->y2 - private->y1;

      /* Depending on a bunch of conditions, we might want to
       * immedieately switch width and height of the pending
       * rectangle.
       */
      if (private->fixed_rule_active                          &&
#if 0
          tool->button_press_state                            == 0    &&
          tool->active_modifier_state                         == 0    &&
#endif
          FEQUAL (private->desired_fixed_size_width,  height) &&
          FEQUAL (private->desired_fixed_size_height, width))
        {
          gdouble x = private->x1;
          gdouble y = private->y1;

          gimp_tool_rectangle_synthesize_motion (rectangle,
                                                 GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT,
                                                 private->x2,
                                                 private->y2);

          /* For some reason these needs to be set separately... */
          g_object_set (rectangle,
                        "x", x,
                        NULL);
          g_object_set (rectangle,
                        "y", y,
                        NULL);
        }
    }
  else if (! strcmp (pspec->name, "aspect-numerator"))
    {
      /* We are only interested in when numerator and denominator
       * swaps, so it's enough to only check e.g. for numerator.
       */

      double    width             = private->x2 - private->x1;
      double    height            = private->y2 - private->y1;
      gdouble   new_inverse_ratio = private->aspect_denominator /
                                    private->aspect_numerator;
      gdouble   lower_ratio;
      gdouble   higher_ratio;

      /* The ratio of the Fixed: Aspect ratio rule and the pending
       * rectangle is very rarely exactly the same so use an
       * interval. For small rectangles the below code will
       * automatically yield a more generous accepted ratio interval
       * which is exactly what we want.
       */
      if (width > height && height > 1.0)
        {
          lower_ratio  = width / (height + 1.0);
          higher_ratio = width / (height - 1.0);
        }
      else
        {
          lower_ratio  = (width - 1.0) / height;
          higher_ratio = (width + 1.0) / height;
        }

      /* Depending on a bunch of conditions, we might want to
       * immedieately switch width and height of the pending
       * rectangle.
       */
      if (private->fixed_rule_active               &&
#if 0
          tool->button_press_state    == 0                 &&
          tool->active_modifier_state == 0                 &&
#endif
          lower_ratio                 <  new_inverse_ratio &&
          higher_ratio                >  new_inverse_ratio)
        {
          gdouble new_x2 = private->x1 + private->y2 - private->y1;
          gdouble new_y2 = private->y1 + private->x2 - private->x1;

          gimp_tool_rectangle_synthesize_motion (rectangle,
                                                 GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT,
                                                 new_x2,
                                                 new_y2);
        }
    }
}

static void
gimp_tool_rectangle_changed (GimpToolWidget *widget)
{
  GimpToolRectangle        *rectangle = GIMP_TOOL_RECTANGLE (widget);
  GimpToolRectanglePrivate *private   = rectangle->private;
  GimpDisplayShell         *shell     = gimp_tool_widget_get_shell (widget);
  gdouble                   x1, y1, x2, y2;
  gint                      handle_width;
  gint                      handle_height;
  gint                      i;

  gimp_tool_rectangle_update_handle_sizes (rectangle);

  gimp_tool_rectangle_get_public_rect (rectangle, &x1, &y1, &x2, &y2);

  gimp_canvas_rectangle_guides_set (private->guides,
                                    x1, y1,
                                    x2 - x1,
                                    y2 - y1,
                                    private->guide, 4);

  gimp_canvas_rectangle_set (private->rectangle,
                             x1, y1,
                             x2 - x1,
                             y2 - y1);

  if (private->draw_ellipse)
    {
      gimp_canvas_arc_set (private->ellipse,
                           (x1 + x2) / 2.0,
                           (y1 + y2) / 2.0,
                           (x2 - x1) / 2.0,
                           (y2 - y1) / 2.0,
                           0.0, 2 * G_PI);
      gimp_canvas_item_set_visible (private->ellipse, TRUE);
    }
  else
    {
      gimp_canvas_item_set_visible (private->ellipse, FALSE);
    }

  if (private->round_corners && private->corner_radius > 0.0)
    {
      gdouble radius;

      radius = MIN (private->corner_radius,
                    MIN ((x2 - x1) / 2.0, (y2 - y1) / 2.0));

      gimp_canvas_arc_set (private->corners[0],
                           x1 + radius,
                           y1 + radius,
                           radius, radius,
                           G_PI / 2.0, G_PI / 2.0);

      gimp_canvas_arc_set (private->corners[1],
                           x2 - radius,
                           y1 + radius,
                           radius, radius,
                           0.0, G_PI / 2.0);

      gimp_canvas_arc_set (private->corners[2],
                           x1 + radius,
                           y2 - radius,
                           radius, radius,
                           G_PI, G_PI / 2.0);

      gimp_canvas_arc_set (private->corners[3],
                           x2 - radius,
                           y2 - radius,
                           radius, radius,
                           G_PI * 1.5, G_PI / 2.0);

      for (i = 0; i < 4; i++)
        gimp_canvas_item_set_visible (private->corners[i], TRUE);
    }
  else
    {
      for (i = 0; i < 4; i++)
        gimp_canvas_item_set_visible (private->corners[i], FALSE);
    }

  gimp_canvas_item_set_visible (private->center, FALSE);

  for (i = 0; i < 4; i++)
    {
      gimp_canvas_item_set_visible (private->creating_corners[i], FALSE);
      gimp_canvas_item_set_highlight (private->creating_corners[i], FALSE);
    }

  for (i = GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT;
       i <= GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM;
       i++)
    {
      gimp_canvas_item_set_visible (private->handles[i], FALSE);
      gimp_canvas_item_set_visible (private->highlight_handles[i], FALSE);
    }

  handle_width  = private->corner_handle_w;
  handle_height = private->corner_handle_h;

  switch (private->function)
    {
    case GIMP_TOOL_RECTANGLE_MOVING:
      if (private->rect_adjusting)
        {
          /* Mark the center because we snap to it */
          gimp_canvas_handle_set_position (private->center,
                                           (x1 + x2) / 2.0,
                                           (y1 + y2) / 2.0);
          gimp_canvas_item_set_visible (private->center, TRUE);
          break;
        }

      /* else fallthrough */

    case GIMP_TOOL_RECTANGLE_DEAD:
    case GIMP_TOOL_RECTANGLE_CREATING:
    case GIMP_TOOL_RECTANGLE_AUTO_SHRINK:
      for (i = 0; i < 4; i++)
        {
          gimp_canvas_corner_set (private->creating_corners[i],
                                  x1, y1, x2 - x1, y2 - y1,
                                  handle_width, handle_height,
                                  private->narrow_mode);
          gimp_canvas_item_set_visible (private->creating_corners[i], TRUE);
        }
      break;

    case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
    case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
      handle_width = private->top_and_bottom_handle_w;
      break;

    case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
    case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
      handle_height = private->left_and_right_handle_h;
      break;

    default:
      break;
    }

  if (handle_width  >= 3                                           &&
      handle_height >= 3                                           &&
      private->function >= GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT &&
      private->function <= GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM)
    {
      GimpCanvasItem *corner;

      if (private->rect_adjusting)
        corner = private->handles[private->function];
      else
        corner = private->highlight_handles[private->function];

      gimp_canvas_corner_set (corner,
                              x1, y1, x2 - x1, y2 - y1,
                              handle_width, handle_height,
                              private->narrow_mode);
      gimp_canvas_item_set_visible (corner, TRUE);
    }

  if (private->highlight && ! private->rect_adjusting)
    {
      GdkRectangle rect;

      rect.x      = x1;
      rect.y      = y1;
      rect.width  = x2 - x1;
      rect.height = y2 - y1;

      gimp_display_shell_set_highlight (shell, &rect, private->highlight_opacity);
    }
  else
    {
      gimp_display_shell_set_highlight (shell, NULL, 0.0);
    }
}

gint
gimp_tool_rectangle_button_press (GimpToolWidget      *widget,
                                  const GimpCoords    *coords,
                                  guint32              time,
                                  GdkModifierType      state,
                                  GimpButtonPressType  press_type)
{
  GimpToolRectangle        *rectangle = GIMP_TOOL_RECTANGLE (widget);
  GimpToolRectanglePrivate *private   = rectangle->private;
  gdouble                   snapped_x, snapped_y;
  gint                      snap_x, snap_y;

  /* Prevent the latching of toggled modifiers (Ctrl and Shift) when the
     selection is cancelled whilst one or both of these modifiers are
     being pressed (see issue #7954 and MR !799) */
  private->fixed_center_copy       = private->fixed_center;
  private->fixed_rule_active_copy  = private->fixed_rule_active;
  private->modifier_toggle_allowed = TRUE;

  /* save existing shape in case of cancellation */
  private->saved_x1 = private->x1;
  private->saved_y1 = private->y1;
  private->saved_x2 = private->x2;
  private->saved_y2 = private->y2;

  gimp_tool_rectangle_setup_snap_offsets (rectangle, coords);
  gimp_tool_widget_get_snap_offsets (widget, &snap_x, &snap_y, NULL, NULL);

  snapped_x = coords->x + snap_x;
  snapped_y = coords->y + snap_y;

  private->lastx = snapped_x;
  private->lasty = snapped_y;

  if (private->function == GIMP_TOOL_RECTANGLE_CREATING)
    {
      /* Remember that this rectangle was created from scratch. */
      private->is_new = TRUE;

      private->x1 = private->x2 = snapped_x;
      private->y1 = private->y2 = snapped_y;

      /* Unless forced, created rectangles should not be started in
       * narrow-mode
       */
      if (private->force_narrow_mode)
        private->narrow_mode = TRUE;
      else
        private->narrow_mode = FALSE;

      /* If the rectangle is being modified we want the center on
       * fixed_center to be at the center of the currently existing
       * rectangle, otherwise we want the point where the user clicked
       * to be the center on fixed_center.
       */
      private->center_x_on_fixed_center = snapped_x;
      private->center_y_on_fixed_center = snapped_y;

      /* When the user toggles modifier keys, we want to keep track of
       * what coordinates the "other side" should have. If we are
       * creating a rectangle, use the current mouse coordinates as
       * the coordinate of the "other side", otherwise use the
       * immediate "other side" for that.
       */
      private->other_side_x = snapped_x;
      private->other_side_y = snapped_y;
    }
  else
    {
      /* This rectangle was not created from scratch. */
      private->is_new = FALSE;

      private->center_x_on_fixed_center = (private->x1 + private->x2) / 2;
      private->center_y_on_fixed_center = (private->y1 + private->y2) / 2;

      gimp_tool_rectangle_get_other_side_coord (rectangle,
                                                &private->other_side_x,
                                                &private->other_side_y);
    }

  gimp_tool_rectangle_update_int_rect (rectangle);

  /* Is the rectangle being rubber-banded? */
  private->rect_adjusting = gimp_tool_rectangle_rect_adjusting_func (rectangle);

  gimp_tool_rectangle_changed (widget);

  gimp_tool_rectangle_update_status (rectangle);

  return 1;
}

void
gimp_tool_rectangle_button_release (GimpToolWidget        *widget,
                                    const GimpCoords      *coords,
                                    guint32                time,
                                    GdkModifierType        state,
                                    GimpButtonReleaseType  release_type)
{
  GimpToolRectangle        *rectangle = GIMP_TOOL_RECTANGLE (widget);
  GimpToolRectanglePrivate *private   = rectangle->private;
  gint                      response  = 0;

  gimp_tool_widget_set_status (widget, NULL);

  /* On button release, we are not rubber-banding the rectangle any longer. */
  private->rect_adjusting = FALSE;

  gimp_tool_widget_set_snap_offsets (widget, 0, 0, 0, 0);

  /* Prevent the latching of toggled modifiers (Ctrl and Shift) when the
     selection is cancelled whilst one or both of these modifiers are
     being pressed (see issue #7954 and MR !799) */
  private->modifier_toggle_allowed = FALSE;
  g_object_set (rectangle,
                "fixed-center", private->fixed_center_copy,
                "fixed-rule-active", private->fixed_rule_active_copy,
                NULL);

  switch (release_type)
    {
    case GIMP_BUTTON_RELEASE_NO_MOTION:
      /* If the first created rectangle was not expanded, halt the
       * tool...
       */
      if (gimp_tool_rectangle_rectangle_is_first (rectangle))
        {
          response = GIMP_TOOL_WIDGET_RESPONSE_CANCEL;
          break;
        }

      /* ...else fallthrough and treat a long click without movement
       * like a normal change
       */

    case GIMP_BUTTON_RELEASE_NORMAL:
      /* If a normal click-drag-release actually created a rectangle
       * with content...
       */
      if (private->x1 != private->x2 &&
          private->y1 != private->y2)
        {
          gimp_tool_rectangle_change_complete (rectangle);
          break;
        }

      /* ...else fallthrough and undo the operation, we can't have
       * zero-extent rectangles
       */

    case GIMP_BUTTON_RELEASE_CANCEL:
      private->x1 = private->saved_x1;
      private->y1 = private->saved_y1;
      private->x2 = private->saved_x2;
      private->y2 = private->saved_y2;

      gimp_tool_rectangle_update_int_rect (rectangle);

      /* If the first created rectangle was canceled, halt the tool */
      if (gimp_tool_rectangle_rectangle_is_first (rectangle))
        response = GIMP_TOOL_WIDGET_RESPONSE_CANCEL;
      break;

    case GIMP_BUTTON_RELEASE_CLICK:
      /* When a dead area is clicked, don't execute. */
      if (private->function != GIMP_TOOL_RECTANGLE_DEAD)
        response = GIMP_TOOL_WIDGET_RESPONSE_CONFIRM;
      break;
    }

  /* We must update this. */
  gimp_tool_rectangle_recalculate_center_xy (rectangle);

  gimp_tool_rectangle_update_options (rectangle);

  gimp_tool_rectangle_changed (widget);

  private->is_first = FALSE;

  /*  emit response at the end, so everything is up to date even if
   *  a signal handler decides hot to shut down the rectangle
   */
  if (response != 0)
    gimp_tool_widget_response (widget, response);
}

void
gimp_tool_rectangle_motion (GimpToolWidget   *widget,
                            const GimpCoords *coords,
                            guint32           time,
                            GdkModifierType   state)
{
  GimpToolRectangle        *rectangle = GIMP_TOOL_RECTANGLE (widget);
  GimpToolRectanglePrivate *private   = rectangle->private;
  gdouble                   snapped_x;
  gdouble                   snapped_y;
  gint                      snap_x, snap_y;

  /* Motion events should be ignored when we're just waiting for the
   * button release event to execute or if the user has grabbed a dead
   * area of the rectangle.
   */
  if (private->function == GIMP_TOOL_RECTANGLE_EXECUTING ||
      private->function == GIMP_TOOL_RECTANGLE_DEAD)
    return;

  /* Handle snapping. */
  gimp_tool_widget_get_snap_offsets (widget, &snap_x, &snap_y, NULL, NULL);

  snapped_x = coords->x + snap_x;
  snapped_y = coords->y + snap_y;

  /* This is the core rectangle shape updating function: */
  gimp_tool_rectangle_update_with_coord (rectangle, snapped_x, snapped_y);

  gimp_tool_rectangle_update_status (rectangle);

  if (private->function == GIMP_TOOL_RECTANGLE_CREATING)
    {
      GimpRectangleFunction function = GIMP_TOOL_RECTANGLE_CREATING;
      gdouble               dx       = snapped_x - private->lastx;
      gdouble               dy       = snapped_y - private->lasty;

      /* When the user starts to move the cursor, set the current
       * function to one of the corner-grabbed functions, depending on
       * in what direction the user starts dragging the rectangle.
       */
      if (dx < 0)
        {
          function = (dy < 0 ?
                      GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT :
                      GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT);
        }
      else if (dx > 0)
        {
          function = (dy < 0 ?
                      GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT :
                      GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT);
        }
      else if (dy < 0)
        {
          function = (dx < 0 ?
                      GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT :
                      GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT);
        }
      else if (dy > 0)
        {
          function = (dx < 0 ?
                      GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT :
                      GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT);
        }

      gimp_tool_rectangle_set_function (rectangle, function);

      if (private->fixed_rule_active &&
          private->fixed_rule == GIMP_RECTANGLE_FIXED_SIZE)
        {
          /* For fixed size, set the function to moving immediately since the
           * rectangle can not be resized anyway.
           */

          /* We fake a coord update to get the right size. */
          gimp_tool_rectangle_update_with_coord (rectangle,
                                                 snapped_x,
                                                 snapped_y);

          gimp_tool_widget_set_snap_offsets (widget,
                                             -(private->x2 - private->x1) / 2,
                                             -(private->y2 - private->y1) / 2,
                                             private->x2 - private->x1,
                                             private->y2 - private->y1);

          gimp_tool_rectangle_set_function (rectangle,
                                            GIMP_TOOL_RECTANGLE_MOVING);
        }
    }

  gimp_tool_rectangle_update_options (rectangle);

  private->lastx = snapped_x;
  private->lasty = snapped_y;
}

GimpHit
gimp_tool_rectangle_hit (GimpToolWidget   *widget,
                         const GimpCoords *coords,
                         GdkModifierType   state,
                         gboolean          proximity)
{
  GimpToolRectangle        *rectangle = GIMP_TOOL_RECTANGLE (widget);
  GimpToolRectanglePrivate *private   = rectangle->private;
  GimpRectangleFunction     function;

  if (private->suppress_updates)
    {
      function = gimp_tool_rectangle_get_function (rectangle);
    }
  else
    {
      function = gimp_tool_rectangle_calc_function (rectangle,
                                                    coords, proximity);
    }

  switch (function)
    {
    case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
    case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
    case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
    case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
    case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
    case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
    case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
    case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
      return GIMP_HIT_DIRECT;

    case GIMP_TOOL_RECTANGLE_CREATING:
    case GIMP_TOOL_RECTANGLE_MOVING:
      return GIMP_HIT_INDIRECT;

    case GIMP_TOOL_RECTANGLE_DEAD:
    case GIMP_TOOL_RECTANGLE_AUTO_SHRINK:
    case GIMP_TOOL_RECTANGLE_EXECUTING:
    default:
      return GIMP_HIT_NONE;
    }
}

void
gimp_tool_rectangle_hover (GimpToolWidget   *widget,
                           const GimpCoords *coords,
                           GdkModifierType   state,
                           gboolean          proximity)
{
  GimpToolRectangle        *rectangle = GIMP_TOOL_RECTANGLE (widget);
  GimpToolRectanglePrivate *private   = rectangle->private;
  GimpRectangleFunction     function;

  if (private->suppress_updates)
    {
      private->suppress_updates--;
      return;
    }

  function = gimp_tool_rectangle_calc_function (rectangle, coords, proximity);

  gimp_tool_rectangle_set_function (rectangle, function);
}

static void
gimp_tool_rectangle_leave_notify (GimpToolWidget *widget)
{
  GimpToolRectangle *rectangle = GIMP_TOOL_RECTANGLE (widget);

  gimp_tool_rectangle_set_function (rectangle, GIMP_TOOL_RECTANGLE_DEAD);

  GIMP_TOOL_WIDGET_CLASS (parent_class)->leave_notify (widget);
}

static gboolean
gimp_tool_rectangle_key_press (GimpToolWidget *widget,
                               GdkEventKey    *kevent)
{
  GimpToolRectangle        *rectangle = GIMP_TOOL_RECTANGLE (widget);
  GimpToolRectanglePrivate *private   = rectangle->private;
  gint                      dx        = 0;
  gint                      dy        = 0;
  gdouble                   new_x     = 0;
  gdouble                   new_y     = 0;

  switch (kevent->keyval)
    {
    case GDK_KEY_Up:
      dy = -1;
      break;
    case GDK_KEY_Left:
      dx = -1;
      break;
    case GDK_KEY_Right:
      dx = 1;
      break;
    case GDK_KEY_Down:
      dy = 1;
      break;

    default:
      return GIMP_TOOL_WIDGET_CLASS (parent_class)->key_press (widget, kevent);
    }

  /*  If the shift key is down, move by an accelerated increment  */
  if (kevent->state & gimp_get_extend_selection_mask ())
    {
      dx *= ARROW_VELOCITY;
      dy *= ARROW_VELOCITY;
    }

  gimp_tool_widget_set_snap_offsets (widget, 0, 0, 0, 0);

  /*  Resize the rectangle if the mouse is over a handle, otherwise move it  */
  switch (private->function)
    {
    case GIMP_TOOL_RECTANGLE_MOVING:
    case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
      new_x = private->x1 + dx;
      new_y = private->y1 + dy;
      private->lastx = new_x;
      private->lasty = new_y;
      break;
    case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
      new_x = private->x2 + dx;
      new_y = private->y1 + dy;
      private->lastx = new_x;
      private->lasty = new_y;
      break;
    case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
      new_x = private->x1 + dx;
      new_y = private->y2 + dy;
      private->lastx = new_x;
      private->lasty = new_y;
      break;
    case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
      new_x = private->x2 + dx;
      new_y = private->y2 + dy;
      private->lastx = new_x;
      private->lasty = new_y;
      break;
    case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
      new_x = private->x1 + dx;
      private->lastx = new_x;
      break;
    case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
      new_x = private->x2 + dx;
      private->lastx = new_x;
      break;
    case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
      new_y = private->y1 + dy;
      private->lasty = new_y;
      break;
    case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
      new_y = private->y2 + dy;
      private->lasty = new_y;
      break;

    default:
      return TRUE;
    }

  gimp_tool_rectangle_update_with_coord (rectangle, new_x, new_y);

  gimp_tool_rectangle_recalculate_center_xy (rectangle);

  gimp_tool_rectangle_update_options (rectangle);

  gimp_tool_rectangle_change_complete (rectangle);

  /*  Evil hack to suppress oper updates. We do this because we don't
   *  want the rectangle tool to change function while the rectangle
   *  is being resized or moved using the keyboard.
   */
  private->suppress_updates = 2;

  return TRUE;
}

static void
gimp_tool_rectangle_motion_modifier (GimpToolWidget  *widget,
                                     GdkModifierType  key,
                                     gboolean         press,
                                     GdkModifierType  state)
{
  GimpToolRectangle        *rectangle = GIMP_TOOL_RECTANGLE (widget);
  GimpToolRectanglePrivate *private   = rectangle->private;
  gboolean                  button1_down;

  button1_down = (state & GDK_BUTTON1_MASK);

  if (key == gimp_get_extend_selection_mask ())
    {
#if 0
      /* Here we want to handle manually when to update the rectangle, so we
       * don't want gimp_tool_rectangle_options_notify to do anything.
       */
      g_signal_handlers_block_by_func (options,
                                       gimp_tool_rectangle_options_notify,
                                       rectangle);
#endif
      if (private->modifier_toggle_allowed)
        g_object_set (rectangle,
                      "fixed-rule-active", ! private->fixed_rule_active,
                      NULL);

#if 0
      g_signal_handlers_unblock_by_func (options,
                                         gimp_tool_rectangle_options_notify,
                                         rectangle);
#endif

      /* Only change the shape if the mouse is still down (i.e. the user is
       * still editing the rectangle.
       */
      if (button1_down)
        {
          if (! private->fixed_rule_active)
            {
              /* Reset anchor point */
              gimp_tool_rectangle_set_other_side_coord (rectangle,
                                                        private->other_side_x,
                                                        private->other_side_y);
            }

          gimp_tool_rectangle_update_with_coord (rectangle,
                                                 private->lastx,
                                                 private->lasty);
        }
    }

  if (key == gimp_get_toggle_behavior_mask ())
    {
      if (private->modifier_toggle_allowed)
        g_object_set (rectangle,
                      "fixed-center", ! private->fixed_center,
                      NULL);

      if (private->fixed_center)
        {
          gimp_tool_rectangle_update_with_coord (rectangle,
                                                 private->lastx,
                                                 private->lasty);

          /* Only emit the rectangle-changed signal if the button is
           * not down. If it is down, the signal will and shall be
           * emitted on _button_release instead.
           */
          if (! button1_down)
            {
              gimp_tool_rectangle_change_complete (rectangle);
            }
        }
      else if (button1_down)
        {
          /* If we are leaving fixed_center mode we want to set the
           * "other side" where it should be. Don't do anything if we
           * came here by a mouse-click though, since then the user
           * has confirmed the shape and we don't want to modify it
           * afterwards.
           */
          gimp_tool_rectangle_set_other_side_coord (rectangle,
                                                    private->other_side_x,
                                                    private->other_side_y);
        }
    }

  gimp_tool_rectangle_update_options (rectangle);
}

static gboolean
gimp_tool_rectangle_get_cursor (GimpToolWidget     *widget,
                                const GimpCoords   *coords,
                                GdkModifierType     state,
                                GimpCursorType     *cursor,
                                GimpToolCursorType *tool_cursor,
                                GimpCursorModifier *modifier)
{
  GimpToolRectangle        *rectangle = GIMP_TOOL_RECTANGLE (widget);
  GimpToolRectanglePrivate *private   = rectangle->private;

  switch (private->function)
    {
    case GIMP_TOOL_RECTANGLE_CREATING:
      *cursor = GIMP_CURSOR_CROSSHAIR_SMALL;
      break;
    case GIMP_TOOL_RECTANGLE_MOVING:
      *cursor   = GIMP_CURSOR_MOVE;
      *modifier = GIMP_CURSOR_MODIFIER_MOVE;
      break;
    case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
      *cursor = GIMP_CURSOR_CORNER_TOP_LEFT;
      break;
    case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
      *cursor = GIMP_CURSOR_CORNER_TOP_RIGHT;
      break;
    case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
      *cursor = GIMP_CURSOR_CORNER_BOTTOM_LEFT;
      break;
    case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
      *cursor = GIMP_CURSOR_CORNER_BOTTOM_RIGHT;
      break;
    case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
      *cursor = GIMP_CURSOR_SIDE_LEFT;
      break;
    case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
      *cursor = GIMP_CURSOR_SIDE_RIGHT;
      break;
    case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
      *cursor = GIMP_CURSOR_SIDE_TOP;
      break;
    case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
      *cursor = GIMP_CURSOR_SIDE_BOTTOM;
      break;

    default:
      return FALSE;
    }

  return TRUE;
}

static void
gimp_tool_rectangle_change_complete (GimpToolRectangle *rectangle)
{
  g_signal_emit (rectangle, rectangle_signals[CHANGE_COMPLETE], 0);
}

static void
gimp_tool_rectangle_update_options (GimpToolRectangle *rectangle)
{
  GimpToolRectanglePrivate *private = rectangle->private;
  gdouble                   x1, y1;
  gdouble                   x2, y2;

  gimp_tool_rectangle_get_public_rect (rectangle, &x1, &y1, &x2, &y2);

#if 0
  g_signal_handlers_block_by_func (options,
                                   gimp_tool_rectangle_options_notify,
                                   rect_tool);
#endif

  g_object_freeze_notify (G_OBJECT (rectangle));

  if (! FEQUAL (private->x, x1))
    g_object_set (rectangle, "x", x1, NULL);

  if (! FEQUAL (private->y, y1))
    g_object_set (rectangle, "y", y1, NULL);

  if (! FEQUAL (private->width, x2 - x1))
    g_object_set (rectangle, "width", x2 - x1, NULL);

  if (! FEQUAL (private->height, y2 - y1))
    g_object_set (rectangle, "height", y2 - y1, NULL);

  g_object_thaw_notify (G_OBJECT (rectangle));

#if 0
  g_signal_handlers_unblock_by_func (options,
                                     gimp_tool_rectangle_options_notify,
                                     rect_tool);
#endif
}

static void
gimp_tool_rectangle_update_handle_sizes (GimpToolRectangle *rectangle)
{
  GimpToolRectanglePrivate *private = rectangle->private;
  GimpDisplayShell         *shell;
  gint                      visible_rectangle_width;
  gint                      visible_rectangle_height;
  gint                      rectangle_width;
  gint                      rectangle_height;
  gdouble                   pub_x1, pub_y1;
  gdouble                   pub_x2, pub_y2;

  shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (rectangle));

  gimp_tool_rectangle_get_public_rect (rectangle,
                                       &pub_x1, &pub_y1, &pub_x2, &pub_y2);
  {
    /* Calculate rectangles of the selection rectangle and the display
     * shell, with origin at (0, 0) of image, and in screen coordinate
     * scale.
     */
    gint x1 =  pub_x1 * shell->scale_x;
    gint y1 =  pub_y1 * shell->scale_y;
    gint w1 = (pub_x2 - pub_x1) * shell->scale_x;
    gint h1 = (pub_y2 - pub_y1) * shell->scale_y;

    gint x2, y2, w2, h2;

    gimp_display_shell_scroll_get_scaled_viewport (shell, &x2, &y2, &w2, &h2);

    rectangle_width  = w1;
    rectangle_height = h1;

    /* Handle size calculations shall be based on the visible part of
     * the rectangle, so calculate the size for the visible rectangle
     * by intersecting with the viewport rectangle.
     */
    gimp_rectangle_intersect (x1, y1,
                              w1, h1,
                              x2, y2,
                              w2, h2,
                              NULL, NULL,
                              &visible_rectangle_width,
                              &visible_rectangle_height);

    /* Determine if we are in narrow-mode or not. */
    if (private->force_narrow_mode)
      private->narrow_mode = TRUE;
    else
      private->narrow_mode = (visible_rectangle_width  < NARROW_MODE_THRESHOLD ||
                              visible_rectangle_height < NARROW_MODE_THRESHOLD);
  }

  if (private->narrow_mode)
    {
      /* Corner handles always have the same (on-screen) size in
       * narrow-mode.
       */
      private->corner_handle_w = NARROW_MODE_HANDLE_SIZE;
      private->corner_handle_h = NARROW_MODE_HANDLE_SIZE;

      private->top_and_bottom_handle_w = CLAMP (rectangle_width,
                                                MIN (rectangle_width - 2,
                                                     NARROW_MODE_HANDLE_SIZE),
                                                G_MAXINT);
      private->left_and_right_handle_h = CLAMP (rectangle_height,
                                                MIN (rectangle_height - 2,
                                                     NARROW_MODE_HANDLE_SIZE),
                                                G_MAXINT);
    }
  else
    {
      /* Calculate and clamp corner handle size. */

      private->corner_handle_w = visible_rectangle_width  / 4;
      private->corner_handle_h = visible_rectangle_height / 4;

      private->corner_handle_w = CLAMP (private->corner_handle_w,
                                        MIN_HANDLE_SIZE,
                                        MAX_HANDLE_SIZE);
      private->corner_handle_h = CLAMP (private->corner_handle_h,
                                        MIN_HANDLE_SIZE,
                                        MAX_HANDLE_SIZE);

      /* Calculate and clamp side handle size. */

      private->top_and_bottom_handle_w = rectangle_width  - 3 * private->corner_handle_w;
      private->left_and_right_handle_h = rectangle_height - 3 * private->corner_handle_h;

      private->top_and_bottom_handle_w = CLAMP (private->top_and_bottom_handle_w,
                                                MIN_HANDLE_SIZE,
                                                G_MAXINT);
      private->left_and_right_handle_h = CLAMP (private->left_and_right_handle_h,
                                                MIN_HANDLE_SIZE,
                                                G_MAXINT);
    }
}

static void
gimp_tool_rectangle_update_status (GimpToolRectangle *rectangle)
{
  GimpToolRectanglePrivate *private = rectangle->private;
  gdouble                   x1, y1, x2, y2;

  gimp_tool_rectangle_get_public_rect (rectangle, &x1, &y1, &x2, &y2);

  if (private->function == GIMP_TOOL_RECTANGLE_MOVING)
    {
      gimp_tool_widget_set_status_coords (GIMP_TOOL_WIDGET (rectangle),
                                          _("Position: "),
                                          x1, ", ", y1,
                                          NULL);
    }
  else
    {
      gchar *aspect_text = NULL;
      gint   width       = x2 - x1;
      gint   height      = y2 - y1;

      if (width > 0.0 && height > 0.0)
        {
          aspect_text = g_strdup_printf ("  (%.2f:1)",
                                         (gdouble) width / (gdouble) height);
        }

      gimp_tool_widget_set_status_coords (GIMP_TOOL_WIDGET (rectangle),
                                          private->status_title,
                                          width, " × ", height,
                                          aspect_text);
      g_free (aspect_text);
    }
}

static void
gimp_tool_rectangle_synthesize_motion (GimpToolRectangle *rectangle,
                                       gint               function,
                                       gdouble            new_x,
                                       gdouble            new_y)
{
  GimpToolRectanglePrivate *private = rectangle->private;
  GimpRectangleFunction     old_function;

  /* We don't want to synthesize motions if the tool control is active
   * since that means the mouse button is down and the rectangle will
   * get updated in _motion anyway. The reason we want to prevent this
   * function from executing is that is emits the
   * rectangle-changed-complete signal which we don't want in the
   * middle of a rectangle change.
   *
   * In addition to that, we don't want to synthesize a motion if
   * there is no pending rectangle because that doesn't make any
   * sense.
   */
  if (private->rect_adjusting)
    return;

  old_function = private->function;

  gimp_tool_rectangle_set_function (rectangle, function);

  gimp_tool_rectangle_update_with_coord (rectangle, new_x, new_y);

  /* We must update this. */
  gimp_tool_rectangle_recalculate_center_xy (rectangle);

  gimp_tool_rectangle_update_options (rectangle);

  gimp_tool_rectangle_set_function (rectangle, old_function);

  gimp_tool_rectangle_change_complete (rectangle);
}

static void
swap_doubles (gdouble *i,
              gdouble *j)
{
  gdouble tmp;

  tmp = *i;
  *i = *j;
  *j = tmp;
}

static GimpRectangleFunction
gimp_tool_rectangle_calc_function (GimpToolRectangle *rectangle,
                                   const GimpCoords  *coords,
                                   gboolean           proximity)
{
  if (! proximity)
    {
      return GIMP_TOOL_RECTANGLE_DEAD;
    }
  else if (gimp_tool_rectangle_coord_outside (rectangle, coords))
    {
      /* The cursor is outside of the rectangle, clicking should
       * create a new rectangle.
       */
      return GIMP_TOOL_RECTANGLE_CREATING;
    }
  else if (gimp_tool_rectangle_coord_on_handle (rectangle,
                                                coords,
                                                GIMP_HANDLE_ANCHOR_NORTH_WEST))
    {
      return GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT;
    }
  else if (gimp_tool_rectangle_coord_on_handle (rectangle,
                                                coords,
                                                GIMP_HANDLE_ANCHOR_SOUTH_EAST))
    {
      return GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT;
    }
  else if  (gimp_tool_rectangle_coord_on_handle (rectangle,
                                                 coords,
                                                 GIMP_HANDLE_ANCHOR_NORTH_EAST))
    {
      return GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT;
    }
  else if (gimp_tool_rectangle_coord_on_handle (rectangle,
                                                coords,
                                                GIMP_HANDLE_ANCHOR_SOUTH_WEST))
    {
      return GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT;
    }
  else if (gimp_tool_rectangle_coord_on_handle (rectangle,
                                                coords,
                                                GIMP_HANDLE_ANCHOR_WEST))
    {
      return GIMP_TOOL_RECTANGLE_RESIZING_LEFT;
    }
  else if (gimp_tool_rectangle_coord_on_handle (rectangle,
                                                coords,
                                                GIMP_HANDLE_ANCHOR_EAST))
    {
      return GIMP_TOOL_RECTANGLE_RESIZING_RIGHT;
    }
  else if (gimp_tool_rectangle_coord_on_handle (rectangle,
                                                coords,
                                                GIMP_HANDLE_ANCHOR_NORTH))
    {
      return GIMP_TOOL_RECTANGLE_RESIZING_TOP;
    }
  else if (gimp_tool_rectangle_coord_on_handle (rectangle,
                                                coords,
                                                GIMP_HANDLE_ANCHOR_SOUTH))
    {
      return GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM;
    }
  else if (gimp_tool_rectangle_coord_on_handle (rectangle,
                                                coords,
                                                GIMP_HANDLE_ANCHOR_CENTER))
    {
      return GIMP_TOOL_RECTANGLE_MOVING;
    }
  else
    {
      return GIMP_TOOL_RECTANGLE_DEAD;
    }
}

/* gimp_tool_rectangle_check_function() is needed to deal with
 * situations where the user drags a corner or edge across one of the
 * existing edges, thereby changing its function.  Ugh.
 */
static void
gimp_tool_rectangle_check_function (GimpToolRectangle *rectangle)

{
  GimpToolRectanglePrivate *private  = rectangle->private;
  GimpRectangleFunction     function = private->function;

  if (private->x2 < private->x1)
    {
      swap_doubles (&private->x1, &private->x2);

      switch (function)
        {
        case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
          function = GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT;
          break;
        case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
          function = GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT;
          break;
        case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
          function = GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT;
          break;
        case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
          function = GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT;
          break;
        case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
          function = GIMP_TOOL_RECTANGLE_RESIZING_RIGHT;
          break;
        case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
          function = GIMP_TOOL_RECTANGLE_RESIZING_LEFT;
          break;
          /* avoid annoying warnings about unhandled enums */
        default:
          break;
        }
    }

  if (private->y2 < private->y1)
    {
      swap_doubles (&private->y1, &private->y2);

      switch (function)
        {
        case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
          function = GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT;
          break;
        case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
          function = GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT;
          break;
        case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
          function = GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT;
          break;
        case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
          function = GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT;
          break;
        case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
          function = GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM;
          break;
        case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
          function = GIMP_TOOL_RECTANGLE_RESIZING_TOP;
          break;
        default:
          break;
        }
    }

  gimp_tool_rectangle_set_function (rectangle, function);
}

/**
 * gimp_tool_rectangle_coord_outside:
 *
 * Returns: %TRUE if the coord is outside the rectangle bounds
 *          including any outside handles.
 */
static gboolean
gimp_tool_rectangle_coord_outside (GimpToolRectangle *rectangle,
                                   const GimpCoords  *coord)
{
  GimpToolRectanglePrivate *private = rectangle->private;
  GimpDisplayShell         *shell;
  gboolean                  narrow_mode = private->narrow_mode;
  gdouble                   x1, y1, x2, y2;
  gdouble                   x1_b, y1_b, x2_b, y2_b;

  shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (rectangle));

  gimp_tool_rectangle_get_public_rect (rectangle, &x1, &y1, &x2, &y2);

  x1_b = x1 - (narrow_mode ? private->corner_handle_w / shell->scale_x : 0);
  x2_b = x2 + (narrow_mode ? private->corner_handle_w / shell->scale_x : 0);
  y1_b = y1 - (narrow_mode ? private->corner_handle_h / shell->scale_y : 0);
  y2_b = y2 + (narrow_mode ? private->corner_handle_h / shell->scale_y : 0);

  return (coord->x < x1_b ||
          coord->x > x2_b ||
          coord->y < y1_b ||
          coord->y > y2_b);
}

/**
 * gimp_tool_rectangle_coord_on_handle:
 *
 * Returns: %TRUE if the coord is on the handle that corresponds to
 *          @anchor.
 */
static gboolean
gimp_tool_rectangle_coord_on_handle (GimpToolRectangle *rectangle,
                                     const GimpCoords  *coords,
                                     GimpHandleAnchor   anchor)
{
  GimpToolRectanglePrivate *private = rectangle->private;
  GimpDisplayShell         *shell;
  gdouble                   x1, y1, x2, y2;
  gdouble                   rect_w, rect_h;
  gdouble                   handle_x          = 0;
  gdouble                   handle_y          = 0;
  gdouble                   handle_width      = 0;
  gdouble                   handle_height     = 0;
  gint                      narrow_mode_x_dir = 0;
  gint                      narrow_mode_y_dir = 0;

  shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (rectangle));

  gimp_tool_rectangle_get_public_rect (rectangle, &x1, &y1, &x2, &y2);

  rect_w = x2 - x1;
  rect_h = y2 - y1;

  switch (anchor)
    {
    case GIMP_HANDLE_ANCHOR_NORTH_WEST:
      handle_x      = x1;
      handle_y      = y1;
      handle_width  = private->corner_handle_w;
      handle_height = private->corner_handle_h;

      narrow_mode_x_dir = -1;
      narrow_mode_y_dir = -1;
      break;

    case GIMP_HANDLE_ANCHOR_SOUTH_EAST:
      handle_x      = x2;
      handle_y      = y2;
      handle_width  = private->corner_handle_w;
      handle_height = private->corner_handle_h;

      narrow_mode_x_dir =  1;
      narrow_mode_y_dir =  1;
      break;

    case GIMP_HANDLE_ANCHOR_NORTH_EAST:
      handle_x      = x2;
      handle_y      = y1;
      handle_width  = private->corner_handle_w;
      handle_height = private->corner_handle_h;

      narrow_mode_x_dir =  1;
      narrow_mode_y_dir = -1;
      break;

    case GIMP_HANDLE_ANCHOR_SOUTH_WEST:
      handle_x      = x1;
      handle_y      = y2;
      handle_width  = private->corner_handle_w;
      handle_height = private->corner_handle_h;

      narrow_mode_x_dir = -1;
      narrow_mode_y_dir =  1;
      break;

    case GIMP_HANDLE_ANCHOR_WEST:
      handle_x      = x1;
      handle_y      = y1 + rect_h / 2;
      handle_width  = private->corner_handle_w;
      handle_height = private->left_and_right_handle_h;

      narrow_mode_x_dir = -1;
      narrow_mode_y_dir =  0;
      break;

    case GIMP_HANDLE_ANCHOR_EAST:
      handle_x      = x2;
      handle_y      = y1 + rect_h / 2;
      handle_width  = private->corner_handle_w;
      handle_height = private->left_and_right_handle_h;

      narrow_mode_x_dir =  1;
      narrow_mode_y_dir =  0;
      break;

    case GIMP_HANDLE_ANCHOR_NORTH:
      handle_x      = x1 + rect_w / 2;
      handle_y      = y1;
      handle_width  = private->top_and_bottom_handle_w;
      handle_height = private->corner_handle_h;

      narrow_mode_x_dir =  0;
      narrow_mode_y_dir = -1;
      break;

    case GIMP_HANDLE_ANCHOR_SOUTH:
      handle_x      = x1 + rect_w / 2;
      handle_y      = y2;
      handle_width  = private->top_and_bottom_handle_w;
      handle_height = private->corner_handle_h;

      narrow_mode_x_dir =  0;
      narrow_mode_y_dir =  1;
      break;

    case GIMP_HANDLE_ANCHOR_CENTER:
      handle_x      = x1 + rect_w / 2;
      handle_y      = y1 + rect_h / 2;

      if (private->narrow_mode)
        {
          handle_width  = rect_w * shell->scale_x;
          handle_height = rect_h * shell->scale_y;
        }
      else
        {
          handle_width  = rect_w * shell->scale_x - private->corner_handle_w * 2;
          handle_height = rect_h * shell->scale_y - private->corner_handle_h * 2;
        }

      narrow_mode_x_dir =  0;
      narrow_mode_y_dir =  0;
      break;
    }

  if (private->narrow_mode)
    {
      handle_x += narrow_mode_x_dir * handle_width  / shell->scale_x;
      handle_y += narrow_mode_y_dir * handle_height / shell->scale_y;
    }

  return gimp_canvas_item_on_handle (private->rectangle,
                                     coords->x, coords->y,
                                     GIMP_HANDLE_SQUARE,
                                     handle_x,     handle_y,
                                     handle_width, handle_height,
                                     anchor);
}

static GimpHandleAnchor
gimp_tool_rectangle_get_anchor (GimpRectangleFunction function)
{
  switch (function)
    {
    case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
      return GIMP_HANDLE_ANCHOR_NORTH_WEST;

    case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
      return GIMP_HANDLE_ANCHOR_NORTH_EAST;

    case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
      return GIMP_HANDLE_ANCHOR_SOUTH_WEST;

    case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
      return GIMP_HANDLE_ANCHOR_SOUTH_EAST;

    case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
      return GIMP_HANDLE_ANCHOR_WEST;

    case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
      return GIMP_HANDLE_ANCHOR_EAST;

    case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
      return GIMP_HANDLE_ANCHOR_NORTH;

    case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
      return GIMP_HANDLE_ANCHOR_SOUTH;

    default:
      return GIMP_HANDLE_ANCHOR_CENTER;
    }
}

static gboolean
gimp_tool_rectangle_rect_rubber_banding_func (GimpToolRectangle *rectangle)
{
  GimpToolRectanglePrivate *private = rectangle->private;

  switch (private->function)
    {
    case GIMP_TOOL_RECTANGLE_CREATING:
    case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
    case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
    case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
    case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
    case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
    case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
    case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
    case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
    case GIMP_TOOL_RECTANGLE_AUTO_SHRINK:
      return TRUE;

    case GIMP_TOOL_RECTANGLE_MOVING:
    case GIMP_TOOL_RECTANGLE_DEAD:
    default:
      break;
    }

  return FALSE;
}

/**
 * gimp_tool_rectangle_rect_adjusting_func:
 * @rectangle:
 *
 * Returns: %TRUE if the current function is a rectangle adjusting
 *          function.
 */
static gboolean
gimp_tool_rectangle_rect_adjusting_func (GimpToolRectangle *rectangle)
{
  GimpToolRectanglePrivate *private = rectangle->private;

  return (gimp_tool_rectangle_rect_rubber_banding_func (rectangle) ||
          private->function == GIMP_TOOL_RECTANGLE_MOVING);
}

/**
 * gimp_tool_rectangle_get_other_side:
 * @rectangle: A #GimpToolRectangle.
 * @other_x:   Pointer to double of the other-x double.
 * @other_y:   Pointer to double of the other-y double.
 *
 * Calculates pointers to member variables that hold the coordinates
 * of the opposite side (either the opposite corner or literally the
 * opposite side), based on the current function. The opposite of a
 * corner needs two coordinates, the opposite of a side only needs
 * one.
 */
static void
gimp_tool_rectangle_get_other_side (GimpToolRectangle  *rectangle,
                                    gdouble           **other_x,
                                    gdouble           **other_y)
{
  GimpToolRectanglePrivate *private = rectangle->private;

  switch (private->function)
    {
    case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
    case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
    case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
      *other_x = &private->x1;
      break;

    case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
    case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
    case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
      *other_x = &private->x2;
      break;

    case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
    case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
    default:
      *other_x = NULL;
      break;
    }

  switch (private->function)
    {
    case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
    case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
    case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
      *other_y = &private->y1;
      break;

    case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
    case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
    case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
      *other_y = &private->y2;
      break;

    case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
    case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
    default:
      *other_y = NULL;
      break;
    }
}

static void
gimp_tool_rectangle_get_other_side_coord (GimpToolRectangle *rectangle,
                                          gdouble           *other_side_x,
                                          gdouble           *other_side_y)
{
  gdouble *other_x = NULL;
  gdouble *other_y = NULL;

  gimp_tool_rectangle_get_other_side (rectangle, &other_x, &other_y);

  if (other_x)
    *other_side_x = *other_x;
  if (other_y)
    *other_side_y = *other_y;
}

static void
gimp_tool_rectangle_set_other_side_coord (GimpToolRectangle *rectangle,
                                          gdouble            other_side_x,
                                          gdouble            other_side_y)
{
  gdouble *other_x = NULL;
  gdouble *other_y = NULL;

  gimp_tool_rectangle_get_other_side (rectangle, &other_x, &other_y);

  if (other_x)
    *other_x = other_side_x;
  if (other_y)
    *other_y = other_side_y;

  gimp_tool_rectangle_check_function (rectangle);

  gimp_tool_rectangle_update_int_rect (rectangle);
}

/**
 * gimp_tool_rectangle_apply_coord:
 * @param:     A #GimpToolRectangle.
 * @coord_x:   X of coord.
 * @coord_y:   Y of coord.
 *
 * Adjust the rectangle to the new position specified by passed
 * coordinate, taking fixed_center into account, which means it
 * expands the rectangle around the center point.
 */
static void
gimp_tool_rectangle_apply_coord (GimpToolRectangle *rectangle,
                                 gdouble            coord_x,
                                 gdouble            coord_y)
{
  GimpToolRectanglePrivate *private = rectangle->private;

  if (private->function == GIMP_TOOL_RECTANGLE_MOVING)
    {
      /* Preserve width and height while moving the grab-point to where the
       * cursor is.
       */
      gdouble w = private->x2 - private->x1;
      gdouble h = private->y2 - private->y1;

      private->x1 = coord_x;
      private->y1 = coord_y;

      private->x2 = private->x1 + w;
      private->y2 = private->y1 + h;

      /* We are done already. */
      return;
    }

  switch (private->function)
    {
    case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
    case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
    case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
      private->x1 = coord_x;

      if (private->fixed_center)
        private->x2 = 2 * private->center_x_on_fixed_center - private->x1;

      break;

    case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
    case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
    case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
      private->x2 = coord_x;

      if (private->fixed_center)
        private->x1 = 2 * private->center_x_on_fixed_center - private->x2;

      break;

    default:
      break;
    }

  switch (private->function)
    {
    case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
    case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
    case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
      private->y1 = coord_y;

      if (private->fixed_center)
        private->y2 = 2 * private->center_y_on_fixed_center - private->y1;

      break;

    case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
    case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
    case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
      private->y2 = coord_y;

      if (private->fixed_center)
        private->y1 = 2 * private->center_y_on_fixed_center - private->y2;

      break;

    default:
      break;
    }
}

static void
gimp_tool_rectangle_setup_snap_offsets (GimpToolRectangle *rectangle,
                                        const GimpCoords  *coords)
{
  GimpToolWidget           *widget  = GIMP_TOOL_WIDGET (rectangle);
  GimpToolRectanglePrivate *private = rectangle->private;
  gdouble                   x1, y1, x2, y2;
  gdouble                   coord_x, coord_y;

  gimp_tool_rectangle_get_public_rect (rectangle, &x1, &y1, &x2, &y2);
  gimp_tool_rectangle_adjust_coord (rectangle,
                                    coords->x, coords->y,
                                    &coord_x, &coord_y);

  switch (private->function)
    {
    case GIMP_TOOL_RECTANGLE_CREATING:
      gimp_tool_widget_set_snap_offsets (widget, 0, 0, 0, 0);
      break;

    case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
      gimp_tool_widget_set_snap_offsets (widget,
                                         x1 - coord_x,
                                         y1 - coord_y,
                                         0, 0);
      break;

    case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
      gimp_tool_widget_set_snap_offsets (widget,
                                         x2 - coord_x,
                                         y1 - coord_y,
                                         0, 0);
      break;

    case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
      gimp_tool_widget_set_snap_offsets (widget,
                                         x1 - coord_x,
                                         y2 - coord_y,
                                         0, 0);
      break;

    case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
      gimp_tool_widget_set_snap_offsets (widget,
                                         x2 - coord_x,
                                         y2 - coord_y,
                                         0, 0);
      break;

    case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
      gimp_tool_widget_set_snap_offsets (widget,
                                         x1 - coord_x, 0,
                                         0, 0);
      break;

    case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
      gimp_tool_widget_set_snap_offsets (widget,
                                         x2 - coord_x, 0,
                                         0, 0);
      break;

    case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
      gimp_tool_widget_set_snap_offsets (widget,
                                         0, y1 - coord_y,
                                         0, 0);
      break;

    case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
      gimp_tool_widget_set_snap_offsets (widget,
                                         0, y2 - coord_y,
                                         0, 0);
      break;

    case GIMP_TOOL_RECTANGLE_MOVING:
      gimp_tool_widget_set_snap_offsets (widget,
                                         x1 - coord_x,
                                         y1 - coord_y,
                                         x2 - x1,
                                         y2 - y1);
      break;

    default:
      break;
    }
}

/**
 * gimp_tool_rectangle_clamp:
 * @rectangle:      A #GimpToolRectangle.
 * @clamped_sides:  Where to put contrainment information.
 * @constraint:     Constraint to use.
 * @symmetrically:  Whether or not to clamp symmetrically.
 *
 * Clamps rectangle inside specified bounds, providing information of
 * where clamping was done. Can also clamp symmetrically.
 */
static void
gimp_tool_rectangle_clamp (GimpToolRectangle       *rectangle,
                           ClampedSide             *clamped_sides,
                           GimpRectangleConstraint  constraint,
                           gboolean                 symmetrically)
{
  gimp_tool_rectangle_clamp_width (rectangle,
                                   clamped_sides,
                                   constraint,
                                   symmetrically);

  gimp_tool_rectangle_clamp_height (rectangle,
                                    clamped_sides,
                                    constraint,
                                    symmetrically);
}

/**
 * gimp_tool_rectangle_clamp_width:
 * @rectangle:      A #GimpToolRectangle.
 * @clamped_sides:  Where to put contrainment information.
 * @constraint:     Constraint to use.
 * @symmetrically:  Whether or not to clamp symmetrically.
 *
 * Clamps height of rectangle. Set symmetrically to true when using
 * for fixed_center:ed rectangles, since that will clamp symmetrically
 * which is just what is needed.
 *
 * When this function constrains, it puts what it constrains in
 * @constraint. This information is essential when an aspect ratio is
 * to be applied.
 */
static void
gimp_tool_rectangle_clamp_width (GimpToolRectangle       *rectangle,
                                 ClampedSide             *clamped_sides,
                                 GimpRectangleConstraint  constraint,
                                 gboolean                 symmetrically)
{
  GimpToolRectanglePrivate *private = rectangle->private;
  gint                      min_x;
  gint                      max_x;

  if (constraint == GIMP_RECTANGLE_CONSTRAIN_NONE)
    return;

  gimp_tool_rectangle_get_constraints (rectangle,
                                       &min_x, NULL,
                                       &max_x, NULL,
                                       constraint);
  if (private->x1 < min_x)
    {
      gdouble dx = min_x - private->x1;

      private->x1 += dx;

      if (symmetrically)
        private->x2 -= dx;

      if (private->x2 < min_x)
        private->x2 = min_x;

      if (clamped_sides)
        *clamped_sides |= CLAMPED_LEFT;
    }

  if (private->x2 > max_x)
    {
      gdouble dx = max_x - private->x2;

      private->x2 += dx;

      if (symmetrically)
        private->x1 -= dx;

      if (private->x1 > max_x)
        private->x1 = max_x;

      if (clamped_sides)
        *clamped_sides |= CLAMPED_RIGHT;
    }
}

/**
 * gimp_tool_rectangle_clamp_height:
 * @rectangle:      A #GimpToolRectangle.
 * @clamped_sides:  Where to put contrainment information.
 * @constraint:     Constraint to use.
 * @symmetrically:  Whether or not to clamp symmetrically.
 *
 * Clamps height of rectangle. Set symmetrically to true when using for
 * fixed_center:ed rectangles, since that will clamp symmetrically which is just
 * what is needed.
 *
 * When this function constrains, it puts what it constrains in
 * @constraint. This information is essential when an aspect ratio is to be
 * applied.
 */
static void
gimp_tool_rectangle_clamp_height (GimpToolRectangle       *rectangle,
                                  ClampedSide             *clamped_sides,
                                  GimpRectangleConstraint  constraint,
                                  gboolean                 symmetrically)
{
  GimpToolRectanglePrivate *private = rectangle->private;
  gint                      min_y;
  gint                      max_y;

  if (constraint == GIMP_RECTANGLE_CONSTRAIN_NONE)
    return;

  gimp_tool_rectangle_get_constraints (rectangle,
                                       NULL, &min_y,
                                       NULL, &max_y,
                                       constraint);
  if (private->y1 < min_y)
    {
      gdouble dy = min_y - private->y1;

      private->y1 += dy;

      if (symmetrically)
        private->y2 -= dy;

      if (private->y2 < min_y)
        private->y2 = min_y;

      if (clamped_sides)
        *clamped_sides |= CLAMPED_TOP;
    }

  if (private->y2 > max_y)
    {
      gdouble dy = max_y - private->y2;

      private->y2 += dy;

      if (symmetrically)
        private->y1 -= dy;

      if (private->y1 > max_y)
        private->y1 = max_y;

      if (clamped_sides)
        *clamped_sides |= CLAMPED_BOTTOM;
    }
}

/**
 * gimp_tool_rectangle_keep_inside:
 * @rectangle: A #GimpToolRectangle.
 *
 * If the rectangle is outside of the canvas, move it into it. If the rectangle is
 * larger than the canvas in any direction, make it fill the canvas in that direction.
 */
static void
gimp_tool_rectangle_keep_inside (GimpToolRectangle      *rectangle,
                                 GimpRectangleConstraint constraint)
{
  gimp_tool_rectangle_keep_inside_horizontally (rectangle, constraint);
  gimp_tool_rectangle_keep_inside_vertically   (rectangle, constraint);
}

/**
 * gimp_tool_rectangle_keep_inside_horizontally:
 * @rectangle:      A #GimpToolRectangle.
 * @constraint:     Constraint to use.
 *
 * If the rectangle is outside of the given constraint horizontally, move it
 * inside. If it is too big to fit inside, make it just as big as the width
 * limit.
 */
static void
gimp_tool_rectangle_keep_inside_horizontally (GimpToolRectangle       *rectangle,
                                              GimpRectangleConstraint  constraint)
{
  GimpToolRectanglePrivate *private = rectangle->private;
  gint                      min_x;
  gint                      max_x;

  if (constraint == GIMP_RECTANGLE_CONSTRAIN_NONE)
    return;

  gimp_tool_rectangle_get_constraints (rectangle,
                                       &min_x, NULL,
                                       &max_x, NULL,
                                       constraint);

  if (max_x - min_x < private->x2 - private->x1)
    {
      private->x1 = min_x;
      private->x2 = max_x;
    }
  else
    {
      if (private->x1 < min_x)
        {
          gdouble dx = min_x - private->x1;

          private->x1 += dx;
          private->x2 += dx;
        }
      if (private->x2 > max_x)
        {
          gdouble dx = max_x - private->x2;

          private->x1 += dx;
          private->x2 += dx;
        }
    }
}

/**
 * gimp_tool_rectangle_keep_inside_vertically:
 * @rectangle:      A #GimpToolRectangle.
 * @constraint:     Constraint to use.
 *
 * If the rectangle is outside of the given constraint vertically,
 * move it inside. If it is too big to fit inside, make it just as big
 * as the width limit.
 */
static void
gimp_tool_rectangle_keep_inside_vertically (GimpToolRectangle       *rectangle,
                                            GimpRectangleConstraint  constraint)
{
  GimpToolRectanglePrivate *private = rectangle->private;
  gint                      min_y;
  gint                      max_y;

  if (constraint == GIMP_RECTANGLE_CONSTRAIN_NONE)
    return;

  gimp_tool_rectangle_get_constraints (rectangle,
                                       NULL, &min_y,
                                       NULL, &max_y,
                                       constraint);

  if (max_y - min_y < private->y2 - private->y1)
    {
      private->y1 = min_y;
      private->y2 = max_y;
    }
  else
    {
      if (private->y1 < min_y)
        {
          gdouble dy = min_y - private->y1;

          private->y1 += dy;
          private->y2 += dy;
        }
      if (private->y2 > max_y)
        {
          gdouble dy = max_y - private->y2;

          private->y1 += dy;
          private->y2 += dy;
        }
    }
}

/**
 * gimp_tool_rectangle_apply_fixed_width:
 * @rectangle:      A #GimpToolRectangle.
 * @constraint:     Constraint to use.
 * @width:
 *
 * Makes the rectangle have a fixed_width, following the constrainment
 * rules of fixed widths as well. Please refer to the rectangle tools
 * spec.
 */
static void
gimp_tool_rectangle_apply_fixed_width (GimpToolRectangle      *rectangle,
                                       GimpRectangleConstraint constraint,
                                       gdouble                 width)
{
  GimpToolRectanglePrivate *private = rectangle->private;

  switch (private->function)
    {
    case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
    case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
    case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
      /* We always want to center around fixed_center here, since we want the
       * anchor point to be directly on the opposite side.
       */
      private->x1 = private->center_x_on_fixed_center -
                    width / 2;
      private->x2 = private->x1 + width;
      break;

    case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
    case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
    case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
      /* We always want to center around fixed_center here, since we want the
       * anchor point to be directly on the opposite side.
       */
      private->x1 = private->center_x_on_fixed_center -
                    width / 2;
      private->x2 = private->x1 + width;
      break;

    default:
      break;
    }

  /* Width shall be kept even after constraints, so we move the
   * rectangle sideways rather than adjusting a side.
   */
  gimp_tool_rectangle_keep_inside_horizontally (rectangle, constraint);
}

/**
 * gimp_tool_rectangle_apply_fixed_height:
 * @rectangle:      A #GimpToolRectangle.
 * @constraint:     Constraint to use.
 * @height:
 *
 * Makes the rectangle have a fixed_height, following the
 * constrainment rules of fixed heights as well. Please refer to the
 * rectangle tools spec.
 */
static void
gimp_tool_rectangle_apply_fixed_height (GimpToolRectangle      *rectangle,
                                        GimpRectangleConstraint constraint,
                                        gdouble                 height)

{
  GimpToolRectanglePrivate *private = rectangle->private;

  switch (private->function)
    {
    case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
    case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
    case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
      /* We always want to center around fixed_center here, since we
       * want the anchor point to be directly on the opposite side.
       */
      private->y1 = private->center_y_on_fixed_center -
                    height / 2;
      private->y2 = private->y1 + height;
      break;

    case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
    case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
    case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
      /* We always want to center around fixed_center here, since we
       * want the anchor point to be directly on the opposite side.
       */
      private->y1 = private->center_y_on_fixed_center -
                    height / 2;
      private->y2 = private->y1 + height;
      break;

    default:
      break;
    }

  /* Width shall be kept even after constraints, so we move the
   * rectangle sideways rather than adjusting a side.
   */
  gimp_tool_rectangle_keep_inside_vertically (rectangle, constraint);
}

/**
 * gimp_tool_rectangle_apply_aspect:
 * @rectangle:      A #GimpToolRectangle.
 * @aspect:         The desired aspect.
 * @clamped_sides:  Bitfield of sides that have been clamped.
 *
 * Adjust the rectangle to the desired aspect.
 *
 * Sometimes, a side must not be moved outwards, for example if a the
 * RIGHT side has been clamped previously, we must not move the RIGHT
 * side to the right, since that would violate the constraint
 * again. The clamped_sides bitfield keeps track of sides that have
 * previously been clamped.
 *
 * If fixed_center is used, the function adjusts the aspect by
 * symmetrically adjusting the left and right, or top and bottom side.
 */
static void
gimp_tool_rectangle_apply_aspect (GimpToolRectangle *rectangle,
                                  gdouble            aspect,
                                  gint               clamped_sides)
{
  GimpToolRectanglePrivate *private = rectangle->private;
  gdouble                   current_w;
  gdouble                   current_h;
  gdouble                   current_aspect;
  SideToResize              side_to_resize = SIDE_TO_RESIZE_NONE;

  current_w = private->x2 - private->x1;
  current_h = private->y2 - private->y1;

  current_aspect = (gdouble) current_w / (gdouble) current_h;

  /* Do we have to do anything? */
  if (current_aspect == aspect)
    return;

  if (private->fixed_center)
    {
      /* We may only adjust the sides symmetrically to get desired aspect. */
      if (current_aspect > aspect)
        {
          /* We prefer to use top and bottom (since that will make the
           * cursor remain on the rectangle edge), unless that is what
           * the user has grabbed.
           */
          switch (private->function)
            {
            case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
            case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
            case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
            case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
            case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
            case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
              if (! (clamped_sides & CLAMPED_TOP) &&
                  ! (clamped_sides & CLAMPED_BOTTOM))
                {
                  side_to_resize = SIDE_TO_RESIZE_TOP_AND_BOTTOM_SYMMETRICALLY;
                }
              else
                {
                  side_to_resize = SIDE_TO_RESIZE_LEFT_AND_RIGHT_SYMMETRICALLY;
                }
              break;

            case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
            case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
            default:
              side_to_resize = SIDE_TO_RESIZE_LEFT_AND_RIGHT_SYMMETRICALLY;
              break;
            }
        }
      else /* (current_aspect < aspect) */
        {
          /* We prefer to use left and right (since that will make the
           * cursor remain on the rectangle edge), unless that is what
           * the user has grabbed.
           */
          switch (private->function)
            {
            case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
            case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
            case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
            case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
            case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
            case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
              if (! (clamped_sides & CLAMPED_LEFT) &&
                  ! (clamped_sides & CLAMPED_RIGHT))
                {
                  side_to_resize = SIDE_TO_RESIZE_LEFT_AND_RIGHT_SYMMETRICALLY;
                }
              else
                {
                  side_to_resize = SIDE_TO_RESIZE_TOP_AND_BOTTOM_SYMMETRICALLY;
                }
              break;

            case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
            case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
            default:
              side_to_resize = SIDE_TO_RESIZE_TOP_AND_BOTTOM_SYMMETRICALLY;
              break;
            }
        }
    }
  else if (current_aspect > aspect)
    {
      /* We can safely pick LEFT or RIGHT, since using those sides
       * will make the rectangle smaller, so we don't need to check
       * for clamped_sides. We may only use TOP and BOTTOM if not
       * those sides have been clamped, since using them will make the
       * rectangle bigger.
       */
      switch (private->function)
        {
        case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
          if (! (clamped_sides & CLAMPED_TOP))
            side_to_resize = SIDE_TO_RESIZE_TOP;
          else
            side_to_resize = SIDE_TO_RESIZE_LEFT;
          break;

        case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
          if (! (clamped_sides & CLAMPED_TOP))
            side_to_resize = SIDE_TO_RESIZE_TOP;
          else
            side_to_resize = SIDE_TO_RESIZE_RIGHT;
          break;

        case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
          if (! (clamped_sides & CLAMPED_BOTTOM))
            side_to_resize = SIDE_TO_RESIZE_BOTTOM;
          else
            side_to_resize = SIDE_TO_RESIZE_LEFT;
          break;

        case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
          if (! (clamped_sides & CLAMPED_BOTTOM))
            side_to_resize = SIDE_TO_RESIZE_BOTTOM;
          else
            side_to_resize = SIDE_TO_RESIZE_RIGHT;
          break;

        case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
          if (! (clamped_sides & CLAMPED_TOP) &&
              ! (clamped_sides & CLAMPED_BOTTOM))
            side_to_resize = SIDE_TO_RESIZE_TOP_AND_BOTTOM_SYMMETRICALLY;
          else
            side_to_resize = SIDE_TO_RESIZE_LEFT;
          break;

        case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
          if (! (clamped_sides & CLAMPED_TOP) &&
              ! (clamped_sides & CLAMPED_BOTTOM))
            side_to_resize = SIDE_TO_RESIZE_TOP_AND_BOTTOM_SYMMETRICALLY;
          else
            side_to_resize = SIDE_TO_RESIZE_RIGHT;
          break;

        case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
        case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
          side_to_resize = SIDE_TO_RESIZE_LEFT_AND_RIGHT_SYMMETRICALLY;
          break;

        case GIMP_TOOL_RECTANGLE_MOVING:
        default:
          if (! (clamped_sides & CLAMPED_BOTTOM))
            side_to_resize = SIDE_TO_RESIZE_BOTTOM;
          else if (! (clamped_sides & CLAMPED_RIGHT))
            side_to_resize = SIDE_TO_RESIZE_RIGHT;
          else if (! (clamped_sides & CLAMPED_TOP))
            side_to_resize = SIDE_TO_RESIZE_TOP;
          else if (! (clamped_sides & CLAMPED_LEFT))
            side_to_resize = SIDE_TO_RESIZE_LEFT;
          break;
        }
    }
  else /* (current_aspect < aspect) */
    {
      /* We can safely pick TOP or BOTTOM, since using those sides
       * will make the rectangle smaller, so we don't need to check
       * for clamped_sides. We may only use LEFT and RIGHT if not
       * those sides have been clamped, since using them will make the
       * rectangle bigger.
       */
      switch (private->function)
        {
        case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
          if (! (clamped_sides & CLAMPED_LEFT))
            side_to_resize = SIDE_TO_RESIZE_LEFT;
          else
            side_to_resize = SIDE_TO_RESIZE_TOP;
          break;

        case GIMP_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
          if (! (clamped_sides & CLAMPED_RIGHT))
            side_to_resize = SIDE_TO_RESIZE_RIGHT;
          else
            side_to_resize = SIDE_TO_RESIZE_TOP;
          break;

        case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
          if (! (clamped_sides & CLAMPED_LEFT))
            side_to_resize = SIDE_TO_RESIZE_LEFT;
          else
            side_to_resize = SIDE_TO_RESIZE_BOTTOM;
          break;

        case GIMP_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
          if (! (clamped_sides & CLAMPED_RIGHT))
            side_to_resize = SIDE_TO_RESIZE_RIGHT;
          else
            side_to_resize = SIDE_TO_RESIZE_BOTTOM;
          break;

        case GIMP_TOOL_RECTANGLE_RESIZING_TOP:
          if (! (clamped_sides & CLAMPED_LEFT) &&
              ! (clamped_sides & CLAMPED_RIGHT))
            side_to_resize = SIDE_TO_RESIZE_LEFT_AND_RIGHT_SYMMETRICALLY;
          else
            side_to_resize = SIDE_TO_RESIZE_TOP;
          break;

        case GIMP_TOOL_RECTANGLE_RESIZING_BOTTOM:
          if (! (clamped_sides & CLAMPED_LEFT) &&
              ! (clamped_sides & CLAMPED_RIGHT))
            side_to_resize = SIDE_TO_RESIZE_LEFT_AND_RIGHT_SYMMETRICALLY;
          else
            side_to_resize = SIDE_TO_RESIZE_BOTTOM;
          break;

        case GIMP_TOOL_RECTANGLE_RESIZING_LEFT:
        case GIMP_TOOL_RECTANGLE_RESIZING_RIGHT:
          side_to_resize = SIDE_TO_RESIZE_TOP_AND_BOTTOM_SYMMETRICALLY;
          break;

        case GIMP_TOOL_RECTANGLE_MOVING:
        default:
          if (! (clamped_sides & CLAMPED_BOTTOM))
            side_to_resize = SIDE_TO_RESIZE_BOTTOM;
          else if (! (clamped_sides & CLAMPED_RIGHT))
            side_to_resize = SIDE_TO_RESIZE_RIGHT;
          else if (! (clamped_sides & CLAMPED_TOP))
            side_to_resize = SIDE_TO_RESIZE_TOP;
          else if (! (clamped_sides & CLAMPED_LEFT))
            side_to_resize = SIDE_TO_RESIZE_LEFT;
          break;
        }
    }

  /* We now know what side(s) we should resize, so now we just solve
   * the aspect equation for that side(s).
   */
  switch (side_to_resize)
    {
    case SIDE_TO_RESIZE_NONE:
      return;

    case SIDE_TO_RESIZE_LEFT:
      private->x1 = private->x2 - aspect * current_h;
      break;

    case SIDE_TO_RESIZE_RIGHT:
      private->x2 = private->x1 + aspect * current_h;
      break;

    case SIDE_TO_RESIZE_TOP:
      private->y1 = private->y2 - current_w / aspect;
      break;

    case SIDE_TO_RESIZE_BOTTOM:
      private->y2 = private->y1 + current_w / aspect;
      break;

    case SIDE_TO_RESIZE_TOP_AND_BOTTOM_SYMMETRICALLY:
      {
        gdouble correct_h = current_w / aspect;

        private->y1 = private->center_y_on_fixed_center - correct_h / 2;
        private->y2 = private->y1 + correct_h;
      }
      break;

    case SIDE_TO_RESIZE_LEFT_AND_RIGHT_SYMMETRICALLY:
      {
        gdouble correct_w = current_h * aspect;

        private->x1 = private->center_x_on_fixed_center - correct_w / 2;
        private->x2 = private->x1 + correct_w;
      }
      break;
    }
}

/**
 * gimp_tool_rectangle_update_with_coord:
 * @rectangle:      A #GimpToolRectangle.
 * @new_x:          New X-coordinate in the context of the current function.
 * @new_y:          New Y-coordinate in the context of the current function.
 *
 * The core rectangle adjustment function. It updates the rectangle
 * for the passed cursor coordinate, taking current function and tool
 * options into account.  It also updates the current
 * private->function if necessary.
 */
static void
gimp_tool_rectangle_update_with_coord (GimpToolRectangle *rectangle,
                                       gdouble            new_x,
                                       gdouble            new_y)
{
  GimpToolRectanglePrivate *private = rectangle->private;

  /* Move the corner or edge the user currently has grabbed. */
  gimp_tool_rectangle_apply_coord (rectangle, new_x, new_y);

  /* Update private->function. The function changes if the user
   * "flips" the rectangle.
   */
  gimp_tool_rectangle_check_function (rectangle);

  /* Clamp the rectangle if necessary */
  gimp_tool_rectangle_handle_general_clamping (rectangle);

  /* If the rectangle is being moved, do not run through any further
   * rectangle adjusting functions since it's shape should not change
   * then.
   */
  if (private->function != GIMP_TOOL_RECTANGLE_MOVING)
    {
      gimp_tool_rectangle_apply_fixed_rule (rectangle);
    }

  gimp_tool_rectangle_update_int_rect (rectangle);
}

static void
gimp_tool_rectangle_apply_fixed_rule (GimpToolRectangle *rectangle)
{
  GimpToolRectanglePrivate    *private = rectangle->private;
  GimpRectangleConstraint      constraint_to_use;
  GimpDisplayShell            *shell;
  GimpImage                   *image;

  shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (rectangle));
  image = gimp_display_get_image (shell->display);

  /* Calculate what constraint to use when needed. */
  constraint_to_use = gimp_tool_rectangle_get_constraint (rectangle);

  if (private->fixed_rule_active &&
      private->fixed_rule == GIMP_RECTANGLE_FIXED_ASPECT)
    {
      gdouble aspect;

      aspect = CLAMP (private->aspect_numerator /
                      private->aspect_denominator,
                      1.0 / gimp_image_get_height (image),
                      gimp_image_get_width (image));

      if (constraint_to_use == GIMP_RECTANGLE_CONSTRAIN_NONE)
        {
          gimp_tool_rectangle_apply_aspect (rectangle,
                                            aspect,
                                            CLAMPED_NONE);
        }
      else
        {
          if (private->function != GIMP_TOOL_RECTANGLE_MOVING)
            {
              ClampedSide clamped_sides = CLAMPED_NONE;

              gimp_tool_rectangle_apply_aspect (rectangle,
                                                aspect,
                                                clamped_sides);

              /* After we have applied aspect, we might have taken the
               * rectangle outside of constraint, so clamp and apply
               * aspect again. We will get the right result this time,
               * since 'clamped_sides' will be setup correctly now.
               */
              gimp_tool_rectangle_clamp (rectangle,
                                         &clamped_sides,
                                         constraint_to_use,
                                         private->fixed_center);

              gimp_tool_rectangle_apply_aspect (rectangle,
                                                aspect,
                                                clamped_sides);
            }
          else
            {
              gimp_tool_rectangle_apply_aspect (rectangle,
                                                aspect,
                                                CLAMPED_NONE);

              gimp_tool_rectangle_keep_inside (rectangle,
                                               constraint_to_use);
            }
        }
    }
  else if (private->fixed_rule_active &&
           private->fixed_rule == GIMP_RECTANGLE_FIXED_SIZE)
    {
      gimp_tool_rectangle_apply_fixed_width (rectangle,
                                             constraint_to_use,
                                             private->desired_fixed_size_width);
      gimp_tool_rectangle_apply_fixed_height (rectangle,
                                              constraint_to_use,
                                              private->desired_fixed_size_height);
    }
  else if (private->fixed_rule_active &&
           private->fixed_rule == GIMP_RECTANGLE_FIXED_WIDTH)
    {
      gimp_tool_rectangle_apply_fixed_width (rectangle,
                                             constraint_to_use,
                                             private->desired_fixed_width);
    }
  else if (private->fixed_rule_active &&
           private->fixed_rule == GIMP_RECTANGLE_FIXED_HEIGHT)
    {
      gimp_tool_rectangle_apply_fixed_height (rectangle,
                                              constraint_to_use,
                                              private->desired_fixed_height);
    }
}

/**
 * gimp_tool_rectangle_get_constraints:
 * @rectangle:      A #GimpToolRectangle.
 * @min_x:
 * @min_y:
 * @max_x:
 * @max_y:          Pointers of where to put constraints. NULL allowed.
 * @constraint:     Whether to return image or layer constraints.
 *
 * Calculates constraint coordinates for image or layer.
 */
static void
gimp_tool_rectangle_get_constraints (GimpToolRectangle       *rectangle,
                                     gint                    *min_x,
                                     gint                    *min_y,
                                     gint                    *max_x,
                                     gint                    *max_y,
                                     GimpRectangleConstraint  constraint)
{
  GimpDisplayShell *shell;
  GimpImage        *image;
  gint              min_x_dummy;
  gint              min_y_dummy;
  gint              max_x_dummy;
  gint              max_y_dummy;

  shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (rectangle));
  image = gimp_display_get_image (shell->display);

  if (! min_x) min_x = &min_x_dummy;
  if (! min_y) min_y = &min_y_dummy;
  if (! max_x) max_x = &max_x_dummy;
  if (! max_y) max_y = &max_y_dummy;

  *min_x = 0;
  *min_y = 0;
  *max_x = 0;
  *max_y = 0;

  switch (constraint)
    {
    case GIMP_RECTANGLE_CONSTRAIN_IMAGE:
      if (image)
        {
          *min_x = 0;
          *min_y = 0;
          *max_x = gimp_image_get_width  (image);
          *max_y = gimp_image_get_height (image);
        }
      break;

    case GIMP_RECTANGLE_CONSTRAIN_DRAWABLE:
      if (image)
        {
          GimpItem *item = GIMP_ITEM (gimp_image_get_active_drawable (image));

          if (item)
            {
              gimp_item_get_offset (item, min_x, min_y);
              *max_x = *min_x + gimp_item_get_width  (item);
              *max_y = *min_y + gimp_item_get_height (item);
            }
        }
      break;

    default:
      g_warning ("Invalid rectangle constraint.\n");
      return;
    }
}

/**
 * gimp_tool_rectangle_handle_general_clamping:
 * @rectangle: A #GimpToolRectangle.
 *
 * Make sure that constraints are applied to the rectangle, either by
 * manually doing it, or by looking at the rectangle tool options and
 * concluding it will be done later.
 */
static void
gimp_tool_rectangle_handle_general_clamping (GimpToolRectangle *rectangle)
{
  GimpToolRectanglePrivate *private = rectangle->private;
  GimpRectangleConstraint   constraint;

  constraint = gimp_tool_rectangle_get_constraint (rectangle);

  /* fixed_aspect takes care of clamping by it self, so just return in
   * case that is in use. Also return if no constraints should be
   * enforced.
   */
  if (constraint == GIMP_RECTANGLE_CONSTRAIN_NONE)
    return;

  if (private->function != GIMP_TOOL_RECTANGLE_MOVING)
    {
      gimp_tool_rectangle_clamp (rectangle,
                                 NULL,
                                 constraint,
                                 private->fixed_center);
    }
  else
    {
      gimp_tool_rectangle_keep_inside (rectangle, constraint);
    }
}

/**
 * gimp_tool_rectangle_update_int_rect:
 * @rectangle:
 *
 * Update integer representation of rectangle.
 **/
static void
gimp_tool_rectangle_update_int_rect (GimpToolRectangle *rectangle)
{
  GimpToolRectanglePrivate *private = rectangle->private;

  private->x1_int = SIGNED_ROUND (private->x1);
  private->y1_int = SIGNED_ROUND (private->y1);

  if (gimp_tool_rectangle_rect_rubber_banding_func (rectangle))
    {
      private->width_int  = (gint) SIGNED_ROUND (private->x2) - private->x1_int;
      private->height_int = (gint) SIGNED_ROUND (private->y2) - private->y1_int;
    }
}

/**
 * gimp_tool_rectangle_adjust_coord:
 * @rectangle:
 * @ccoord_x_input:
 * @ccoord_x_input:
 * @ccoord_x_output:
 * @ccoord_x_output:
 *
 * Transforms a coordinate to better fit the public behaviour of the
 * rectangle.
 */
static void
gimp_tool_rectangle_adjust_coord (GimpToolRectangle *rectangle,
                                  gdouble            coord_x_input,
                                  gdouble            coord_y_input,
                                  gdouble           *coord_x_output,
                                  gdouble           *coord_y_output)
{
  GimpToolRectanglePrivate *priv = rectangle->private;

  switch (priv->precision)
    {
    case GIMP_RECTANGLE_PRECISION_INT:
      *coord_x_output = RINT (coord_x_input);
      *coord_y_output = RINT (coord_y_input);
      break;

    case GIMP_RECTANGLE_PRECISION_DOUBLE:
    default:
      *coord_x_output = coord_x_input;
      *coord_y_output = coord_y_input;
      break;
    }
}

static void
gimp_tool_rectangle_recalculate_center_xy (GimpToolRectangle *rectangle)
{
  GimpToolRectanglePrivate *private = rectangle->private;

  private->center_x_on_fixed_center = (private->x1 + private->x2) / 2;
  private->center_y_on_fixed_center = (private->y1 + private->y2) / 2;
}


/*  public functions  */

GimpToolWidget *
gimp_tool_rectangle_new (GimpDisplayShell  *shell)
{
  g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);

  return g_object_new (GIMP_TYPE_TOOL_RECTANGLE,
                       "shell",      shell,
                       NULL);
}

GimpRectangleFunction
gimp_tool_rectangle_get_function (GimpToolRectangle *rectangle)
{
  g_return_val_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle),
                        GIMP_TOOL_RECTANGLE_DEAD);

  return rectangle->private->function;
}

void
gimp_tool_rectangle_set_function (GimpToolRectangle     *rectangle,
                                  GimpRectangleFunction  function)
{
  GimpToolRectanglePrivate *private;

  g_return_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle));

  private = rectangle->private;

  if (private->function != function)
    {
      private->function = function;

      gimp_tool_rectangle_changed (GIMP_TOOL_WIDGET (rectangle));
    }
}

void
gimp_tool_rectangle_set_constraint (GimpToolRectangle       *rectangle,
                                    GimpRectangleConstraint  constraint)
{
  GimpToolRectanglePrivate *private;

  g_return_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle));

  private = rectangle->private;

  if (constraint != private->constraint)
    {
      g_object_freeze_notify (G_OBJECT (rectangle));

      private->constraint = constraint;
      g_object_notify (G_OBJECT (rectangle), "constraint");

      gimp_tool_rectangle_clamp (rectangle, NULL, constraint, FALSE);

      g_object_thaw_notify (G_OBJECT (rectangle));

      gimp_tool_rectangle_change_complete (rectangle);
    }
}

GimpRectangleConstraint
gimp_tool_rectangle_get_constraint (GimpToolRectangle *rectangle)
{
  g_return_val_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle), 0);

  return rectangle->private->constraint;
}

/**
 * gimp_tool_rectangle_get_public_rect:
 * @rectangle:
 * @x1:
 * @y1:
 * @x2:
 * @y2:
 *
 * This function returns the rectangle as it appears to be publicly
 * (based on integer or double precision-mode).
 **/
void
gimp_tool_rectangle_get_public_rect (GimpToolRectangle *rectangle,
                                     gdouble           *x1,
                                     gdouble           *y1,
                                     gdouble           *x2,
                                     gdouble           *y2)
{
  GimpToolRectanglePrivate *priv;

  g_return_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle));
  g_return_if_fail (x1 != NULL);
  g_return_if_fail (y1 != NULL);
  g_return_if_fail (x2 != NULL);
  g_return_if_fail (y2 != NULL);

  priv = rectangle->private;

  switch (priv->precision)
    {
    case GIMP_RECTANGLE_PRECISION_INT:
      *x1 = priv->x1_int;
      *y1 = priv->y1_int;
      *x2 = priv->x1_int + priv->width_int;
      *y2 = priv->y1_int + priv->height_int;
      break;

    case GIMP_RECTANGLE_PRECISION_DOUBLE:
    default:
      *x1 = priv->x1;
      *y1 = priv->y1;
      *x2 = priv->x2;
      *y2 = priv->y2;
      break;
    }
}

/**
 * gimp_tool_rectangle_pending_size_set:
 * @width_property:  Option property to set to pending rectangle width.
 * @height_property: Option property to set to pending rectangle height.
 *
 * Sets specified rectangle tool options properties to the width and
 * height of the current pending rectangle.
 */
void
gimp_tool_rectangle_pending_size_set (GimpToolRectangle *rectangle,
                                      GObject           *object,
                                      const gchar       *width_property,
                                      const gchar       *height_property)
{
  GimpToolRectanglePrivate *private;

  g_return_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle));
  g_return_if_fail (width_property  != NULL);
  g_return_if_fail (height_property != NULL);

  private = rectangle->private;

  g_object_set (object,
                width_property,  MAX (private->x2 - private->x1, 1.0),
                height_property, MAX (private->y2 - private->y1, 1.0),
                NULL);
}

/**
 * gimp_tool_rectangle_constraint_size_set:
 * @width_property:  Option property to set to current constraint width.
 * @height_property: Option property to set to current constraint height.
 *
 * Sets specified rectangle tool options properties to the width and
 * height of the current constraint size.
 */
void
gimp_tool_rectangle_constraint_size_set (GimpToolRectangle *rectangle,
                                         GObject           *object,
                                         const gchar       *width_property,
                                         const gchar       *height_property)
{
  GimpDisplayShell *shell;
  GimpContext      *context;
  GimpImage        *image;
  gdouble           width;
  gdouble           height;

  g_return_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle));

  shell   = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (rectangle));
  context = gimp_get_user_context (shell->display->gimp);
  image   = gimp_context_get_image (context);

  if (! image)
    {
      width  = 1.0;
      height = 1.0;
    }
  else
    {
      GimpRectangleConstraint constraint;

      constraint = gimp_tool_rectangle_get_constraint (rectangle);

      switch (constraint)
        {
        case GIMP_RECTANGLE_CONSTRAIN_DRAWABLE:
          {
            GimpItem *item = GIMP_ITEM (gimp_image_get_active_layer (image));

            if (! item)
              {
                width  = 1.0;
                height = 1.0;
              }
            else
              {
                width  = gimp_item_get_width  (item);
                height = gimp_item_get_height (item);
              }
          }
          break;

        case GIMP_RECTANGLE_CONSTRAIN_IMAGE:
        default:
          {
            width  = gimp_image_get_width  (image);
            height = gimp_image_get_height (image);
          }
          break;
        }
    }

  g_object_set (object,
                width_property,  width,
                height_property, height,
                NULL);
}

/**
 * gimp_tool_rectangle_rectangle_is_first:
 * @rectangle:
 *
 * Returns: %TRUE if the user is creating the first rectangle with
 * this instance from scratch, %FALSE if modifying an existing
 * rectangle, or creating a new rectangle, discarding the existing
 * one. This function is only meaningful in _motion and
 * _button_release.
 */
gboolean
gimp_tool_rectangle_rectangle_is_first (GimpToolRectangle *rectangle)
{
  g_return_val_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle), FALSE);

  return rectangle->private->is_first;
}

/**
 * gimp_tool_rectangle_rectangle_is_new:
 * @rectangle:
 *
 * Returns: %TRUE if the user is creating a new rectangle from
 * scratch, %FALSE if modifying n previously existing rectangle. This
 * function is only meaningful in _motion and _button_release.
 */
gboolean
gimp_tool_rectangle_rectangle_is_new (GimpToolRectangle *rectangle)
{
  g_return_val_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle), FALSE);

  return rectangle->private->is_new;
}

/**
 * gimp_tool_rectangle_point_in_rectangle:
 * @rectangle:
 * @x:         X-coord of point to test (in image coordinates)
 * @y:         Y-coord of point to test (in image coordinates)
 *
 * Returns: %TRUE if the passed point was within the rectangle
 **/
gboolean
gimp_tool_rectangle_point_in_rectangle (GimpToolRectangle *rectangle,
                                        gdouble            x,
                                        gdouble            y)
{
  gdouble  x1, y1, x2, y2;

  g_return_val_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle), FALSE);

  gimp_tool_rectangle_get_public_rect (rectangle, &x1, &y1, &x2, &y2);

  return (x >= x1 && x <= x2 &&
          y >= y1 && y <= y2);
}

/**
 * gimp_tool_rectangle_frame_item:
 * @rectangle: a #GimpToolRectangle interface
 * @item:      a #GimpItem attached to the image on which a
 *             rectangle is being shown.
 *
 * Convenience function to set the corners of the rectangle to
 * match the bounds of the specified item.  The rectangle interface
 * must be active (i.e., showing a rectangle), and the item must be
 * attached to the image on which the rectangle is active.
 **/
void
gimp_tool_rectangle_frame_item (GimpToolRectangle *rectangle,
                                GimpItem          *item)
{
  GimpDisplayShell *shell;
  gint              offset_x;
  gint              offset_y;
  gint              width;
  gint              height;

  g_return_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle));
  g_return_if_fail (GIMP_IS_ITEM (item));
  g_return_if_fail (gimp_item_is_attached (item));

  shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (rectangle));

  g_return_if_fail (gimp_display_get_image (shell->display) ==
                    gimp_item_get_image (item));

  width  = gimp_item_get_width  (item);
  height = gimp_item_get_height (item);

  gimp_item_get_offset (item, &offset_x, &offset_y);

  gimp_tool_rectangle_set_function (rectangle, GIMP_TOOL_RECTANGLE_CREATING);

  g_object_set (rectangle,
                "x1", (gdouble) offset_x,
                "y1", (gdouble) offset_y,
                "x2", (gdouble) (offset_x + width),
                "y2", (gdouble) (offset_y + height),
                NULL);

  /* kludge to force handle sizes to update.  This call may be harmful
   * if this function is ever moved out of the text tool code.
   */
  gimp_tool_rectangle_set_constraint (rectangle, GIMP_RECTANGLE_CONSTRAIN_NONE);
}

void
gimp_tool_rectangle_auto_shrink (GimpToolRectangle *rectangle,
                                 gboolean           shrink_merged)
{
  GimpToolRectanglePrivate *private;
  GimpDisplayShell         *shell;
  GimpImage                *image;
  GimpPickable             *pickable;
  gint                      offset_x = 0;
  gint                      offset_y = 0;
  gint                      x1, y1;
  gint                      x2, y2;
  gint                      shrunk_x;
  gint                      shrunk_y;
  gint                      shrunk_width;
  gint                      shrunk_height;

  g_return_if_fail (GIMP_IS_TOOL_RECTANGLE (rectangle));

  private = rectangle->private;

  shell = gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (rectangle));
  image = gimp_display_get_image (shell->display);

  if (shrink_merged)
    {
      pickable = GIMP_PICKABLE (image);

      x1 = private->x1;
      y1 = private->y1;
      x2 = private->x2;
      y2 = private->y2;
    }
  else
    {
      pickable = GIMP_PICKABLE (gimp_image_get_active_drawable (image));

      if (! pickable)
        return;

      gimp_item_get_offset (GIMP_ITEM (pickable), &offset_x, &offset_y);

      x1 = private->x1 - offset_x;
      y1 = private->y1 - offset_y;
      x2 = private->x2 - offset_x;
      y2 = private->y2 - offset_y;
    }

  switch (gimp_pickable_auto_shrink (pickable,
                                     x1, y1, x2 - x1, y2 - y1,
                                     &shrunk_x,
                                     &shrunk_y,
                                     &shrunk_width,
                                     &shrunk_height))
    {
    case GIMP_AUTO_SHRINK_SHRINK:
      {
        GimpRectangleFunction original_function = private->function;

        private->function = GIMP_TOOL_RECTANGLE_AUTO_SHRINK;

        private->x1 = offset_x + shrunk_x;
        private->y1 = offset_y + shrunk_y;
        private->x2 = offset_x + shrunk_x + shrunk_width;
        private->y2 = offset_y + shrunk_y + shrunk_height;

        gimp_tool_rectangle_update_int_rect (rectangle);

        gimp_tool_rectangle_change_complete (rectangle);

        private->function = original_function;

        gimp_tool_rectangle_update_options (rectangle);
      }
      break;

    default:
      break;
    }
}
