/* GIMP - The GNU Image Manipulation Program * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * gimptoolrectangle.c * Copyright (C) 2017 Michael Natterer * * 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 . */ #include "config.h" #include #include #include #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; } }