summaryrefslogtreecommitdiffstats
path: root/app/display/gimptoolrectangle.c
diff options
context:
space:
mode:
Diffstat (limited to 'app/display/gimptoolrectangle.c')
-rw-r--r--app/display/gimptoolrectangle.c4266
1 files changed, 4266 insertions, 0 deletions
diff --git a/app/display/gimptoolrectangle.c b/app/display/gimptoolrectangle.c
new file mode 100644
index 0000000..5b5a3a7
--- /dev/null
+++ b/app/display/gimptoolrectangle.c
@@ -0,0 +1,4266 @@
+/* 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];
+};
+
+
+/* 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;
+}
+
+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;
+
+ /* 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);
+
+ 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
+
+ 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 ())
+ {
+ 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;
+ }
+}