diff options
Diffstat (limited to '')
-rw-r--r-- | app/display/gimptoolrectangle.c | 4266 |
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; + } +} |