PIKApp/app/display/pikatoolrectangle.c

4366 lines
151 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* PIKA - Photo and Image Kooker Application
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
*
* Original copyright, applying to most contents (license remains unchanged):
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* pikatoolrectangle.c
* Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
*
* Based on PikaRectangleTool
* Copyright (C) 2007 Martin Nordholts
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <gegl.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include "libpikabase/pikabase.h"
#include "libpikamath/pikamath.h"
#include "display-types.h"
#include "core/pika.h"
#include "core/pikacontext.h"
#include "core/pikaimage.h"
#include "core/pikaitem.h"
#include "core/pikapickable.h"
#include "core/pikapickable-auto-shrink.h"
#include "widgets/pikawidgets-utils.h"
#include "pikacanvasarc.h"
#include "pikacanvascorner.h"
#include "pikacanvashandle.h"
#include "pikacanvasitem-utils.h"
#include "pikacanvasrectangle.h"
#include "pikacanvasrectangleguides.h"
#include "pikadisplay.h"
#include "pikadisplayshell.h"
#include "pikadisplayshell-scroll.h"
#include "pikatoolrectangle.h"
#include "pika-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 _PikaToolRectanglePrivate
{
/* The following members are "constants", that is, variables that are setup
* during pika_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 coordinates 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. */
PikaRectangleConstraint constraint;
/* What precision the rectangle will appear to have externally (it
* will always be double internally)
*/
PikaRectanglePrecision 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;
PikaRectangleFunction function;
/* The following values are externally synced with PikaRectangleOptions */
gboolean highlight;
gdouble highlight_opacity;
PikaGuidesType guide;
gdouble x;
gdouble y;
gdouble width;
gdouble height;
gboolean fixed_rule_active;
PikaRectangleFixedRule 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 */
PikaCanvasItem *guides;
PikaCanvasItem *rectangle;
PikaCanvasItem *ellipse;
PikaCanvasItem *corners[4];
PikaCanvasItem *center;
PikaCanvasItem *creating_corners[4];
PikaCanvasItem *handles[PIKA_N_TOOL_RECTANGLE_FUNCTIONS];
PikaCanvasItem *highlight_handles[PIKA_N_TOOL_RECTANGLE_FUNCTIONS];
/* Flags to prevent temporary changes to the Expand from center and Fixed
options for the rectangle select tool, ellipse select tool and the crop
tool due to use of the modifier keys becoming latched See issue #7954 and
MR !779. */
gboolean fixed_center_copy;
gboolean fixed_rule_active_copy;
gboolean modifier_toggle_allowed;
};
/* local function prototypes */
static void pika_tool_rectangle_constructed (GObject *object);
static void pika_tool_rectangle_finalize (GObject *object);
static void pika_tool_rectangle_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void pika_tool_rectangle_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
static void pika_tool_rectangle_notify (GObject *object,
GParamSpec *pspec);
static void pika_tool_rectangle_changed (PikaToolWidget *widget);
static gint pika_tool_rectangle_button_press (PikaToolWidget *widget,
const PikaCoords *coords,
guint32 time,
GdkModifierType state,
PikaButtonPressType press_type);
static void pika_tool_rectangle_button_release (PikaToolWidget *widget,
const PikaCoords *coords,
guint32 time,
GdkModifierType state,
PikaButtonReleaseType release_type);
static void pika_tool_rectangle_motion (PikaToolWidget *widget,
const PikaCoords *coords,
guint32 time,
GdkModifierType state);
static PikaHit pika_tool_rectangle_hit (PikaToolWidget *widget,
const PikaCoords *coords,
GdkModifierType state,
gboolean proximity);
static void pika_tool_rectangle_hover (PikaToolWidget *widget,
const PikaCoords *coords,
GdkModifierType state,
gboolean proximity);
static void pika_tool_rectangle_leave_notify (PikaToolWidget *widget);
static gboolean pika_tool_rectangle_key_press (PikaToolWidget *widget,
GdkEventKey *kevent);
static void pika_tool_rectangle_motion_modifier (PikaToolWidget *widget,
GdkModifierType key,
gboolean press,
GdkModifierType state);
static gboolean pika_tool_rectangle_get_cursor (PikaToolWidget *widget,
const PikaCoords *coords,
GdkModifierType state,
PikaCursorType *cursor,
PikaToolCursorType *tool_cursor,
PikaCursorModifier *modifier);
static void pika_tool_rectangle_change_complete (PikaToolRectangle *rectangle);
static void pika_tool_rectangle_update_options (PikaToolRectangle *rectangle);
static void pika_tool_rectangle_update_handle_sizes
(PikaToolRectangle *rectangle);
static void pika_tool_rectangle_update_status (PikaToolRectangle *rectangle);
static void pika_tool_rectangle_synthesize_motion
(PikaToolRectangle *rectangle,
gint function,
gdouble new_x,
gdouble new_y);
static PikaRectangleFunction
pika_tool_rectangle_calc_function (PikaToolRectangle *rectangle,
const PikaCoords *coords,
gboolean proximity);
static void pika_tool_rectangle_check_function (PikaToolRectangle *rectangle);
static gboolean pika_tool_rectangle_coord_outside (PikaToolRectangle *rectangle,
const PikaCoords *coords);
static gboolean pika_tool_rectangle_coord_on_handle (PikaToolRectangle *rectangle,
const PikaCoords *coords,
PikaHandleAnchor anchor);
static PikaHandleAnchor pika_tool_rectangle_get_anchor
(PikaRectangleFunction function);
static gboolean pika_tool_rectangle_rect_rubber_banding_func
(PikaToolRectangle *rectangle);
static gboolean pika_tool_rectangle_rect_adjusting_func
(PikaToolRectangle *rectangle);
static void pika_tool_rectangle_get_other_side (PikaToolRectangle *rectangle,
gdouble **other_x,
gdouble **other_y);
static void pika_tool_rectangle_get_other_side_coord
(PikaToolRectangle *rectangle,
gdouble *other_side_x,
gdouble *other_side_y);
static void pika_tool_rectangle_set_other_side_coord
(PikaToolRectangle *rectangle,
gdouble other_side_x,
gdouble other_side_y);
static void pika_tool_rectangle_apply_coord (PikaToolRectangle *rectangle,
gdouble coord_x,
gdouble coord_y);
static void pika_tool_rectangle_setup_snap_offsets
(PikaToolRectangle *rectangle,
const PikaCoords *coords);
static void pika_tool_rectangle_clamp (PikaToolRectangle *rectangle,
ClampedSide *clamped_sides,
PikaRectangleConstraint constraint,
gboolean symmetrically);
static void pika_tool_rectangle_clamp_width (PikaToolRectangle *rectangle,
ClampedSide *clamped_sides,
PikaRectangleConstraint constraint,
gboolean symmetrically);
static void pika_tool_rectangle_clamp_height (PikaToolRectangle *rectangle,
ClampedSide *clamped_sides,
PikaRectangleConstraint constraint,
gboolean symmetrically);
static void pika_tool_rectangle_keep_inside (PikaToolRectangle *rectangle,
PikaRectangleConstraint constraint);
static void pika_tool_rectangle_keep_inside_horizontally
(PikaToolRectangle *rectangle,
PikaRectangleConstraint constraint);
static void pika_tool_rectangle_keep_inside_vertically
(PikaToolRectangle *rectangle,
PikaRectangleConstraint constraint);
static void pika_tool_rectangle_apply_fixed_width
(PikaToolRectangle *rectangle,
PikaRectangleConstraint constraint,
gdouble width);
static void pika_tool_rectangle_apply_fixed_height
(PikaToolRectangle *rectangle,
PikaRectangleConstraint constraint,
gdouble height);
static void pika_tool_rectangle_apply_aspect (PikaToolRectangle *rectangle,
gdouble aspect,
gint clamped_sides);
static void pika_tool_rectangle_update_with_coord
(PikaToolRectangle *rectangle,
gdouble new_x,
gdouble new_y);
static void pika_tool_rectangle_apply_fixed_rule(PikaToolRectangle *rectangle);
static void pika_tool_rectangle_get_constraints (PikaToolRectangle *rectangle,
gint *min_x,
gint *min_y,
gint *max_x,
gint *max_y,
PikaRectangleConstraint constraint);
static void pika_tool_rectangle_handle_general_clamping
(PikaToolRectangle *rectangle);
static void pika_tool_rectangle_update_int_rect (PikaToolRectangle *rectangle);
static void pika_tool_rectangle_adjust_coord (PikaToolRectangle *rectangle,
gdouble coord_x_input,
gdouble coord_y_input,
gdouble *coord_x_output,
gdouble *coord_y_output);
static void pika_tool_rectangle_recalculate_center_xy
(PikaToolRectangle *rectangle);
G_DEFINE_TYPE_WITH_PRIVATE (PikaToolRectangle, pika_tool_rectangle,
PIKA_TYPE_TOOL_WIDGET)
#define parent_class pika_tool_rectangle_parent_class
static guint rectangle_signals[LAST_SIGNAL] = { 0, };
static void
pika_tool_rectangle_class_init (PikaToolRectangleClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
PikaToolWidgetClass *widget_class = PIKA_TOOL_WIDGET_CLASS (klass);
object_class->constructed = pika_tool_rectangle_constructed;
object_class->finalize = pika_tool_rectangle_finalize;
object_class->set_property = pika_tool_rectangle_set_property;
object_class->get_property = pika_tool_rectangle_get_property;
object_class->notify = pika_tool_rectangle_notify;
widget_class->changed = pika_tool_rectangle_changed;
widget_class->button_press = pika_tool_rectangle_button_press;
widget_class->button_release = pika_tool_rectangle_button_release;
widget_class->motion = pika_tool_rectangle_motion;
widget_class->hit = pika_tool_rectangle_hit;
widget_class->hover = pika_tool_rectangle_hover;
widget_class->leave_notify = pika_tool_rectangle_leave_notify;
widget_class->key_press = pika_tool_rectangle_key_press;
widget_class->motion_modifier = pika_tool_rectangle_motion_modifier;
widget_class->get_cursor = pika_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 (PikaToolRectangleClass, change_complete),
NULL, NULL, NULL,
G_TYPE_NONE, 0);
g_object_class_install_property (object_class, PROP_X1,
g_param_spec_double ("x1",
NULL, NULL,
-PIKA_MAX_IMAGE_SIZE,
PIKA_MAX_IMAGE_SIZE,
0.0,
PIKA_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_Y1,
g_param_spec_double ("y1",
NULL, NULL,
-PIKA_MAX_IMAGE_SIZE,
PIKA_MAX_IMAGE_SIZE,
0.0,
PIKA_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_X2,
g_param_spec_double ("x2",
NULL, NULL,
-PIKA_MAX_IMAGE_SIZE,
PIKA_MAX_IMAGE_SIZE,
0.0,
PIKA_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_Y2,
g_param_spec_double ("y2",
NULL, NULL,
-PIKA_MAX_IMAGE_SIZE,
PIKA_MAX_IMAGE_SIZE,
0.0,
PIKA_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_CONSTRAINT,
g_param_spec_enum ("constraint",
NULL, NULL,
PIKA_TYPE_RECTANGLE_CONSTRAINT,
PIKA_RECTANGLE_CONSTRAIN_NONE,
PIKA_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_PRECISION,
g_param_spec_enum ("precision",
NULL, NULL,
PIKA_TYPE_RECTANGLE_PRECISION,
PIKA_RECTANGLE_PRECISION_INT,
PIKA_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_NARROW_MODE,
g_param_spec_boolean ("narrow-mode",
NULL, NULL,
FALSE,
PIKA_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,
PIKA_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_DRAW_ELLIPSE,
g_param_spec_boolean ("draw-ellipse",
NULL, NULL,
FALSE,
PIKA_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_ROUND_CORNERS,
g_param_spec_boolean ("round-corners",
NULL, NULL,
FALSE,
PIKA_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,
PIKA_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_STATUS_TITLE,
g_param_spec_string ("status-title",
NULL, NULL,
_("Rectangle: "),
PIKA_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_HIGHLIGHT,
g_param_spec_boolean ("highlight",
NULL, NULL,
FALSE,
PIKA_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,
PIKA_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_GUIDE,
g_param_spec_enum ("guide",
NULL, NULL,
PIKA_TYPE_GUIDES_TYPE,
PIKA_GUIDES_NONE,
PIKA_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_X,
g_param_spec_double ("x",
NULL, NULL,
-PIKA_MAX_IMAGE_SIZE,
PIKA_MAX_IMAGE_SIZE,
0.0,
PIKA_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_Y,
g_param_spec_double ("y",
NULL, NULL,
-PIKA_MAX_IMAGE_SIZE,
PIKA_MAX_IMAGE_SIZE,
0.0,
PIKA_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_WIDTH,
g_param_spec_double ("width",
NULL, NULL,
0.0,
PIKA_MAX_IMAGE_SIZE,
0.0,
PIKA_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_HEIGHT,
g_param_spec_double ("height",
NULL, NULL,
0.0,
PIKA_MAX_IMAGE_SIZE,
0.0,
PIKA_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,
PIKA_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_FIXED_RULE,
g_param_spec_enum ("fixed-rule",
NULL, NULL,
PIKA_TYPE_RECTANGLE_FIXED_RULE,
PIKA_RECTANGLE_FIXED_ASPECT,
PIKA_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, PIKA_MAX_IMAGE_SIZE,
100.0,
PIKA_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, PIKA_MAX_IMAGE_SIZE,
100.0,
PIKA_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, PIKA_MAX_IMAGE_SIZE,
100.0,
PIKA_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, PIKA_MAX_IMAGE_SIZE,
100.0,
PIKA_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, PIKA_MAX_IMAGE_SIZE,
1.0,
PIKA_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, PIKA_MAX_IMAGE_SIZE,
1.0,
PIKA_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_FIXED_CENTER,
g_param_spec_boolean ("fixed-center",
NULL, NULL,
FALSE,
PIKA_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
}
static void
pika_tool_rectangle_init (PikaToolRectangle *rectangle)
{
rectangle->private = pika_tool_rectangle_get_instance_private (rectangle);
rectangle->private->function = PIKA_TOOL_RECTANGLE_CREATING;
rectangle->private->is_first = TRUE;
rectangle->private->modifier_toggle_allowed = FALSE;
}
static void
pika_tool_rectangle_constructed (GObject *object)
{
PikaToolRectangle *rectangle = PIKA_TOOL_RECTANGLE (object);
PikaToolWidget *widget = PIKA_TOOL_WIDGET (object);
PikaToolRectanglePrivate *private = rectangle->private;
PikaCanvasGroup *stroke_group;
gint i;
G_OBJECT_CLASS (parent_class)->constructed (object);
stroke_group = pika_tool_widget_add_stroke_group (widget);
pika_tool_widget_push_group (widget, stroke_group);
private->guides = pika_tool_widget_add_rectangle_guides (widget,
0, 0, 10, 10,
PIKA_GUIDES_NONE);
private->rectangle = pika_tool_widget_add_rectangle (widget,
0, 0, 10, 10,
FALSE);
private->ellipse = pika_tool_widget_add_arc (widget,
0, 0, 10, 10,
0.0, 2 * G_PI,
FALSE);
for (i = 0; i < 4; i++)
private->corners[i] = pika_tool_widget_add_arc (widget,
0, 0, 10, 10,
0.0, 2 * G_PI,
FALSE);
pika_tool_widget_pop_group (widget);
private->center = pika_tool_widget_add_handle (widget,
PIKA_HANDLE_CROSS,
0, 0,
PIKA_CANVAS_HANDLE_SIZE_SMALL,
PIKA_CANVAS_HANDLE_SIZE_SMALL,
PIKA_HANDLE_ANCHOR_CENTER);
pika_tool_widget_push_group (widget, stroke_group);
private->creating_corners[0] =
pika_tool_widget_add_corner (widget,
0, 0, 10, 10,
PIKA_HANDLE_ANCHOR_NORTH_WEST,
10, 10,
FALSE);
private->creating_corners[1] =
pika_tool_widget_add_corner (widget,
0, 0, 10, 10,
PIKA_HANDLE_ANCHOR_NORTH_EAST,
10, 10,
FALSE);
private->creating_corners[2] =
pika_tool_widget_add_corner (widget,
0, 0, 10, 10,
PIKA_HANDLE_ANCHOR_SOUTH_WEST,
10, 10,
FALSE);
private->creating_corners[3] =
pika_tool_widget_add_corner (widget,
0, 0, 10, 10,
PIKA_HANDLE_ANCHOR_SOUTH_EAST,
10, 10,
FALSE);
pika_tool_widget_pop_group (widget);
for (i = PIKA_TOOL_RECTANGLE_RESIZING_UPPER_LEFT;
i <= PIKA_TOOL_RECTANGLE_RESIZING_BOTTOM;
i++)
{
PikaHandleAnchor anchor;
anchor = pika_tool_rectangle_get_anchor (i);
pika_tool_widget_push_group (widget, stroke_group);
private->handles[i] = pika_tool_widget_add_corner (widget,
0, 0, 10, 10,
anchor,
10, 10,
FALSE);
pika_tool_widget_pop_group (widget);
private->highlight_handles[i] = pika_tool_widget_add_corner (widget,
0, 0, 10, 10,
anchor,
10, 10,
FALSE);
pika_canvas_item_set_highlight (private->highlight_handles[i], TRUE);
}
pika_tool_rectangle_changed (widget);
}
static void
pika_tool_rectangle_finalize (GObject *object)
{
PikaToolRectangle *rectangle = PIKA_TOOL_RECTANGLE (object);
PikaToolRectanglePrivate *private = rectangle->private;
g_clear_pointer (&private->status_title, g_free);
G_OBJECT_CLASS (parent_class)->finalize (object);
}
static void
pika_tool_rectangle_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
PikaToolRectangle *rectangle = PIKA_TOOL_RECTANGLE (object);
PikaToolRectanglePrivate *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
pika_tool_rectangle_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
PikaToolRectangle *rectangle = PIKA_TOOL_RECTANGLE (object);
PikaToolRectanglePrivate *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
pika_tool_rectangle_notify (GObject *object,
GParamSpec *pspec)
{
PikaToolRectangle *rectangle = PIKA_TOOL_RECTANGLE (object);
PikaToolRectanglePrivate *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"))
{
pika_tool_rectangle_update_int_rect (rectangle);
pika_tool_rectangle_recalculate_center_xy (rectangle);
pika_tool_rectangle_update_options (rectangle);
}
else if (! strcmp (pspec->name, "x") &&
! PIXEL_FEQUAL (private->x1, private->x))
{
pika_tool_rectangle_synthesize_motion (rectangle,
PIKA_TOOL_RECTANGLE_MOVING,
private->x,
private->y1);
}
else if (! strcmp (pspec->name, "y") &&
! PIXEL_FEQUAL (private->y1, private->y))
{
pika_tool_rectangle_synthesize_motion (rectangle,
PIKA_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;
}
pika_tool_rectangle_synthesize_motion (rectangle,
PIKA_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;
}
pika_tool_rectangle_synthesize_motion (rectangle,
PIKA_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;
pika_tool_rectangle_synthesize_motion (rectangle,
PIKA_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;
pika_tool_rectangle_synthesize_motion (rectangle,
PIKA_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT,
new_x2,
new_y2);
}
}
}
static void
pika_tool_rectangle_changed (PikaToolWidget *widget)
{
PikaToolRectangle *rectangle = PIKA_TOOL_RECTANGLE (widget);
PikaToolRectanglePrivate *private = rectangle->private;
PikaDisplayShell *shell = pika_tool_widget_get_shell (widget);
gdouble x1, y1, x2, y2;
gint handle_width;
gint handle_height;
gint i;
pika_tool_rectangle_update_handle_sizes (rectangle);
pika_tool_rectangle_get_public_rect (rectangle, &x1, &y1, &x2, &y2);
pika_canvas_rectangle_guides_set (private->guides,
x1, y1,
x2 - x1,
y2 - y1,
private->guide, 4);
pika_canvas_rectangle_set (private->rectangle,
x1, y1,
x2 - x1,
y2 - y1);
if (private->draw_ellipse)
{
pika_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);
pika_canvas_item_set_visible (private->ellipse, TRUE);
}
else
{
pika_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));
pika_canvas_arc_set (private->corners[0],
x1 + radius,
y1 + radius,
radius, radius,
G_PI / 2.0, G_PI / 2.0);
pika_canvas_arc_set (private->corners[1],
x2 - radius,
y1 + radius,
radius, radius,
0.0, G_PI / 2.0);
pika_canvas_arc_set (private->corners[2],
x1 + radius,
y2 - radius,
radius, radius,
G_PI, G_PI / 2.0);
pika_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++)
pika_canvas_item_set_visible (private->corners[i], TRUE);
}
else
{
for (i = 0; i < 4; i++)
pika_canvas_item_set_visible (private->corners[i], FALSE);
}
pika_canvas_item_set_visible (private->center, FALSE);
for (i = 0; i < 4; i++)
{
pika_canvas_item_set_visible (private->creating_corners[i], FALSE);
pika_canvas_item_set_highlight (private->creating_corners[i], FALSE);
}
for (i = PIKA_TOOL_RECTANGLE_RESIZING_UPPER_LEFT;
i <= PIKA_TOOL_RECTANGLE_RESIZING_BOTTOM;
i++)
{
pika_canvas_item_set_visible (private->handles[i], FALSE);
pika_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 PIKA_TOOL_RECTANGLE_MOVING:
if (private->rect_adjusting)
{
/* Mark the center because we snap to it */
pika_canvas_handle_set_position (private->center,
(x1 + x2) / 2.0,
(y1 + y2) / 2.0);
pika_canvas_item_set_visible (private->center, TRUE);
break;
}
/* else fallthrough */
case PIKA_TOOL_RECTANGLE_DEAD:
case PIKA_TOOL_RECTANGLE_CREATING:
case PIKA_TOOL_RECTANGLE_AUTO_SHRINK:
for (i = 0; i < 4; i++)
{
pika_canvas_corner_set (private->creating_corners[i],
x1, y1, x2 - x1, y2 - y1,
handle_width, handle_height,
private->narrow_mode);
pika_canvas_item_set_visible (private->creating_corners[i], TRUE);
}
break;
case PIKA_TOOL_RECTANGLE_RESIZING_TOP:
case PIKA_TOOL_RECTANGLE_RESIZING_BOTTOM:
handle_width = private->top_and_bottom_handle_w;
break;
case PIKA_TOOL_RECTANGLE_RESIZING_LEFT:
case PIKA_TOOL_RECTANGLE_RESIZING_RIGHT:
handle_height = private->left_and_right_handle_h;
break;
default:
break;
}
if (handle_width >= 3 &&
handle_height >= 3 &&
private->function >= PIKA_TOOL_RECTANGLE_RESIZING_UPPER_LEFT &&
private->function <= PIKA_TOOL_RECTANGLE_RESIZING_BOTTOM)
{
PikaCanvasItem *corner;
if (private->rect_adjusting)
corner = private->handles[private->function];
else
corner = private->highlight_handles[private->function];
pika_canvas_corner_set (corner,
x1, y1, x2 - x1, y2 - y1,
handle_width, handle_height,
private->narrow_mode);
pika_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;
pika_display_shell_set_highlight (shell, &rect, private->highlight_opacity);
}
else
{
pika_display_shell_set_highlight (shell, NULL, 0.0);
}
}
gint
pika_tool_rectangle_button_press (PikaToolWidget *widget,
const PikaCoords *coords,
guint32 time,
GdkModifierType state,
PikaButtonPressType press_type)
{
PikaToolRectangle *rectangle = PIKA_TOOL_RECTANGLE (widget);
PikaToolRectanglePrivate *private = rectangle->private;
gdouble snapped_x, snapped_y;
gint snap_x, snap_y;
/* prevent the latching of toggled modifiers (Ctrl and Shift) when the
selection is cancelled whilst one or both of these modifiers are
being pressed (see issue #7954 and MR !779) */
private->fixed_center_copy = private->fixed_center;
private->fixed_rule_active_copy = private->fixed_rule_active;
private->modifier_toggle_allowed = TRUE;
/* save existing shape in case of cancellation */
private->saved_x1 = private->x1;
private->saved_y1 = private->y1;
private->saved_x2 = private->x2;
private->saved_y2 = private->y2;
pika_tool_rectangle_setup_snap_offsets (rectangle, coords);
pika_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 == PIKA_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;
pika_tool_rectangle_get_other_side_coord (rectangle,
&private->other_side_x,
&private->other_side_y);
}
pika_tool_rectangle_update_int_rect (rectangle);
/* Is the rectangle being rubber-banded? */
private->rect_adjusting = pika_tool_rectangle_rect_adjusting_func (rectangle);
pika_tool_rectangle_changed (widget);
pika_tool_rectangle_update_status (rectangle);
return 1;
}
void
pika_tool_rectangle_button_release (PikaToolWidget *widget,
const PikaCoords *coords,
guint32 time,
GdkModifierType state,
PikaButtonReleaseType release_type)
{
PikaToolRectangle *rectangle = PIKA_TOOL_RECTANGLE (widget);
PikaToolRectanglePrivate *private = rectangle->private;
gint response = 0;
pika_tool_widget_set_status (widget, NULL);
/* On button release, we are not rubber-banding the rectangle any longer. */
private->rect_adjusting = FALSE;
pika_tool_widget_set_snap_offsets (widget, 0, 0, 0, 0);
/* prevent the latching of toggled modifiers (Ctrl and Shift) when the
selection is cancelled whilst one or both of these modifiers are
being pressed (see issue #7954 and MR !779) */
private->modifier_toggle_allowed = FALSE;
g_object_set (rectangle,
"fixed-center", private->fixed_center_copy,
NULL);
g_object_set (rectangle,
"fixed-rule-active", private->fixed_rule_active_copy,
NULL);
switch (release_type)
{
case PIKA_BUTTON_RELEASE_NO_MOTION:
/* If the first created rectangle was not expanded, halt the
* tool...
*/
if (pika_tool_rectangle_rectangle_is_first (rectangle))
{
response = PIKA_TOOL_WIDGET_RESPONSE_CANCEL;
break;
}
/* ...else fallthrough and treat a long click without movement
* like a normal change
*/
case PIKA_BUTTON_RELEASE_NORMAL:
/* If a normal click-drag-release actually created a rectangle
* with content...
*/
if (private->x1 != private->x2 &&
private->y1 != private->y2)
{
pika_tool_rectangle_change_complete (rectangle);
break;
}
/* ...else fallthrough and undo the operation, we can't have
* zero-extent rectangles
*/
case PIKA_BUTTON_RELEASE_CANCEL:
private->x1 = private->saved_x1;
private->y1 = private->saved_y1;
private->x2 = private->saved_x2;
private->y2 = private->saved_y2;
pika_tool_rectangle_update_int_rect (rectangle);
/* If the first created rectangle was canceled, halt the tool */
if (pika_tool_rectangle_rectangle_is_first (rectangle))
response = PIKA_TOOL_WIDGET_RESPONSE_CANCEL;
break;
case PIKA_BUTTON_RELEASE_CLICK:
/* When a dead area is clicked, don't execute. */
if (private->function != PIKA_TOOL_RECTANGLE_DEAD)
response = PIKA_TOOL_WIDGET_RESPONSE_CONFIRM;
break;
}
/* We must update this. */
pika_tool_rectangle_recalculate_center_xy (rectangle);
pika_tool_rectangle_update_options (rectangle);
pika_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)
pika_tool_widget_response (widget, response);
}
void
pika_tool_rectangle_motion (PikaToolWidget *widget,
const PikaCoords *coords,
guint32 time,
GdkModifierType state)
{
PikaToolRectangle *rectangle = PIKA_TOOL_RECTANGLE (widget);
PikaToolRectanglePrivate *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 == PIKA_TOOL_RECTANGLE_EXECUTING ||
private->function == PIKA_TOOL_RECTANGLE_DEAD)
return;
/* Handle snapping. */
pika_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: */
pika_tool_rectangle_update_with_coord (rectangle, snapped_x, snapped_y);
pika_tool_rectangle_update_status (rectangle);
if (private->function == PIKA_TOOL_RECTANGLE_CREATING)
{
PikaRectangleFunction function = PIKA_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 ?
PIKA_TOOL_RECTANGLE_RESIZING_UPPER_LEFT :
PIKA_TOOL_RECTANGLE_RESIZING_LOWER_LEFT);
}
else if (dx > 0)
{
function = (dy < 0 ?
PIKA_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT :
PIKA_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT);
}
else if (dy < 0)
{
function = (dx < 0 ?
PIKA_TOOL_RECTANGLE_RESIZING_UPPER_LEFT :
PIKA_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT);
}
else if (dy > 0)
{
function = (dx < 0 ?
PIKA_TOOL_RECTANGLE_RESIZING_LOWER_LEFT :
PIKA_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT);
}
pika_tool_rectangle_set_function (rectangle, function);
if (private->fixed_rule_active &&
private->fixed_rule == PIKA_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. */
pika_tool_rectangle_update_with_coord (rectangle,
snapped_x,
snapped_y);
pika_tool_widget_set_snap_offsets (widget,
-(private->x2 - private->x1) / 2,
-(private->y2 - private->y1) / 2,
private->x2 - private->x1,
private->y2 - private->y1);
pika_tool_rectangle_set_function (rectangle,
PIKA_TOOL_RECTANGLE_MOVING);
}
}
pika_tool_rectangle_update_options (rectangle);
private->lastx = snapped_x;
private->lasty = snapped_y;
}
PikaHit
pika_tool_rectangle_hit (PikaToolWidget *widget,
const PikaCoords *coords,
GdkModifierType state,
gboolean proximity)
{
PikaToolRectangle *rectangle = PIKA_TOOL_RECTANGLE (widget);
PikaToolRectanglePrivate *private = rectangle->private;
PikaRectangleFunction function;
if (private->suppress_updates)
{
function = pika_tool_rectangle_get_function (rectangle);
}
else
{
function = pika_tool_rectangle_calc_function (rectangle,
coords, proximity);
}
switch (function)
{
case PIKA_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
case PIKA_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
case PIKA_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
case PIKA_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
case PIKA_TOOL_RECTANGLE_RESIZING_LEFT:
case PIKA_TOOL_RECTANGLE_RESIZING_RIGHT:
case PIKA_TOOL_RECTANGLE_RESIZING_TOP:
case PIKA_TOOL_RECTANGLE_RESIZING_BOTTOM:
return PIKA_HIT_DIRECT;
case PIKA_TOOL_RECTANGLE_CREATING:
case PIKA_TOOL_RECTANGLE_MOVING:
return PIKA_HIT_INDIRECT;
case PIKA_TOOL_RECTANGLE_DEAD:
case PIKA_TOOL_RECTANGLE_AUTO_SHRINK:
case PIKA_TOOL_RECTANGLE_EXECUTING:
default:
return PIKA_HIT_NONE;
}
}
void
pika_tool_rectangle_hover (PikaToolWidget *widget,
const PikaCoords *coords,
GdkModifierType state,
gboolean proximity)
{
PikaToolRectangle *rectangle = PIKA_TOOL_RECTANGLE (widget);
PikaToolRectanglePrivate *private = rectangle->private;
PikaRectangleFunction function;
if (private->suppress_updates)
{
private->suppress_updates--;
return;
}
function = pika_tool_rectangle_calc_function (rectangle, coords, proximity);
pika_tool_rectangle_set_function (rectangle, function);
}
static void
pika_tool_rectangle_leave_notify (PikaToolWidget *widget)
{
PikaToolRectangle *rectangle = PIKA_TOOL_RECTANGLE (widget);
pika_tool_rectangle_set_function (rectangle, PIKA_TOOL_RECTANGLE_DEAD);
PIKA_TOOL_WIDGET_CLASS (parent_class)->leave_notify (widget);
}
static gboolean
pika_tool_rectangle_key_press (PikaToolWidget *widget,
GdkEventKey *kevent)
{
PikaToolRectangle *rectangle = PIKA_TOOL_RECTANGLE (widget);
PikaToolRectanglePrivate *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 PIKA_TOOL_WIDGET_CLASS (parent_class)->key_press (widget, kevent);
}
/* If the shift key is down, move by an accelerated increment */
if (kevent->state & pika_get_extend_selection_mask ())
{
dx *= ARROW_VELOCITY;
dy *= ARROW_VELOCITY;
}
pika_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 PIKA_TOOL_RECTANGLE_MOVING:
case PIKA_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 PIKA_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 PIKA_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 PIKA_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 PIKA_TOOL_RECTANGLE_RESIZING_LEFT:
new_x = private->x1 + dx;
private->lastx = new_x;
break;
case PIKA_TOOL_RECTANGLE_RESIZING_RIGHT:
new_x = private->x2 + dx;
private->lastx = new_x;
break;
case PIKA_TOOL_RECTANGLE_RESIZING_TOP:
new_y = private->y1 + dy;
private->lasty = new_y;
break;
case PIKA_TOOL_RECTANGLE_RESIZING_BOTTOM:
new_y = private->y2 + dy;
private->lasty = new_y;
break;
default:
return TRUE;
}
pika_tool_rectangle_update_with_coord (rectangle, new_x, new_y);
pika_tool_rectangle_recalculate_center_xy (rectangle);
pika_tool_rectangle_update_options (rectangle);
pika_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
pika_tool_rectangle_motion_modifier (PikaToolWidget *widget,
GdkModifierType key,
gboolean press,
GdkModifierType state)
{
PikaToolRectangle *rectangle = PIKA_TOOL_RECTANGLE (widget);
PikaToolRectanglePrivate *private = rectangle->private;
gboolean button1_down;
button1_down = (state & GDK_BUTTON1_MASK);
if (key == pika_get_extend_selection_mask ())
{
#if 0
/* Here we want to handle manually when to update the rectangle, so we
* don't want pika_tool_rectangle_options_notify to do anything.
*/
g_signal_handlers_block_by_func (options,
pika_tool_rectangle_options_notify,
rectangle);
#endif
if (private->modifier_toggle_allowed)
g_object_set (rectangle,
"fixed-rule-active", ! private->fixed_rule_active,
NULL);
#if 0
g_signal_handlers_unblock_by_func (options,
pika_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 */
pika_tool_rectangle_set_other_side_coord (rectangle,
private->other_side_x,
private->other_side_y);
}
pika_tool_rectangle_update_with_coord (rectangle,
private->lastx,
private->lasty);
}
}
if (key == pika_get_toggle_behavior_mask ())
{
if (private->modifier_toggle_allowed)
g_object_set (rectangle,
"fixed-center", ! private->fixed_center,
NULL);
if (private->fixed_center)
{
pika_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)
{
pika_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.
*/
pika_tool_rectangle_set_other_side_coord (rectangle,
private->other_side_x,
private->other_side_y);
}
}
pika_tool_rectangle_update_options (rectangle);
}
static gboolean
pika_tool_rectangle_get_cursor (PikaToolWidget *widget,
const PikaCoords *coords,
GdkModifierType state,
PikaCursorType *cursor,
PikaToolCursorType *tool_cursor,
PikaCursorModifier *modifier)
{
PikaToolRectangle *rectangle = PIKA_TOOL_RECTANGLE (widget);
PikaToolRectanglePrivate *private = rectangle->private;
switch (private->function)
{
case PIKA_TOOL_RECTANGLE_CREATING:
*cursor = PIKA_CURSOR_CROSSHAIR_SMALL;
break;
case PIKA_TOOL_RECTANGLE_MOVING:
*cursor = PIKA_CURSOR_MOVE;
*modifier = PIKA_CURSOR_MODIFIER_MOVE;
break;
case PIKA_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
*cursor = PIKA_CURSOR_CORNER_TOP_LEFT;
break;
case PIKA_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
*cursor = PIKA_CURSOR_CORNER_TOP_RIGHT;
break;
case PIKA_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
*cursor = PIKA_CURSOR_CORNER_BOTTOM_LEFT;
break;
case PIKA_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
*cursor = PIKA_CURSOR_CORNER_BOTTOM_RIGHT;
break;
case PIKA_TOOL_RECTANGLE_RESIZING_LEFT:
*cursor = PIKA_CURSOR_SIDE_LEFT;
break;
case PIKA_TOOL_RECTANGLE_RESIZING_RIGHT:
*cursor = PIKA_CURSOR_SIDE_RIGHT;
break;
case PIKA_TOOL_RECTANGLE_RESIZING_TOP:
*cursor = PIKA_CURSOR_SIDE_TOP;
break;
case PIKA_TOOL_RECTANGLE_RESIZING_BOTTOM:
*cursor = PIKA_CURSOR_SIDE_BOTTOM;
break;
default:
return FALSE;
}
return TRUE;
}
static void
pika_tool_rectangle_change_complete (PikaToolRectangle *rectangle)
{
g_signal_emit (rectangle, rectangle_signals[CHANGE_COMPLETE], 0);
}
static void
pika_tool_rectangle_update_options (PikaToolRectangle *rectangle)
{
PikaToolRectanglePrivate *private = rectangle->private;
gdouble x1 = 0;
gdouble y1 = 0;
gdouble x2 = 0;
gdouble y2 = 0;
pika_tool_rectangle_get_public_rect (rectangle, &x1, &y1, &x2, &y2);
#if 0
g_signal_handlers_block_by_func (options,
pika_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,
pika_tool_rectangle_options_notify,
rect_tool);
#endif
}
static void
pika_tool_rectangle_update_handle_sizes (PikaToolRectangle *rectangle)
{
PikaToolRectanglePrivate *private = rectangle->private;
PikaDisplayShell *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 = pika_tool_widget_get_shell (PIKA_TOOL_WIDGET (rectangle));
pika_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;
pika_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.
*/
pika_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
pika_tool_rectangle_update_status (PikaToolRectangle *rectangle)
{
PikaToolRectanglePrivate *private = rectangle->private;
gdouble x1 = 0;
gdouble y1 = 0;
gdouble x2 = 0;
gdouble y2 = 0;
pika_tool_rectangle_get_public_rect (rectangle, &x1, &y1, &x2, &y2);
if (private->function == PIKA_TOOL_RECTANGLE_MOVING)
{
pika_tool_widget_set_status_coords (PIKA_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);
}
pika_tool_widget_set_status_coords (PIKA_TOOL_WIDGET (rectangle),
private->status_title,
width, " × ", height,
aspect_text);
g_free (aspect_text);
}
}
static void
pika_tool_rectangle_synthesize_motion (PikaToolRectangle *rectangle,
gint function,
gdouble new_x,
gdouble new_y)
{
PikaToolRectanglePrivate *private = rectangle->private;
PikaRectangleFunction 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;
pika_tool_rectangle_set_function (rectangle, function);
pika_tool_rectangle_update_with_coord (rectangle, new_x, new_y);
/* We must update this. */
pika_tool_rectangle_recalculate_center_xy (rectangle);
pika_tool_rectangle_update_options (rectangle);
pika_tool_rectangle_set_function (rectangle, old_function);
pika_tool_rectangle_change_complete (rectangle);
}
static void
swap_doubles (gdouble *i,
gdouble *j)
{
gdouble tmp;
tmp = *i;
*i = *j;
*j = tmp;
}
static PikaRectangleFunction
pika_tool_rectangle_calc_function (PikaToolRectangle *rectangle,
const PikaCoords *coords,
gboolean proximity)
{
if (! proximity)
{
return PIKA_TOOL_RECTANGLE_DEAD;
}
else if (pika_tool_rectangle_coord_outside (rectangle, coords))
{
/* The cursor is outside of the rectangle, clicking should
* create a new rectangle.
*/
return PIKA_TOOL_RECTANGLE_CREATING;
}
else if (pika_tool_rectangle_coord_on_handle (rectangle,
coords,
PIKA_HANDLE_ANCHOR_NORTH_WEST))
{
return PIKA_TOOL_RECTANGLE_RESIZING_UPPER_LEFT;
}
else if (pika_tool_rectangle_coord_on_handle (rectangle,
coords,
PIKA_HANDLE_ANCHOR_SOUTH_EAST))
{
return PIKA_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT;
}
else if (pika_tool_rectangle_coord_on_handle (rectangle,
coords,
PIKA_HANDLE_ANCHOR_NORTH_EAST))
{
return PIKA_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT;
}
else if (pika_tool_rectangle_coord_on_handle (rectangle,
coords,
PIKA_HANDLE_ANCHOR_SOUTH_WEST))
{
return PIKA_TOOL_RECTANGLE_RESIZING_LOWER_LEFT;
}
else if (pika_tool_rectangle_coord_on_handle (rectangle,
coords,
PIKA_HANDLE_ANCHOR_WEST))
{
return PIKA_TOOL_RECTANGLE_RESIZING_LEFT;
}
else if (pika_tool_rectangle_coord_on_handle (rectangle,
coords,
PIKA_HANDLE_ANCHOR_EAST))
{
return PIKA_TOOL_RECTANGLE_RESIZING_RIGHT;
}
else if (pika_tool_rectangle_coord_on_handle (rectangle,
coords,
PIKA_HANDLE_ANCHOR_NORTH))
{
return PIKA_TOOL_RECTANGLE_RESIZING_TOP;
}
else if (pika_tool_rectangle_coord_on_handle (rectangle,
coords,
PIKA_HANDLE_ANCHOR_SOUTH))
{
return PIKA_TOOL_RECTANGLE_RESIZING_BOTTOM;
}
else if (pika_tool_rectangle_coord_on_handle (rectangle,
coords,
PIKA_HANDLE_ANCHOR_CENTER))
{
return PIKA_TOOL_RECTANGLE_MOVING;
}
else
{
return PIKA_TOOL_RECTANGLE_DEAD;
}
}
/* pika_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
pika_tool_rectangle_check_function (PikaToolRectangle *rectangle)
{
PikaToolRectanglePrivate *private = rectangle->private;
PikaRectangleFunction function = private->function;
if (private->x2 < private->x1)
{
swap_doubles (&private->x1, &private->x2);
switch (function)
{
case PIKA_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
function = PIKA_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT;
break;
case PIKA_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
function = PIKA_TOOL_RECTANGLE_RESIZING_UPPER_LEFT;
break;
case PIKA_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
function = PIKA_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT;
break;
case PIKA_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
function = PIKA_TOOL_RECTANGLE_RESIZING_LOWER_LEFT;
break;
case PIKA_TOOL_RECTANGLE_RESIZING_LEFT:
function = PIKA_TOOL_RECTANGLE_RESIZING_RIGHT;
break;
case PIKA_TOOL_RECTANGLE_RESIZING_RIGHT:
function = PIKA_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 PIKA_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
function = PIKA_TOOL_RECTANGLE_RESIZING_LOWER_LEFT;
break;
case PIKA_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
function = PIKA_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT;
break;
case PIKA_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
function = PIKA_TOOL_RECTANGLE_RESIZING_UPPER_LEFT;
break;
case PIKA_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
function = PIKA_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT;
break;
case PIKA_TOOL_RECTANGLE_RESIZING_TOP:
function = PIKA_TOOL_RECTANGLE_RESIZING_BOTTOM;
break;
case PIKA_TOOL_RECTANGLE_RESIZING_BOTTOM:
function = PIKA_TOOL_RECTANGLE_RESIZING_TOP;
break;
default:
break;
}
}
pika_tool_rectangle_set_function (rectangle, function);
}
/**
* pika_tool_rectangle_coord_outside:
*
* Returns: %TRUE if the coord is outside the rectangle bounds
* including any outside handles.
*/
static gboolean
pika_tool_rectangle_coord_outside (PikaToolRectangle *rectangle,
const PikaCoords *coord)
{
PikaToolRectanglePrivate *private = rectangle->private;
PikaDisplayShell *shell;
gboolean narrow_mode = private->narrow_mode;
gdouble x1, y1, x2, y2;
gdouble x1_b, y1_b, x2_b, y2_b;
shell = pika_tool_widget_get_shell (PIKA_TOOL_WIDGET (rectangle));
pika_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);
}
/**
* pika_tool_rectangle_coord_on_handle:
*
* Returns: %TRUE if the coord is on the handle that corresponds to
* @anchor.
*/
static gboolean
pika_tool_rectangle_coord_on_handle (PikaToolRectangle *rectangle,
const PikaCoords *coords,
PikaHandleAnchor anchor)
{
PikaToolRectanglePrivate *private = rectangle->private;
PikaDisplayShell *shell;
gdouble x1 = 0;
gdouble y1 = 0;
gdouble x2 = 0;
gdouble y2 = 0;
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 = pika_tool_widget_get_shell (PIKA_TOOL_WIDGET (rectangle));
pika_tool_rectangle_get_public_rect (rectangle, &x1, &y1, &x2, &y2);
rect_w = x2 - x1;
rect_h = y2 - y1;
switch (anchor)
{
case PIKA_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 PIKA_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 PIKA_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 PIKA_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 PIKA_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 PIKA_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 PIKA_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 PIKA_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 PIKA_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 pika_canvas_item_on_handle (private->rectangle,
coords->x, coords->y,
PIKA_HANDLE_SQUARE,
handle_x, handle_y,
handle_width, handle_height,
anchor);
}
static PikaHandleAnchor
pika_tool_rectangle_get_anchor (PikaRectangleFunction function)
{
switch (function)
{
case PIKA_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
return PIKA_HANDLE_ANCHOR_NORTH_WEST;
case PIKA_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
return PIKA_HANDLE_ANCHOR_NORTH_EAST;
case PIKA_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
return PIKA_HANDLE_ANCHOR_SOUTH_WEST;
case PIKA_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
return PIKA_HANDLE_ANCHOR_SOUTH_EAST;
case PIKA_TOOL_RECTANGLE_RESIZING_LEFT:
return PIKA_HANDLE_ANCHOR_WEST;
case PIKA_TOOL_RECTANGLE_RESIZING_RIGHT:
return PIKA_HANDLE_ANCHOR_EAST;
case PIKA_TOOL_RECTANGLE_RESIZING_TOP:
return PIKA_HANDLE_ANCHOR_NORTH;
case PIKA_TOOL_RECTANGLE_RESIZING_BOTTOM:
return PIKA_HANDLE_ANCHOR_SOUTH;
default:
return PIKA_HANDLE_ANCHOR_CENTER;
}
}
static gboolean
pika_tool_rectangle_rect_rubber_banding_func (PikaToolRectangle *rectangle)
{
PikaToolRectanglePrivate *private = rectangle->private;
switch (private->function)
{
case PIKA_TOOL_RECTANGLE_CREATING:
case PIKA_TOOL_RECTANGLE_RESIZING_LEFT:
case PIKA_TOOL_RECTANGLE_RESIZING_RIGHT:
case PIKA_TOOL_RECTANGLE_RESIZING_TOP:
case PIKA_TOOL_RECTANGLE_RESIZING_BOTTOM:
case PIKA_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
case PIKA_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
case PIKA_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
case PIKA_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
case PIKA_TOOL_RECTANGLE_AUTO_SHRINK:
return TRUE;
case PIKA_TOOL_RECTANGLE_MOVING:
case PIKA_TOOL_RECTANGLE_DEAD:
default:
break;
}
return FALSE;
}
/**
* pika_tool_rectangle_rect_adjusting_func:
* @rectangle:
*
* Returns: %TRUE if the current function is a rectangle adjusting
* function.
*/
static gboolean
pika_tool_rectangle_rect_adjusting_func (PikaToolRectangle *rectangle)
{
PikaToolRectanglePrivate *private = rectangle->private;
return (pika_tool_rectangle_rect_rubber_banding_func (rectangle) ||
private->function == PIKA_TOOL_RECTANGLE_MOVING);
}
/**
* pika_tool_rectangle_get_other_side:
* @rectangle: A #PikaToolRectangle.
* @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
pika_tool_rectangle_get_other_side (PikaToolRectangle *rectangle,
gdouble **other_x,
gdouble **other_y)
{
PikaToolRectanglePrivate *private = rectangle->private;
switch (private->function)
{
case PIKA_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
case PIKA_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
case PIKA_TOOL_RECTANGLE_RESIZING_RIGHT:
*other_x = &private->x1;
break;
case PIKA_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
case PIKA_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
case PIKA_TOOL_RECTANGLE_RESIZING_LEFT:
*other_x = &private->x2;
break;
case PIKA_TOOL_RECTANGLE_RESIZING_TOP:
case PIKA_TOOL_RECTANGLE_RESIZING_BOTTOM:
default:
*other_x = NULL;
break;
}
switch (private->function)
{
case PIKA_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
case PIKA_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
case PIKA_TOOL_RECTANGLE_RESIZING_BOTTOM:
*other_y = &private->y1;
break;
case PIKA_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
case PIKA_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
case PIKA_TOOL_RECTANGLE_RESIZING_TOP:
*other_y = &private->y2;
break;
case PIKA_TOOL_RECTANGLE_RESIZING_LEFT:
case PIKA_TOOL_RECTANGLE_RESIZING_RIGHT:
default:
*other_y = NULL;
break;
}
}
static void
pika_tool_rectangle_get_other_side_coord (PikaToolRectangle *rectangle,
gdouble *other_side_x,
gdouble *other_side_y)
{
gdouble *other_x = NULL;
gdouble *other_y = NULL;
pika_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
pika_tool_rectangle_set_other_side_coord (PikaToolRectangle *rectangle,
gdouble other_side_x,
gdouble other_side_y)
{
gdouble *other_x = NULL;
gdouble *other_y = NULL;
pika_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;
pika_tool_rectangle_check_function (rectangle);
pika_tool_rectangle_update_int_rect (rectangle);
}
/**
* pika_tool_rectangle_apply_coord:
* @param: A #PikaToolRectangle.
* @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
pika_tool_rectangle_apply_coord (PikaToolRectangle *rectangle,
gdouble coord_x,
gdouble coord_y)
{
PikaToolRectanglePrivate *private = rectangle->private;
if (private->function == PIKA_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 PIKA_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
case PIKA_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
case PIKA_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 PIKA_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
case PIKA_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
case PIKA_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 PIKA_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
case PIKA_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
case PIKA_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 PIKA_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
case PIKA_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
case PIKA_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
pika_tool_rectangle_setup_snap_offsets (PikaToolRectangle *rectangle,
const PikaCoords *coords)
{
PikaToolWidget *widget = PIKA_TOOL_WIDGET (rectangle);
PikaToolRectanglePrivate *private = rectangle->private;
gdouble x1 = 0;
gdouble y1 = 0;
gdouble x2 = 0;
gdouble y2 = 0;
gdouble coord_x, coord_y;
pika_tool_rectangle_get_public_rect (rectangle, &x1, &y1, &x2, &y2);
pika_tool_rectangle_adjust_coord (rectangle,
coords->x, coords->y,
&coord_x, &coord_y);
switch (private->function)
{
case PIKA_TOOL_RECTANGLE_CREATING:
pika_tool_widget_set_snap_offsets (widget, 0, 0, 0, 0);
break;
case PIKA_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
pika_tool_widget_set_snap_offsets (widget,
x1 - coord_x,
y1 - coord_y,
0, 0);
break;
case PIKA_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
pika_tool_widget_set_snap_offsets (widget,
x2 - coord_x,
y1 - coord_y,
0, 0);
break;
case PIKA_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
pika_tool_widget_set_snap_offsets (widget,
x1 - coord_x,
y2 - coord_y,
0, 0);
break;
case PIKA_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
pika_tool_widget_set_snap_offsets (widget,
x2 - coord_x,
y2 - coord_y,
0, 0);
break;
case PIKA_TOOL_RECTANGLE_RESIZING_LEFT:
pika_tool_widget_set_snap_offsets (widget,
x1 - coord_x, 0,
0, 0);
break;
case PIKA_TOOL_RECTANGLE_RESIZING_RIGHT:
pika_tool_widget_set_snap_offsets (widget,
x2 - coord_x, 0,
0, 0);
break;
case PIKA_TOOL_RECTANGLE_RESIZING_TOP:
pika_tool_widget_set_snap_offsets (widget,
0, y1 - coord_y,
0, 0);
break;
case PIKA_TOOL_RECTANGLE_RESIZING_BOTTOM:
pika_tool_widget_set_snap_offsets (widget,
0, y2 - coord_y,
0, 0);
break;
case PIKA_TOOL_RECTANGLE_MOVING:
pika_tool_widget_set_snap_offsets (widget,
x1 - coord_x,
y1 - coord_y,
x2 - x1,
y2 - y1);
break;
default:
break;
}
}
/**
* pika_tool_rectangle_clamp:
* @rectangle: A #PikaToolRectangle.
* @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
pika_tool_rectangle_clamp (PikaToolRectangle *rectangle,
ClampedSide *clamped_sides,
PikaRectangleConstraint constraint,
gboolean symmetrically)
{
pika_tool_rectangle_clamp_width (rectangle,
clamped_sides,
constraint,
symmetrically);
pika_tool_rectangle_clamp_height (rectangle,
clamped_sides,
constraint,
symmetrically);
}
/**
* pika_tool_rectangle_clamp_width:
* @rectangle: A #PikaToolRectangle.
* @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
pika_tool_rectangle_clamp_width (PikaToolRectangle *rectangle,
ClampedSide *clamped_sides,
PikaRectangleConstraint constraint,
gboolean symmetrically)
{
PikaToolRectanglePrivate *private = rectangle->private;
gint min_x;
gint max_x;
if (constraint == PIKA_RECTANGLE_CONSTRAIN_NONE)
return;
pika_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;
}
}
/**
* pika_tool_rectangle_clamp_height:
* @rectangle: A #PikaToolRectangle.
* @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
pika_tool_rectangle_clamp_height (PikaToolRectangle *rectangle,
ClampedSide *clamped_sides,
PikaRectangleConstraint constraint,
gboolean symmetrically)
{
PikaToolRectanglePrivate *private = rectangle->private;
gint min_y;
gint max_y;
if (constraint == PIKA_RECTANGLE_CONSTRAIN_NONE)
return;
pika_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;
}
}
/**
* pika_tool_rectangle_keep_inside:
* @rectangle: A #PikaToolRectangle.
*
* 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
pika_tool_rectangle_keep_inside (PikaToolRectangle *rectangle,
PikaRectangleConstraint constraint)
{
pika_tool_rectangle_keep_inside_horizontally (rectangle, constraint);
pika_tool_rectangle_keep_inside_vertically (rectangle, constraint);
}
/**
* pika_tool_rectangle_keep_inside_horizontally:
* @rectangle: A #PikaToolRectangle.
* @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
pika_tool_rectangle_keep_inside_horizontally (PikaToolRectangle *rectangle,
PikaRectangleConstraint constraint)
{
PikaToolRectanglePrivate *private = rectangle->private;
gint min_x;
gint max_x;
if (constraint == PIKA_RECTANGLE_CONSTRAIN_NONE)
return;
pika_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;
}
}
}
/**
* pika_tool_rectangle_keep_inside_vertically:
* @rectangle: A #PikaToolRectangle.
* @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
pika_tool_rectangle_keep_inside_vertically (PikaToolRectangle *rectangle,
PikaRectangleConstraint constraint)
{
PikaToolRectanglePrivate *private = rectangle->private;
gint min_y;
gint max_y;
if (constraint == PIKA_RECTANGLE_CONSTRAIN_NONE)
return;
pika_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;
}
}
}
/**
* pika_tool_rectangle_apply_fixed_width:
* @rectangle: A #PikaToolRectangle.
* @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
pika_tool_rectangle_apply_fixed_width (PikaToolRectangle *rectangle,
PikaRectangleConstraint constraint,
gdouble width)
{
PikaToolRectanglePrivate *private = rectangle->private;
switch (private->function)
{
case PIKA_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
case PIKA_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
case PIKA_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 PIKA_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
case PIKA_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
case PIKA_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.
*/
pika_tool_rectangle_keep_inside_horizontally (rectangle, constraint);
}
/**
* pika_tool_rectangle_apply_fixed_height:
* @rectangle: A #PikaToolRectangle.
* @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
pika_tool_rectangle_apply_fixed_height (PikaToolRectangle *rectangle,
PikaRectangleConstraint constraint,
gdouble height)
{
PikaToolRectanglePrivate *private = rectangle->private;
switch (private->function)
{
case PIKA_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
case PIKA_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
case PIKA_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 PIKA_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
case PIKA_TOOL_RECTANGLE_RESIZING_LOWER_RIGHT:
case PIKA_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.
*/
pika_tool_rectangle_keep_inside_vertically (rectangle, constraint);
}
/**
* pika_tool_rectangle_apply_aspect:
* @rectangle: A #PikaToolRectangle.
* @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
pika_tool_rectangle_apply_aspect (PikaToolRectangle *rectangle,
gdouble aspect,
gint clamped_sides)
{
PikaToolRectanglePrivate *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 PIKA_TOOL_RECTANGLE_RESIZING_LEFT:
case PIKA_TOOL_RECTANGLE_RESIZING_RIGHT:
case PIKA_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
case PIKA_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
case PIKA_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
case PIKA_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 PIKA_TOOL_RECTANGLE_RESIZING_TOP:
case PIKA_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 PIKA_TOOL_RECTANGLE_RESIZING_TOP:
case PIKA_TOOL_RECTANGLE_RESIZING_BOTTOM:
case PIKA_TOOL_RECTANGLE_RESIZING_UPPER_LEFT:
case PIKA_TOOL_RECTANGLE_RESIZING_UPPER_RIGHT:
case PIKA_TOOL_RECTANGLE_RESIZING_LOWER_LEFT:
case PIKA_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 PIKA_TOOL_RECTANGLE_RESIZING_LEFT:
case PIKA_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 PIKA_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 PIKA_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 PIKA_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 PIKA_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 PIKA_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 PIKA_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 PIKA_TOOL_RECTANGLE_RESIZING_BOTTOM:
case PIKA_TOOL_RECTANGLE_RESIZING_TOP:
side_to_resize = SIDE_TO_RESIZE_LEFT_AND_RIGHT_SYMMETRICALLY;
break;
case PIKA_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 PIKA_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 PIKA_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 PIKA_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 PIKA_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 PIKA_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 PIKA_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 PIKA_TOOL_RECTANGLE_RESIZING_LEFT:
case PIKA_TOOL_RECTANGLE_RESIZING_RIGHT:
side_to_resize = SIDE_TO_RESIZE_TOP_AND_BOTTOM_SYMMETRICALLY;
break;
case PIKA_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;
}
}
/**
* pika_tool_rectangle_update_with_coord:
* @rectangle: A #PikaToolRectangle.
* @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
pika_tool_rectangle_update_with_coord (PikaToolRectangle *rectangle,
gdouble new_x,
gdouble new_y)
{
PikaToolRectanglePrivate *private = rectangle->private;
/* Move the corner or edge the user currently has grabbed. */
pika_tool_rectangle_apply_coord (rectangle, new_x, new_y);
/* Update private->function. The function changes if the user
* "flips" the rectangle.
*/
pika_tool_rectangle_check_function (rectangle);
/* Clamp the rectangle if necessary */
pika_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 != PIKA_TOOL_RECTANGLE_MOVING)
{
pika_tool_rectangle_apply_fixed_rule (rectangle);
}
pika_tool_rectangle_update_int_rect (rectangle);
}
static void
pika_tool_rectangle_apply_fixed_rule (PikaToolRectangle *rectangle)
{
PikaToolRectanglePrivate *private = rectangle->private;
PikaRectangleConstraint constraint_to_use;
PikaDisplayShell *shell;
PikaImage *image;
shell = pika_tool_widget_get_shell (PIKA_TOOL_WIDGET (rectangle));
image = pika_display_get_image (shell->display);
/* Calculate what constraint to use when needed. */
constraint_to_use = pika_tool_rectangle_get_constraint (rectangle);
if (private->fixed_rule_active &&
private->fixed_rule == PIKA_RECTANGLE_FIXED_ASPECT)
{
gdouble aspect;
aspect = CLAMP (private->aspect_numerator /
private->aspect_denominator,
1.0 / pika_image_get_height (image),
pika_image_get_width (image));
if (constraint_to_use == PIKA_RECTANGLE_CONSTRAIN_NONE)
{
pika_tool_rectangle_apply_aspect (rectangle,
aspect,
CLAMPED_NONE);
}
else
{
if (private->function != PIKA_TOOL_RECTANGLE_MOVING)
{
ClampedSide clamped_sides = CLAMPED_NONE;
pika_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.
*/
pika_tool_rectangle_clamp (rectangle,
&clamped_sides,
constraint_to_use,
private->fixed_center);
pika_tool_rectangle_apply_aspect (rectangle,
aspect,
clamped_sides);
}
else
{
pika_tool_rectangle_apply_aspect (rectangle,
aspect,
CLAMPED_NONE);
pika_tool_rectangle_keep_inside (rectangle,
constraint_to_use);
}
}
}
else if (private->fixed_rule_active &&
private->fixed_rule == PIKA_RECTANGLE_FIXED_SIZE)
{
pika_tool_rectangle_apply_fixed_width (rectangle,
constraint_to_use,
private->desired_fixed_size_width);
pika_tool_rectangle_apply_fixed_height (rectangle,
constraint_to_use,
private->desired_fixed_size_height);
}
else if (private->fixed_rule_active &&
private->fixed_rule == PIKA_RECTANGLE_FIXED_WIDTH)
{
pika_tool_rectangle_apply_fixed_width (rectangle,
constraint_to_use,
private->desired_fixed_width);
}
else if (private->fixed_rule_active &&
private->fixed_rule == PIKA_RECTANGLE_FIXED_HEIGHT)
{
pika_tool_rectangle_apply_fixed_height (rectangle,
constraint_to_use,
private->desired_fixed_height);
}
}
/**
* pika_tool_rectangle_get_constraints:
* @rectangle: A #PikaToolRectangle.
* @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
pika_tool_rectangle_get_constraints (PikaToolRectangle *rectangle,
gint *min_x,
gint *min_y,
gint *max_x,
gint *max_y,
PikaRectangleConstraint constraint)
{
PikaDisplayShell *shell;
PikaImage *image;
gint min_x_dummy;
gint min_y_dummy;
gint max_x_dummy;
gint max_y_dummy;
shell = pika_tool_widget_get_shell (PIKA_TOOL_WIDGET (rectangle));
image = pika_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 PIKA_RECTANGLE_CONSTRAIN_IMAGE:
if (image)
{
*min_x = 0;
*min_y = 0;
*max_x = pika_image_get_width (image);
*max_y = pika_image_get_height (image);
}
break;
case PIKA_RECTANGLE_CONSTRAIN_DRAWABLE:
if (image)
{
GList *items = pika_image_get_selected_drawables (image);
GList *iter;
/* Min and max constraints are respectively the smallest and
* highest drawable coordinates.
*/
for (iter = items; iter; iter = iter->next)
{
gint item_min_x;
gint item_min_y;
pika_item_get_offset (iter->data, &item_min_x, &item_min_y);
*min_x = MIN (*min_x, item_min_x);
*min_y = MIN (*min_y, item_min_y);
*max_x = MAX (*max_x, item_min_x + pika_item_get_width (iter->data));
*max_y = MAX (*max_y, item_min_y + pika_item_get_height (iter->data));
}
g_list_free (items);
}
break;
default:
g_warning ("Invalid rectangle constraint.\n");
return;
}
}
/**
* pika_tool_rectangle_handle_general_clamping:
* @rectangle: A #PikaToolRectangle.
*
* 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
pika_tool_rectangle_handle_general_clamping (PikaToolRectangle *rectangle)
{
PikaToolRectanglePrivate *private = rectangle->private;
PikaRectangleConstraint constraint;
constraint = pika_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 == PIKA_RECTANGLE_CONSTRAIN_NONE)
return;
if (private->function != PIKA_TOOL_RECTANGLE_MOVING)
{
pika_tool_rectangle_clamp (rectangle,
NULL,
constraint,
private->fixed_center);
}
else
{
pika_tool_rectangle_keep_inside (rectangle, constraint);
}
}
/**
* pika_tool_rectangle_update_int_rect:
* @rectangle:
*
* Update integer representation of rectangle.
**/
static void
pika_tool_rectangle_update_int_rect (PikaToolRectangle *rectangle)
{
PikaToolRectanglePrivate *private = rectangle->private;
private->x1_int = SIGNED_ROUND (private->x1);
private->y1_int = SIGNED_ROUND (private->y1);
if (pika_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;
}
}
/**
* pika_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 behavior of the
* rectangle.
*/
static void
pika_tool_rectangle_adjust_coord (PikaToolRectangle *rectangle,
gdouble coord_x_input,
gdouble coord_y_input,
gdouble *coord_x_output,
gdouble *coord_y_output)
{
PikaToolRectanglePrivate *priv = rectangle->private;
switch (priv->precision)
{
case PIKA_RECTANGLE_PRECISION_INT:
*coord_x_output = RINT (coord_x_input);
*coord_y_output = RINT (coord_y_input);
break;
case PIKA_RECTANGLE_PRECISION_DOUBLE:
default:
*coord_x_output = coord_x_input;
*coord_y_output = coord_y_input;
break;
}
}
static void
pika_tool_rectangle_recalculate_center_xy (PikaToolRectangle *rectangle)
{
PikaToolRectanglePrivate *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 */
PikaToolWidget *
pika_tool_rectangle_new (PikaDisplayShell *shell)
{
g_return_val_if_fail (PIKA_IS_DISPLAY_SHELL (shell), NULL);
return g_object_new (PIKA_TYPE_TOOL_RECTANGLE,
"shell", shell,
NULL);
}
PikaRectangleFunction
pika_tool_rectangle_get_function (PikaToolRectangle *rectangle)
{
g_return_val_if_fail (PIKA_IS_TOOL_RECTANGLE (rectangle),
PIKA_TOOL_RECTANGLE_DEAD);
return rectangle->private->function;
}
void
pika_tool_rectangle_set_function (PikaToolRectangle *rectangle,
PikaRectangleFunction function)
{
PikaToolRectanglePrivate *private;
g_return_if_fail (PIKA_IS_TOOL_RECTANGLE (rectangle));
private = rectangle->private;
if (private->function != function)
{
private->function = function;
pika_tool_rectangle_changed (PIKA_TOOL_WIDGET (rectangle));
}
}
void
pika_tool_rectangle_set_constraint (PikaToolRectangle *rectangle,
PikaRectangleConstraint constraint)
{
PikaToolRectanglePrivate *private;
g_return_if_fail (PIKA_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");
pika_tool_rectangle_clamp (rectangle, NULL, constraint, FALSE);
g_object_thaw_notify (G_OBJECT (rectangle));
pika_tool_rectangle_change_complete (rectangle);
}
}
PikaRectangleConstraint
pika_tool_rectangle_get_constraint (PikaToolRectangle *rectangle)
{
g_return_val_if_fail (PIKA_IS_TOOL_RECTANGLE (rectangle), 0);
return rectangle->private->constraint;
}
/**
* pika_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
pika_tool_rectangle_get_public_rect (PikaToolRectangle *rectangle,
gdouble *x1,
gdouble *y1,
gdouble *x2,
gdouble *y2)
{
PikaToolRectanglePrivate *priv;
g_return_if_fail (PIKA_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 PIKA_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 PIKA_RECTANGLE_PRECISION_DOUBLE:
default:
*x1 = priv->x1;
*y1 = priv->y1;
*x2 = priv->x2;
*y2 = priv->y2;
break;
}
}
/**
* pika_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
pika_tool_rectangle_pending_size_set (PikaToolRectangle *rectangle,
GObject *object,
const gchar *width_property,
const gchar *height_property)
{
PikaToolRectanglePrivate *private;
g_return_if_fail (PIKA_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);
}
/**
* pika_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
pika_tool_rectangle_constraint_size_set (PikaToolRectangle *rectangle,
GObject *object,
const gchar *width_property,
const gchar *height_property)
{
PikaDisplayShell *shell;
PikaContext *context;
PikaImage *image;
gdouble width;
gdouble height;
g_return_if_fail (PIKA_IS_TOOL_RECTANGLE (rectangle));
shell = pika_tool_widget_get_shell (PIKA_TOOL_WIDGET (rectangle));
context = pika_get_user_context (shell->display->pika);
image = pika_context_get_image (context);
if (! image)
{
width = 1.0;
height = 1.0;
}
else
{
PikaRectangleConstraint constraint;
constraint = pika_tool_rectangle_get_constraint (rectangle);
switch (constraint)
{
case PIKA_RECTANGLE_CONSTRAIN_DRAWABLE:
{
GList *items = pika_image_get_selected_layers (image);
GList *iter;
width = 0.0;
height = 0.0;
for (iter = items; iter; iter = iter->next)
{
if (width == 0.0 || height == 0.0)
{
width = pika_item_get_width (iter->data);
height = pika_item_get_height (iter->data);
}
else if (width != pika_item_get_width (iter->data) ||
height != pika_item_get_height (iter->data))
{
width = 0.0;
height = 0.0;
break;
}
}
/* Set constraint to the selected layers' dimensions if all
* selected layers have the same dimension. Otherwise set to
* image dimensions.
*/
if (width == 0.0 || height == 0.0)
{
width = pika_image_get_width (image);
height = pika_image_get_height (image);
}
}
break;
case PIKA_RECTANGLE_CONSTRAIN_IMAGE:
default:
{
width = pika_image_get_width (image);
height = pika_image_get_height (image);
}
break;
}
}
g_object_set (object,
width_property, width,
height_property, height,
NULL);
}
/**
* pika_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
pika_tool_rectangle_rectangle_is_first (PikaToolRectangle *rectangle)
{
g_return_val_if_fail (PIKA_IS_TOOL_RECTANGLE (rectangle), FALSE);
return rectangle->private->is_first;
}
/**
* pika_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
pika_tool_rectangle_rectangle_is_new (PikaToolRectangle *rectangle)
{
g_return_val_if_fail (PIKA_IS_TOOL_RECTANGLE (rectangle), FALSE);
return rectangle->private->is_new;
}
/**
* pika_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
pika_tool_rectangle_point_in_rectangle (PikaToolRectangle *rectangle,
gdouble x,
gdouble y)
{
gdouble x1 = 0;
gdouble y1 = 0;
gdouble x2 = 0;
gdouble y2 = 0;
g_return_val_if_fail (PIKA_IS_TOOL_RECTANGLE (rectangle), FALSE);
pika_tool_rectangle_get_public_rect (rectangle, &x1, &y1, &x2, &y2);
return (x >= x1 && x <= x2 &&
y >= y1 && y <= y2);
}
/**
* pika_tool_rectangle_frame_item:
* @rectangle: a #PikaToolRectangle interface
* @item: a #PikaItem 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
pika_tool_rectangle_frame_item (PikaToolRectangle *rectangle,
PikaItem *item)
{
PikaDisplayShell *shell;
gint offset_x;
gint offset_y;
gint width;
gint height;
PikaRectangleFunction old_function;
g_return_if_fail (PIKA_IS_TOOL_RECTANGLE (rectangle));
g_return_if_fail (PIKA_IS_ITEM (item));
g_return_if_fail (pika_item_is_attached (item));
shell = pika_tool_widget_get_shell (PIKA_TOOL_WIDGET (rectangle));
g_return_if_fail (pika_display_get_image (shell->display) ==
pika_item_get_image (item));
width = pika_item_get_width (item);
height = pika_item_get_height (item);
pika_item_get_offset (item, &offset_x, &offset_y);
old_function = rectangle->private->function;
pika_tool_rectangle_set_function (rectangle, PIKA_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.
*/
pika_tool_rectangle_set_constraint (rectangle, PIKA_RECTANGLE_CONSTRAIN_NONE);
pika_tool_rectangle_set_function (rectangle, old_function);
}
void
pika_tool_rectangle_auto_shrink (PikaToolRectangle *rectangle,
gboolean shrink_merged)
{
PikaToolRectanglePrivate *private;
PikaDisplayShell *shell;
PikaImage *image;
GList *pickables = NULL;
GList *iter;
gdouble new_x1, new_y1;
gdouble new_x2, new_y2;
g_return_if_fail (PIKA_IS_TOOL_RECTANGLE (rectangle));
private = rectangle->private;
shell = pika_tool_widget_get_shell (PIKA_TOOL_WIDGET (rectangle));
image = pika_display_get_image (shell->display);
if (shrink_merged)
pickables = g_list_prepend (NULL, image);
else
pickables = pika_image_get_selected_drawables (image);
if (! pickables)
return;
new_x1 = new_y1 = G_MAXINT;
new_x2 = new_y2 = G_MININT;
for (iter = pickables; iter; iter = iter->next)
{
gint x1, y1;
gint x2, y2;
gint offset_x = 0;
gint offset_y = 0;
gint shrunk_x;
gint shrunk_y;
gint shrunk_width;
gint shrunk_height;
if (PIKA_IS_IMAGE (iter->data))
{
x1 = private->x1;
y1 = private->y1;
x2 = private->x2;
y2 = private->y2;
}
else
{
pika_item_get_offset (PIKA_ITEM (iter->data), &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 (pika_pickable_auto_shrink (iter->data,
x1, y1, x2 - x1, y2 - y1,
&shrunk_x,
&shrunk_y,
&shrunk_width,
&shrunk_height))
{
case PIKA_AUTO_SHRINK_SHRINK:
{
new_x1 = MIN (new_x1, offset_x + shrunk_x);
new_y1 = MIN (new_y1, offset_y + shrunk_y);
new_x2 = MAX (new_x2, offset_x + shrunk_x + shrunk_width);
new_y2 = MAX (new_y2, offset_y + shrunk_y + shrunk_height);
}
break;
default:
break;
}
}
if (new_x1 != G_MAXINT && new_y1 != G_MAXINT &&
new_x2 != G_MININT && new_y2 != G_MININT)
{
PikaRectangleFunction original_function = private->function;
private->function = PIKA_TOOL_RECTANGLE_AUTO_SHRINK;
private->x1 = new_x1;
private->y1 = new_y1;
private->x2 = new_x2;
private->y2 = new_y2;
pika_tool_rectangle_update_int_rect (rectangle);
pika_tool_rectangle_change_complete (rectangle);
private->function = original_function;
pika_tool_rectangle_update_options (rectangle);
}
g_list_free (pickables);
}