2499 lines
88 KiB
C
2499 lines
88 KiB
C
/* 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
|
|
*
|
|
* pikatooltransformgrid.c
|
|
* Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
|
|
*
|
|
* Based on PikaUnifiedTransformTool
|
|
* Copyright (C) 2011 Mikael Magnusson <mikachu@src.gnome.org>
|
|
*
|
|
* 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 "libpikabase/pikabase.h"
|
|
#include "libpikamath/pikamath.h"
|
|
|
|
#include "display-types.h"
|
|
|
|
#include "core/pika-transform-utils.h"
|
|
#include "core/pika-utils.h"
|
|
|
|
#include "widgets/pikawidgets-utils.h"
|
|
|
|
#include "pikacanvashandle.h"
|
|
#include "pikacanvastransformguides.h"
|
|
#include "pikadisplayshell.h"
|
|
#include "pikatooltransformgrid.h"
|
|
|
|
#include "pika-intl.h"
|
|
|
|
|
|
#define MIN_HANDLE_SIZE 6
|
|
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_TRANSFORM,
|
|
PROP_X1,
|
|
PROP_Y1,
|
|
PROP_X2,
|
|
PROP_Y2,
|
|
PROP_PIVOT_X,
|
|
PROP_PIVOT_Y,
|
|
PROP_GUIDE_TYPE,
|
|
PROP_N_GUIDES,
|
|
PROP_CLIP_GUIDES,
|
|
PROP_SHOW_GUIDES,
|
|
PROP_INSIDE_FUNCTION,
|
|
PROP_OUTSIDE_FUNCTION,
|
|
PROP_USE_CORNER_HANDLES,
|
|
PROP_USE_PERSPECTIVE_HANDLES,
|
|
PROP_USE_SIDE_HANDLES,
|
|
PROP_USE_SHEAR_HANDLES,
|
|
PROP_USE_CENTER_HANDLE,
|
|
PROP_USE_PIVOT_HANDLE,
|
|
PROP_DYNAMIC_HANDLE_SIZE,
|
|
PROP_CONSTRAIN_MOVE,
|
|
PROP_CONSTRAIN_SCALE,
|
|
PROP_CONSTRAIN_ROTATE,
|
|
PROP_CONSTRAIN_SHEAR,
|
|
PROP_CONSTRAIN_PERSPECTIVE,
|
|
PROP_FROMPIVOT_SCALE,
|
|
PROP_FROMPIVOT_SHEAR,
|
|
PROP_FROMPIVOT_PERSPECTIVE,
|
|
PROP_CORNERSNAP,
|
|
PROP_FIXEDPIVOT
|
|
};
|
|
|
|
|
|
struct _PikaToolTransformGridPrivate
|
|
{
|
|
PikaMatrix3 transform;
|
|
gdouble x1, y1;
|
|
gdouble x2, y2;
|
|
gdouble pivot_x;
|
|
gdouble pivot_y;
|
|
PikaGuidesType guide_type;
|
|
gint n_guides;
|
|
gboolean clip_guides;
|
|
gboolean show_guides;
|
|
PikaTransformFunction inside_function;
|
|
PikaTransformFunction outside_function;
|
|
gboolean use_corner_handles;
|
|
gboolean use_perspective_handles;
|
|
gboolean use_side_handles;
|
|
gboolean use_shear_handles;
|
|
gboolean use_center_handle;
|
|
gboolean use_pivot_handle;
|
|
gboolean dynamic_handle_size;
|
|
gboolean constrain_move;
|
|
gboolean constrain_scale;
|
|
gboolean constrain_rotate;
|
|
gboolean constrain_shear;
|
|
gboolean constrain_perspective;
|
|
gboolean frompivot_scale;
|
|
gboolean frompivot_shear;
|
|
gboolean frompivot_perspective;
|
|
gboolean cornersnap;
|
|
gboolean fixedpivot;
|
|
|
|
gdouble curx; /* current x coord */
|
|
gdouble cury; /* current y coord */
|
|
|
|
gdouble button_down; /* is the mouse button pressed */
|
|
gdouble mousex; /* x coord where mouse was clicked */
|
|
gdouble mousey; /* y coord where mouse was clicked */
|
|
|
|
gdouble cx, cy; /* center point (for moving) */
|
|
|
|
/* transformed handle coords */
|
|
gdouble tx1, ty1;
|
|
gdouble tx2, ty2;
|
|
gdouble tx3, ty3;
|
|
gdouble tx4, ty4;
|
|
gdouble tcx, tcy;
|
|
gdouble tpx, tpy;
|
|
|
|
/* previous transformed handle coords */
|
|
gdouble prev_tx1, prev_ty1;
|
|
gdouble prev_tx2, prev_ty2;
|
|
gdouble prev_tx3, prev_ty3;
|
|
gdouble prev_tx4, prev_ty4;
|
|
gdouble prev_tcx, prev_tcy;
|
|
gdouble prev_tpx, prev_tpy;
|
|
|
|
PikaTransformHandle handle; /* current tool activity */
|
|
|
|
PikaCanvasItem *guides;
|
|
PikaCanvasItem *handles[PIKA_N_TRANSFORM_HANDLES];
|
|
PikaCanvasItem *center_items[2];
|
|
PikaCanvasItem *pivot_items[2];
|
|
};
|
|
|
|
|
|
/* local function prototypes */
|
|
|
|
static void pika_tool_transform_grid_constructed (GObject *object);
|
|
static void pika_tool_transform_grid_set_property (GObject *object,
|
|
guint property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec);
|
|
static void pika_tool_transform_grid_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec);
|
|
|
|
static void pika_tool_transform_grid_changed (PikaToolWidget *widget);
|
|
static gint pika_tool_transform_grid_button_press (PikaToolWidget *widget,
|
|
const PikaCoords *coords,
|
|
guint32 time,
|
|
GdkModifierType state,
|
|
PikaButtonPressType press_type);
|
|
static void pika_tool_transform_grid_button_release (PikaToolWidget *widget,
|
|
const PikaCoords *coords,
|
|
guint32 time,
|
|
GdkModifierType state,
|
|
PikaButtonReleaseType release_type);
|
|
static void pika_tool_transform_grid_motion (PikaToolWidget *widget,
|
|
const PikaCoords *coords,
|
|
guint32 time,
|
|
GdkModifierType state);
|
|
static PikaHit pika_tool_transform_grid_hit (PikaToolWidget *widget,
|
|
const PikaCoords *coords,
|
|
GdkModifierType state,
|
|
gboolean proximity);
|
|
static void pika_tool_transform_grid_hover (PikaToolWidget *widget,
|
|
const PikaCoords *coords,
|
|
GdkModifierType state,
|
|
gboolean proximity);
|
|
static void pika_tool_transform_grid_leave_notify (PikaToolWidget *widget);
|
|
static void pika_tool_transform_grid_hover_modifier (PikaToolWidget *widget,
|
|
GdkModifierType key,
|
|
gboolean press,
|
|
GdkModifierType state);
|
|
static gboolean pika_tool_transform_grid_get_cursor (PikaToolWidget *widget,
|
|
const PikaCoords *coords,
|
|
GdkModifierType state,
|
|
PikaCursorType *cursor,
|
|
PikaToolCursorType *tool_cursor,
|
|
PikaCursorModifier *modifier);
|
|
|
|
static PikaTransformHandle
|
|
pika_tool_transform_grid_get_handle_for_coords
|
|
(PikaToolTransformGrid *grid,
|
|
const PikaCoords *coords);
|
|
static void pika_tool_transform_grid_update_hilight (PikaToolTransformGrid *grid);
|
|
static void pika_tool_transform_grid_update_box (PikaToolTransformGrid *grid);
|
|
static void pika_tool_transform_grid_update_matrix (PikaToolTransformGrid *grid);
|
|
static void pika_tool_transform_grid_calc_handles (PikaToolTransformGrid *grid,
|
|
gint *handle_w,
|
|
gint *handle_h);
|
|
|
|
|
|
G_DEFINE_TYPE_WITH_PRIVATE (PikaToolTransformGrid, pika_tool_transform_grid,
|
|
PIKA_TYPE_TOOL_WIDGET)
|
|
|
|
#define parent_class pika_tool_transform_grid_parent_class
|
|
|
|
|
|
static void
|
|
pika_tool_transform_grid_class_init (PikaToolTransformGridClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
PikaToolWidgetClass *widget_class = PIKA_TOOL_WIDGET_CLASS (klass);
|
|
|
|
object_class->constructed = pika_tool_transform_grid_constructed;
|
|
object_class->set_property = pika_tool_transform_grid_set_property;
|
|
object_class->get_property = pika_tool_transform_grid_get_property;
|
|
|
|
widget_class->changed = pika_tool_transform_grid_changed;
|
|
widget_class->button_press = pika_tool_transform_grid_button_press;
|
|
widget_class->button_release = pika_tool_transform_grid_button_release;
|
|
widget_class->motion = pika_tool_transform_grid_motion;
|
|
widget_class->hit = pika_tool_transform_grid_hit;
|
|
widget_class->hover = pika_tool_transform_grid_hover;
|
|
widget_class->leave_notify = pika_tool_transform_grid_leave_notify;
|
|
widget_class->hover_modifier = pika_tool_transform_grid_hover_modifier;
|
|
widget_class->get_cursor = pika_tool_transform_grid_get_cursor;
|
|
widget_class->update_on_scale = TRUE;
|
|
|
|
g_object_class_install_property (object_class, PROP_TRANSFORM,
|
|
pika_param_spec_matrix3 ("transform",
|
|
NULL, NULL,
|
|
NULL,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
|
|
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_PIVOT_X,
|
|
g_param_spec_double ("pivot-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_PIVOT_Y,
|
|
g_param_spec_double ("pivot-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_GUIDE_TYPE,
|
|
g_param_spec_enum ("guide-type", NULL, NULL,
|
|
PIKA_TYPE_GUIDES_TYPE,
|
|
PIKA_GUIDES_NONE,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
|
|
g_object_class_install_property (object_class, PROP_N_GUIDES,
|
|
g_param_spec_int ("n-guides", NULL, NULL,
|
|
1, 128, 4,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
|
|
g_object_class_install_property (object_class, PROP_CLIP_GUIDES,
|
|
g_param_spec_boolean ("clip-guides", NULL, NULL,
|
|
FALSE,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
|
|
g_object_class_install_property (object_class, PROP_SHOW_GUIDES,
|
|
g_param_spec_boolean ("show-guides", NULL, NULL,
|
|
TRUE,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
|
|
g_object_class_install_property (object_class, PROP_INSIDE_FUNCTION,
|
|
g_param_spec_enum ("inside-function",
|
|
NULL, NULL,
|
|
PIKA_TYPE_TRANSFORM_FUNCTION,
|
|
PIKA_TRANSFORM_FUNCTION_MOVE,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
|
|
g_object_class_install_property (object_class, PROP_OUTSIDE_FUNCTION,
|
|
g_param_spec_enum ("outside-function",
|
|
NULL, NULL,
|
|
PIKA_TYPE_TRANSFORM_FUNCTION,
|
|
PIKA_TRANSFORM_FUNCTION_ROTATE,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
|
|
g_object_class_install_property (object_class, PROP_USE_CORNER_HANDLES,
|
|
g_param_spec_boolean ("use-corner-handles",
|
|
NULL, NULL,
|
|
FALSE,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
|
|
g_object_class_install_property (object_class, PROP_USE_PERSPECTIVE_HANDLES,
|
|
g_param_spec_boolean ("use-perspective-handles",
|
|
NULL, NULL,
|
|
FALSE,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
|
|
g_object_class_install_property (object_class, PROP_USE_SIDE_HANDLES,
|
|
g_param_spec_boolean ("use-side-handles",
|
|
NULL, NULL,
|
|
FALSE,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
|
|
g_object_class_install_property (object_class, PROP_USE_SHEAR_HANDLES,
|
|
g_param_spec_boolean ("use-shear-handles",
|
|
NULL, NULL,
|
|
FALSE,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
|
|
g_object_class_install_property (object_class, PROP_USE_CENTER_HANDLE,
|
|
g_param_spec_boolean ("use-center-handle",
|
|
NULL, NULL,
|
|
FALSE,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
|
|
g_object_class_install_property (object_class, PROP_USE_PIVOT_HANDLE,
|
|
g_param_spec_boolean ("use-pivot-handle",
|
|
NULL, NULL,
|
|
FALSE,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
|
|
g_object_class_install_property (object_class, PROP_DYNAMIC_HANDLE_SIZE,
|
|
g_param_spec_boolean ("dynamic-handle-size",
|
|
NULL, NULL,
|
|
TRUE,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
|
|
g_object_class_install_property (object_class, PROP_CONSTRAIN_MOVE,
|
|
g_param_spec_boolean ("constrain-move",
|
|
NULL, NULL,
|
|
FALSE,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
|
|
g_object_class_install_property (object_class, PROP_CONSTRAIN_SCALE,
|
|
g_param_spec_boolean ("constrain-scale",
|
|
NULL, NULL,
|
|
FALSE,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
|
|
g_object_class_install_property (object_class, PROP_CONSTRAIN_ROTATE,
|
|
g_param_spec_boolean ("constrain-rotate",
|
|
NULL, NULL,
|
|
FALSE,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
|
|
g_object_class_install_property (object_class, PROP_CONSTRAIN_SHEAR,
|
|
g_param_spec_boolean ("constrain-shear",
|
|
NULL, NULL,
|
|
FALSE,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
|
|
g_object_class_install_property (object_class, PROP_CONSTRAIN_PERSPECTIVE,
|
|
g_param_spec_boolean ("constrain-perspective",
|
|
NULL, NULL,
|
|
FALSE,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
|
|
g_object_class_install_property (object_class, PROP_FROMPIVOT_SCALE,
|
|
g_param_spec_boolean ("frompivot-scale",
|
|
NULL, NULL,
|
|
FALSE,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
|
|
g_object_class_install_property (object_class, PROP_FROMPIVOT_SHEAR,
|
|
g_param_spec_boolean ("frompivot-shear",
|
|
NULL, NULL,
|
|
FALSE,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
|
|
g_object_class_install_property (object_class, PROP_FROMPIVOT_PERSPECTIVE,
|
|
g_param_spec_boolean ("frompivot-perspective",
|
|
NULL, NULL,
|
|
FALSE,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
|
|
g_object_class_install_property (object_class, PROP_CORNERSNAP,
|
|
g_param_spec_boolean ("cornersnap",
|
|
NULL, NULL,
|
|
FALSE,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
|
|
g_object_class_install_property (object_class, PROP_FIXEDPIVOT,
|
|
g_param_spec_boolean ("fixedpivot",
|
|
NULL, NULL,
|
|
FALSE,
|
|
PIKA_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT));
|
|
}
|
|
|
|
static void
|
|
pika_tool_transform_grid_init (PikaToolTransformGrid *grid)
|
|
{
|
|
grid->private = pika_tool_transform_grid_get_instance_private (grid);
|
|
}
|
|
|
|
static void
|
|
pika_tool_transform_grid_constructed (GObject *object)
|
|
{
|
|
PikaToolTransformGrid *grid = PIKA_TOOL_TRANSFORM_GRID (object);
|
|
PikaToolWidget *widget = PIKA_TOOL_WIDGET (object);
|
|
PikaToolTransformGridPrivate *private = grid->private;
|
|
PikaCanvasGroup *stroke_group;
|
|
gint i;
|
|
|
|
G_OBJECT_CLASS (parent_class)->constructed (object);
|
|
|
|
private->guides = pika_tool_widget_add_transform_guides (widget,
|
|
&private->transform,
|
|
private->x1,
|
|
private->y1,
|
|
private->x2,
|
|
private->y2,
|
|
private->guide_type,
|
|
private->n_guides,
|
|
private->clip_guides);
|
|
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
/* draw the scale handles */
|
|
private->handles[PIKA_TRANSFORM_HANDLE_NW + i] =
|
|
pika_tool_widget_add_handle (widget,
|
|
PIKA_HANDLE_SQUARE,
|
|
0, 0, 10, 10,
|
|
PIKA_HANDLE_ANCHOR_CENTER);
|
|
|
|
/* draw the perspective handles */
|
|
private->handles[PIKA_TRANSFORM_HANDLE_NW_P + i] =
|
|
pika_tool_widget_add_handle (widget,
|
|
PIKA_HANDLE_DIAMOND,
|
|
0, 0, 10, 10,
|
|
PIKA_HANDLE_ANCHOR_CENTER);
|
|
|
|
/* draw the side handles */
|
|
private->handles[PIKA_TRANSFORM_HANDLE_N + i] =
|
|
pika_tool_widget_add_handle (widget,
|
|
PIKA_HANDLE_SQUARE,
|
|
0, 0, 10, 10,
|
|
PIKA_HANDLE_ANCHOR_CENTER);
|
|
|
|
/* draw the shear handles */
|
|
private->handles[PIKA_TRANSFORM_HANDLE_N_S + i] =
|
|
pika_tool_widget_add_handle (widget,
|
|
PIKA_HANDLE_FILLED_DIAMOND,
|
|
0, 0, 10, 10,
|
|
PIKA_HANDLE_ANCHOR_CENTER);
|
|
}
|
|
|
|
/* draw the rotation center axis handle */
|
|
stroke_group = pika_tool_widget_add_stroke_group (widget);
|
|
|
|
private->handles[PIKA_TRANSFORM_HANDLE_PIVOT] =
|
|
PIKA_CANVAS_ITEM (stroke_group);
|
|
|
|
pika_tool_widget_push_group (widget, stroke_group);
|
|
|
|
private->pivot_items[0] =
|
|
pika_tool_widget_add_handle (widget,
|
|
PIKA_HANDLE_CIRCLE,
|
|
0, 0, 10, 10,
|
|
PIKA_HANDLE_ANCHOR_CENTER);
|
|
private->pivot_items[1] =
|
|
pika_tool_widget_add_handle (widget,
|
|
PIKA_HANDLE_CROSS,
|
|
0, 0, 10, 10,
|
|
PIKA_HANDLE_ANCHOR_CENTER);
|
|
|
|
pika_tool_widget_pop_group (widget);
|
|
|
|
/* draw the center handle */
|
|
stroke_group = pika_tool_widget_add_stroke_group (widget);
|
|
|
|
private->handles[PIKA_TRANSFORM_HANDLE_CENTER] =
|
|
PIKA_CANVAS_ITEM (stroke_group);
|
|
|
|
pika_tool_widget_push_group (widget, stroke_group);
|
|
|
|
private->center_items[0] =
|
|
pika_tool_widget_add_handle (widget,
|
|
PIKA_HANDLE_SQUARE,
|
|
0, 0, 10, 10,
|
|
PIKA_HANDLE_ANCHOR_CENTER);
|
|
private->center_items[1] =
|
|
pika_tool_widget_add_handle (widget,
|
|
PIKA_HANDLE_CROSS,
|
|
0, 0, 10, 10,
|
|
PIKA_HANDLE_ANCHOR_CENTER);
|
|
|
|
pika_tool_widget_pop_group (widget);
|
|
|
|
pika_tool_transform_grid_changed (widget);
|
|
}
|
|
|
|
static void
|
|
pika_tool_transform_grid_set_property (GObject *object,
|
|
guint property_id,
|
|
const GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
PikaToolTransformGrid *grid = PIKA_TOOL_TRANSFORM_GRID (object);
|
|
PikaToolTransformGridPrivate *private = grid->private;
|
|
gboolean box = FALSE;
|
|
|
|
switch (property_id)
|
|
{
|
|
case PROP_TRANSFORM:
|
|
{
|
|
PikaMatrix3 *transform = g_value_get_boxed (value);
|
|
|
|
if (transform)
|
|
private->transform = *transform;
|
|
else
|
|
pika_matrix3_identity (&private->transform);
|
|
}
|
|
break;
|
|
|
|
case PROP_X1:
|
|
private->x1 = g_value_get_double (value);
|
|
box = TRUE;
|
|
break;
|
|
case PROP_Y1:
|
|
private->y1 = g_value_get_double (value);
|
|
box = TRUE;
|
|
break;
|
|
case PROP_X2:
|
|
private->x2 = g_value_get_double (value);
|
|
box = TRUE;
|
|
break;
|
|
case PROP_Y2:
|
|
private->y2 = g_value_get_double (value);
|
|
box = TRUE;
|
|
break;
|
|
|
|
case PROP_PIVOT_X:
|
|
private->pivot_x = g_value_get_double (value);
|
|
break;
|
|
case PROP_PIVOT_Y:
|
|
private->pivot_y = g_value_get_double (value);
|
|
break;
|
|
|
|
case PROP_GUIDE_TYPE:
|
|
private->guide_type = g_value_get_enum (value);
|
|
break;
|
|
case PROP_N_GUIDES:
|
|
private->n_guides = g_value_get_int (value);
|
|
break;
|
|
case PROP_CLIP_GUIDES:
|
|
private->clip_guides = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_SHOW_GUIDES:
|
|
private->show_guides = g_value_get_boolean (value);
|
|
break;
|
|
|
|
case PROP_INSIDE_FUNCTION:
|
|
private->inside_function = g_value_get_enum (value);
|
|
break;
|
|
case PROP_OUTSIDE_FUNCTION:
|
|
private->outside_function = g_value_get_enum (value);
|
|
break;
|
|
|
|
case PROP_USE_CORNER_HANDLES:
|
|
private->use_corner_handles = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_USE_PERSPECTIVE_HANDLES:
|
|
private->use_perspective_handles = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_USE_SIDE_HANDLES:
|
|
private->use_side_handles = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_USE_SHEAR_HANDLES:
|
|
private->use_shear_handles = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_USE_CENTER_HANDLE:
|
|
private->use_center_handle = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_USE_PIVOT_HANDLE:
|
|
private->use_pivot_handle = g_value_get_boolean (value);
|
|
break;
|
|
|
|
case PROP_DYNAMIC_HANDLE_SIZE:
|
|
private->dynamic_handle_size = g_value_get_boolean (value);
|
|
break;
|
|
|
|
case PROP_CONSTRAIN_MOVE:
|
|
private->constrain_move = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_CONSTRAIN_SCALE:
|
|
private->constrain_scale = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_CONSTRAIN_ROTATE:
|
|
private->constrain_rotate = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_CONSTRAIN_SHEAR:
|
|
private->constrain_shear = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_CONSTRAIN_PERSPECTIVE:
|
|
private->constrain_perspective = g_value_get_boolean (value);
|
|
break;
|
|
|
|
case PROP_FROMPIVOT_SCALE:
|
|
private->frompivot_scale = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_FROMPIVOT_SHEAR:
|
|
private->frompivot_shear = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_FROMPIVOT_PERSPECTIVE:
|
|
private->frompivot_perspective = g_value_get_boolean (value);
|
|
break;
|
|
|
|
case PROP_CORNERSNAP:
|
|
private->cornersnap = g_value_get_boolean (value);
|
|
break;
|
|
case PROP_FIXEDPIVOT:
|
|
private->fixedpivot = g_value_get_boolean (value);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
|
|
if (box)
|
|
{
|
|
private->cx = (private->x1 + private->x2) / 2.0;
|
|
private->cy = (private->y1 + private->y2) / 2.0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
pika_tool_transform_grid_get_property (GObject *object,
|
|
guint property_id,
|
|
GValue *value,
|
|
GParamSpec *pspec)
|
|
{
|
|
PikaToolTransformGrid *grid = PIKA_TOOL_TRANSFORM_GRID (object);
|
|
PikaToolTransformGridPrivate *private = grid->private;
|
|
|
|
switch (property_id)
|
|
{
|
|
case PROP_TRANSFORM:
|
|
g_value_set_boxed (value, &private->transform);
|
|
break;
|
|
|
|
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_PIVOT_X:
|
|
g_value_set_double (value, private->pivot_x);
|
|
break;
|
|
case PROP_PIVOT_Y:
|
|
g_value_set_double (value, private->pivot_y);
|
|
break;
|
|
|
|
case PROP_GUIDE_TYPE:
|
|
g_value_set_enum (value, private->guide_type);
|
|
break;
|
|
case PROP_N_GUIDES:
|
|
g_value_set_int (value, private->n_guides);
|
|
break;
|
|
case PROP_CLIP_GUIDES:
|
|
g_value_set_boolean (value, private->clip_guides);
|
|
break;
|
|
case PROP_SHOW_GUIDES:
|
|
g_value_set_boolean (value, private->show_guides);
|
|
break;
|
|
|
|
case PROP_INSIDE_FUNCTION:
|
|
g_value_set_enum (value, private->inside_function);
|
|
break;
|
|
case PROP_OUTSIDE_FUNCTION:
|
|
g_value_set_enum (value, private->outside_function);
|
|
break;
|
|
|
|
case PROP_USE_CORNER_HANDLES:
|
|
g_value_set_boolean (value, private->use_corner_handles);
|
|
break;
|
|
case PROP_USE_PERSPECTIVE_HANDLES:
|
|
g_value_set_boolean (value, private->use_perspective_handles);
|
|
break;
|
|
case PROP_USE_SIDE_HANDLES:
|
|
g_value_set_boolean (value, private->use_side_handles);
|
|
break;
|
|
case PROP_USE_SHEAR_HANDLES:
|
|
g_value_set_boolean (value, private->use_shear_handles);
|
|
break;
|
|
case PROP_USE_CENTER_HANDLE:
|
|
g_value_set_boolean (value, private->use_center_handle);
|
|
break;
|
|
case PROP_USE_PIVOT_HANDLE:
|
|
g_value_set_boolean (value, private->use_pivot_handle);
|
|
break;
|
|
|
|
case PROP_DYNAMIC_HANDLE_SIZE:
|
|
g_value_set_boolean (value, private->dynamic_handle_size);
|
|
break;
|
|
|
|
case PROP_CONSTRAIN_MOVE:
|
|
g_value_set_boolean (value, private->constrain_move);
|
|
break;
|
|
case PROP_CONSTRAIN_SCALE:
|
|
g_value_set_boolean (value, private->constrain_scale);
|
|
break;
|
|
case PROP_CONSTRAIN_ROTATE:
|
|
g_value_set_boolean (value, private->constrain_rotate);
|
|
break;
|
|
case PROP_CONSTRAIN_SHEAR:
|
|
g_value_set_boolean (value, private->constrain_shear);
|
|
break;
|
|
case PROP_CONSTRAIN_PERSPECTIVE:
|
|
g_value_set_boolean (value, private->constrain_perspective);
|
|
break;
|
|
|
|
case PROP_FROMPIVOT_SCALE:
|
|
g_value_set_boolean (value, private->frompivot_scale);
|
|
break;
|
|
case PROP_FROMPIVOT_SHEAR:
|
|
g_value_set_boolean (value, private->frompivot_shear);
|
|
break;
|
|
case PROP_FROMPIVOT_PERSPECTIVE:
|
|
g_value_set_boolean (value, private->frompivot_perspective);
|
|
break;
|
|
|
|
case PROP_CORNERSNAP:
|
|
g_value_set_boolean (value, private->cornersnap);
|
|
break;
|
|
case PROP_FIXEDPIVOT:
|
|
g_value_set_boolean (value, private->fixedpivot);
|
|
break;
|
|
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
transform_is_convex (PikaVector2 *pos)
|
|
{
|
|
return pika_transform_polygon_is_convex (pos[0].x, pos[0].y,
|
|
pos[1].x, pos[1].y,
|
|
pos[2].x, pos[2].y,
|
|
pos[3].x, pos[3].y);
|
|
}
|
|
|
|
static gboolean
|
|
transform_grid_is_convex (PikaToolTransformGrid *grid)
|
|
{
|
|
PikaToolTransformGridPrivate *private = grid->private;
|
|
|
|
return pika_transform_polygon_is_convex (private->tx1, private->ty1,
|
|
private->tx2, private->ty2,
|
|
private->tx3, private->ty3,
|
|
private->tx4, private->ty4);
|
|
}
|
|
|
|
static inline gboolean
|
|
vectorisnull (PikaVector2 v)
|
|
{
|
|
return ((v.x == 0.0) && (v.y == 0.0));
|
|
}
|
|
|
|
static inline gdouble
|
|
dotprod (PikaVector2 a,
|
|
PikaVector2 b)
|
|
{
|
|
return a.x * b.x + a.y * b.y;
|
|
}
|
|
|
|
static inline gdouble
|
|
norm (PikaVector2 a)
|
|
{
|
|
return sqrt (dotprod (a, a));
|
|
}
|
|
|
|
static inline PikaVector2
|
|
vectorsubtract (PikaVector2 a,
|
|
PikaVector2 b)
|
|
{
|
|
PikaVector2 c;
|
|
|
|
c.x = a.x - b.x;
|
|
c.y = a.y - b.y;
|
|
|
|
return c;
|
|
}
|
|
|
|
static inline PikaVector2
|
|
vectoradd (PikaVector2 a,
|
|
PikaVector2 b)
|
|
{
|
|
PikaVector2 c;
|
|
|
|
c.x = a.x + b.x;
|
|
c.y = a.y + b.y;
|
|
|
|
return c;
|
|
}
|
|
|
|
static inline PikaVector2
|
|
scalemult (PikaVector2 a,
|
|
gdouble b)
|
|
{
|
|
PikaVector2 c;
|
|
|
|
c.x = a.x * b;
|
|
c.y = a.y * b;
|
|
|
|
return c;
|
|
}
|
|
|
|
static inline PikaVector2
|
|
vectorproject (PikaVector2 a,
|
|
PikaVector2 b)
|
|
{
|
|
return scalemult (b, dotprod (a, b) / dotprod (b, b));
|
|
}
|
|
|
|
/* finds the clockwise angle between the vectors given, 0-2π */
|
|
static inline gdouble
|
|
calcangle (PikaVector2 a,
|
|
PikaVector2 b)
|
|
{
|
|
gdouble angle, angle2;
|
|
gdouble length;
|
|
|
|
if (vectorisnull (a) || vectorisnull (b))
|
|
return 0.0;
|
|
|
|
length = norm (a) * norm (b);
|
|
|
|
angle = acos (SAFE_CLAMP (dotprod (a, b) / length, -1.0, +1.0));
|
|
angle2 = b.y;
|
|
b.y = -b.x;
|
|
b.x = angle2;
|
|
angle2 = acos (SAFE_CLAMP (dotprod (a, b) / length, -1.0, +1.0));
|
|
|
|
return ((angle2 > G_PI / 2.0) ? angle : 2.0 * G_PI - angle);
|
|
}
|
|
|
|
static inline PikaVector2
|
|
rotate2d (PikaVector2 p,
|
|
gdouble angle)
|
|
{
|
|
PikaVector2 ret;
|
|
|
|
ret.x = cos (angle) * p.x-sin (angle) * p.y;
|
|
ret.y = sin (angle) * p.x+cos (angle) * p.y;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static inline PikaVector2
|
|
lineintersect (PikaVector2 p1, PikaVector2 p2,
|
|
PikaVector2 q1, PikaVector2 q2)
|
|
{
|
|
gdouble denom, u;
|
|
PikaVector2 p;
|
|
|
|
denom = (q2.y - q1.y) * (p2.x - p1.x) - (q2.x - q1.x) * (p2.y - p1.y);
|
|
if (denom == 0.0)
|
|
{
|
|
p.x = (p1.x + p2.x + q1.x + q2.x) / 4;
|
|
p.y = (p1.y + p2.y + q1.y + q2.y) / 4;
|
|
}
|
|
else
|
|
{
|
|
u = (q2.x - q1.x) * (p1.y - q1.y) - (q2.y - q1.y) * (p1.x - q1.x);
|
|
u /= denom;
|
|
|
|
p.x = p1.x + u * (p2.x - p1.x);
|
|
p.y = p1.y + u * (p2.y - p1.y);
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
static inline PikaVector2
|
|
get_pivot_delta (PikaToolTransformGrid *grid,
|
|
PikaVector2 *oldpos,
|
|
PikaVector2 *newpos,
|
|
PikaVector2 pivot)
|
|
{
|
|
PikaToolTransformGridPrivate *private = grid->private;
|
|
PikaMatrix3 transform_before;
|
|
PikaMatrix3 transform_after;
|
|
PikaVector2 delta;
|
|
|
|
pika_matrix3_identity (&transform_before);
|
|
pika_matrix3_identity (&transform_after);
|
|
|
|
pika_transform_matrix_perspective (&transform_before,
|
|
private->x1,
|
|
private->y1,
|
|
private->x2 - private->x1,
|
|
private->y2 - private->y1,
|
|
oldpos[0].x, oldpos[0].y,
|
|
oldpos[1].x, oldpos[1].y,
|
|
oldpos[2].x, oldpos[2].y,
|
|
oldpos[3].x, oldpos[3].y);
|
|
pika_transform_matrix_perspective (&transform_after,
|
|
private->x1,
|
|
private->y1,
|
|
private->x2 - private->x1,
|
|
private->y2 - private->y1,
|
|
newpos[0].x, newpos[0].y,
|
|
newpos[1].x, newpos[1].y,
|
|
newpos[2].x, newpos[2].y,
|
|
newpos[3].x, newpos[3].y);
|
|
pika_matrix3_invert (&transform_before);
|
|
pika_matrix3_mult (&transform_after, &transform_before);
|
|
pika_matrix3_transform_point (&transform_before,
|
|
pivot.x, pivot.y, &delta.x, &delta.y);
|
|
|
|
delta = vectorsubtract (delta, pivot);
|
|
|
|
return delta;
|
|
}
|
|
|
|
static gboolean
|
|
point_is_inside_polygon (gint n,
|
|
gdouble *x,
|
|
gdouble *y,
|
|
gdouble px,
|
|
gdouble py)
|
|
{
|
|
gint i, j;
|
|
gboolean odd = FALSE;
|
|
|
|
for (i = 0, j = n - 1; i < n; j = i++)
|
|
{
|
|
if ((y[i] < py && y[j] >= py) ||
|
|
(y[j] < py && y[i] >= py))
|
|
{
|
|
if (x[i] + (py - y[i]) / (y[j] - y[i]) * (x[j] - x[i]) < px)
|
|
odd = !odd;
|
|
}
|
|
}
|
|
|
|
return odd;
|
|
}
|
|
|
|
static gboolean
|
|
point_is_inside_polygon_pos (PikaVector2 *pos,
|
|
PikaVector2 point)
|
|
{
|
|
return point_is_inside_polygon (4,
|
|
(gdouble[4]){ pos[0].x, pos[1].x,
|
|
pos[3].x, pos[2].x },
|
|
(gdouble[4]){ pos[0].y, pos[1].y,
|
|
pos[3].y, pos[2].y },
|
|
point.x, point.y);
|
|
}
|
|
|
|
static void
|
|
get_handle_geometry (PikaToolTransformGrid *grid,
|
|
PikaVector2 *position,
|
|
gdouble *angle)
|
|
{
|
|
PikaToolTransformGridPrivate *private = grid->private;
|
|
|
|
PikaVector2 o[] = { { .x = private->tx1, .y = private->ty1 },
|
|
{ .x = private->tx2, .y = private->ty2 },
|
|
{ .x = private->tx3, .y = private->ty3 },
|
|
{ .x = private->tx4, .y = private->ty4 } };
|
|
PikaVector2 right = { .x = 1.0, .y = 0.0 };
|
|
PikaVector2 up = { .x = 0.0, .y = 1.0 };
|
|
|
|
if (position)
|
|
{
|
|
position[0] = o[0];
|
|
position[1] = o[1];
|
|
position[2] = o[2];
|
|
position[3] = o[3];
|
|
}
|
|
|
|
angle[0] = calcangle (vectorsubtract (o[1], o[0]), right);
|
|
angle[1] = calcangle (vectorsubtract (o[3], o[2]), right);
|
|
angle[2] = calcangle (vectorsubtract (o[3], o[1]), up);
|
|
angle[3] = calcangle (vectorsubtract (o[2], o[0]), up);
|
|
|
|
angle[4] = (angle[0] + angle[3]) / 2.0;
|
|
angle[5] = (angle[0] + angle[2]) / 2.0;
|
|
angle[6] = (angle[1] + angle[3]) / 2.0;
|
|
angle[7] = (angle[1] + angle[2]) / 2.0;
|
|
|
|
angle[8] = (angle[0] + angle[1] + angle[2] + angle[3]) / 4.0;
|
|
}
|
|
|
|
static void
|
|
pika_tool_transform_grid_changed (PikaToolWidget *widget)
|
|
{
|
|
PikaToolTransformGrid *grid = PIKA_TOOL_TRANSFORM_GRID (widget);
|
|
PikaToolTransformGridPrivate *private = grid->private;
|
|
gdouble angle[9];
|
|
PikaVector2 o[4], t[4];
|
|
gint handle_w;
|
|
gint handle_h;
|
|
gint d, i;
|
|
|
|
pika_tool_transform_grid_update_box (grid);
|
|
|
|
pika_canvas_transform_guides_set (private->guides,
|
|
&private->transform,
|
|
private->x1,
|
|
private->y1,
|
|
private->x2,
|
|
private->y2,
|
|
private->guide_type,
|
|
private->n_guides,
|
|
private->clip_guides);
|
|
pika_canvas_item_set_visible (private->guides, private->show_guides);
|
|
|
|
get_handle_geometry (grid, o, angle);
|
|
pika_tool_transform_grid_calc_handles (grid, &handle_w, &handle_h);
|
|
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
PikaCanvasItem *h;
|
|
gdouble factor;
|
|
|
|
/* the scale handles */
|
|
factor = 1.0;
|
|
if (private->use_perspective_handles)
|
|
factor = 1.5;
|
|
|
|
h = private->handles[PIKA_TRANSFORM_HANDLE_NW + i];
|
|
pika_canvas_item_set_visible (h, private->use_corner_handles);
|
|
|
|
if (private->use_corner_handles)
|
|
{
|
|
pika_canvas_handle_set_position (h, o[i].x, o[i].y);
|
|
pika_canvas_handle_set_size (h, handle_w * factor, handle_h * factor);
|
|
pika_canvas_handle_set_angles (h, angle[i + 4], 0.0);
|
|
}
|
|
|
|
/* the perspective handles */
|
|
factor = 1.0;
|
|
if (private->use_corner_handles)
|
|
factor = 0.8;
|
|
|
|
h = private->handles[PIKA_TRANSFORM_HANDLE_NW_P + i];
|
|
pika_canvas_item_set_visible (h, private->use_perspective_handles);
|
|
|
|
if (private->use_perspective_handles)
|
|
{
|
|
pika_canvas_handle_set_position (h, o[i].x, o[i].y);
|
|
pika_canvas_handle_set_size (h, handle_w * factor, handle_h * factor);
|
|
pika_canvas_handle_set_angles (h, angle[i + 4], 0.0);
|
|
}
|
|
}
|
|
|
|
/* draw the side handles */
|
|
t[0] = scalemult (vectoradd (o[0], o[1]), 0.5);
|
|
t[1] = scalemult (vectoradd (o[2], o[3]), 0.5);
|
|
t[2] = scalemult (vectoradd (o[1], o[3]), 0.5);
|
|
t[3] = scalemult (vectoradd (o[2], o[0]), 0.5);
|
|
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
PikaCanvasItem *h;
|
|
|
|
h = private->handles[PIKA_TRANSFORM_HANDLE_N + i];
|
|
pika_canvas_item_set_visible (h, private->use_side_handles);
|
|
|
|
if (private->use_side_handles)
|
|
{
|
|
pika_canvas_handle_set_position (h, t[i].x, t[i].y);
|
|
pika_canvas_handle_set_size (h, handle_w, handle_h);
|
|
pika_canvas_handle_set_angles (h, angle[i], 0.0);
|
|
}
|
|
}
|
|
|
|
/* draw the shear handles */
|
|
t[0] = scalemult (vectoradd ( o[0] , scalemult (o[1], 3.0)),
|
|
0.25);
|
|
t[1] = scalemult (vectoradd (scalemult (o[2], 3.0), o[3] ),
|
|
0.25);
|
|
t[2] = scalemult (vectoradd ( o[1] , scalemult (o[3], 3.0)),
|
|
0.25);
|
|
t[3] = scalemult (vectoradd (scalemult (o[0], 3.0), o[2] ),
|
|
0.25);
|
|
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
PikaCanvasItem *h;
|
|
|
|
h = private->handles[PIKA_TRANSFORM_HANDLE_N_S + i];
|
|
pika_canvas_item_set_visible (h, private->use_shear_handles);
|
|
|
|
if (private->use_shear_handles)
|
|
{
|
|
pika_canvas_handle_set_position (h, t[i].x, t[i].y);
|
|
pika_canvas_handle_set_size (h, handle_w, handle_h);
|
|
pika_canvas_handle_set_angles (h, angle[i], 0.0);
|
|
}
|
|
}
|
|
|
|
d = MIN (handle_w, handle_h);
|
|
if (private->use_center_handle)
|
|
d *= 2; /* so you can grab it from under the center handle */
|
|
|
|
pika_canvas_item_set_visible (private->handles[PIKA_TRANSFORM_HANDLE_PIVOT],
|
|
private->use_pivot_handle);
|
|
|
|
if (private->use_pivot_handle)
|
|
{
|
|
pika_canvas_handle_set_position (private->pivot_items[0],
|
|
private->tpx, private->tpy);
|
|
pika_canvas_handle_set_size (private->pivot_items[0], d, d);
|
|
|
|
pika_canvas_handle_set_position (private->pivot_items[1],
|
|
private->tpx, private->tpy);
|
|
pika_canvas_handle_set_size (private->pivot_items[1], d, d);
|
|
}
|
|
|
|
d = MIN (handle_w, handle_h);
|
|
|
|
pika_canvas_item_set_visible (private->handles[PIKA_TRANSFORM_HANDLE_CENTER],
|
|
private->use_center_handle);
|
|
|
|
if (private->use_center_handle)
|
|
{
|
|
pika_canvas_handle_set_position (private->center_items[0],
|
|
private->tcx, private->tcy);
|
|
pika_canvas_handle_set_size (private->center_items[0], d, d);
|
|
pika_canvas_handle_set_angles (private->center_items[0], angle[8], 0.0);
|
|
|
|
pika_canvas_handle_set_position (private->center_items[1],
|
|
private->tcx, private->tcy);
|
|
pika_canvas_handle_set_size (private->center_items[1], d, d);
|
|
pika_canvas_handle_set_angles (private->center_items[1], angle[8], 0.0);
|
|
}
|
|
|
|
pika_tool_transform_grid_update_hilight (grid);
|
|
}
|
|
|
|
gint
|
|
pika_tool_transform_grid_button_press (PikaToolWidget *widget,
|
|
const PikaCoords *coords,
|
|
guint32 time,
|
|
GdkModifierType state,
|
|
PikaButtonPressType press_type)
|
|
{
|
|
PikaToolTransformGrid *grid = PIKA_TOOL_TRANSFORM_GRID (widget);
|
|
PikaToolTransformGridPrivate *private = grid->private;
|
|
|
|
private->button_down = TRUE;
|
|
private->mousex = coords->x;
|
|
private->mousey = coords->y;
|
|
|
|
if (private->handle != PIKA_TRANSFORM_HANDLE_NONE)
|
|
{
|
|
if (private->handles[private->handle])
|
|
{
|
|
PikaCanvasItem *handle;
|
|
gdouble x, y;
|
|
|
|
switch (private->handle)
|
|
{
|
|
case PIKA_TRANSFORM_HANDLE_CENTER:
|
|
handle = private->center_items[0];
|
|
break;
|
|
|
|
case PIKA_TRANSFORM_HANDLE_PIVOT:
|
|
handle = private->pivot_items[0];
|
|
break;
|
|
|
|
default:
|
|
handle = private->handles[private->handle];
|
|
break;
|
|
}
|
|
|
|
pika_canvas_handle_get_position (handle, &x, &y);
|
|
|
|
pika_tool_widget_set_snap_offsets (widget,
|
|
SIGNED_ROUND (x - coords->x),
|
|
SIGNED_ROUND (y - coords->y),
|
|
0, 0);
|
|
}
|
|
else
|
|
{
|
|
pika_tool_widget_set_snap_offsets (widget, 0, 0, 0, 0);
|
|
}
|
|
|
|
private->prev_tx1 = private->tx1;
|
|
private->prev_ty1 = private->ty1;
|
|
private->prev_tx2 = private->tx2;
|
|
private->prev_ty2 = private->ty2;
|
|
private->prev_tx3 = private->tx3;
|
|
private->prev_ty3 = private->ty3;
|
|
private->prev_tx4 = private->tx4;
|
|
private->prev_ty4 = private->ty4;
|
|
private->prev_tpx = private->tpx;
|
|
private->prev_tpy = private->tpy;
|
|
private->prev_tcx = private->tcx;
|
|
private->prev_tcy = private->tcy;
|
|
|
|
return private->handle;
|
|
}
|
|
|
|
pika_tool_widget_set_snap_offsets (widget, 0, 0, 0, 0);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
pika_tool_transform_grid_button_release (PikaToolWidget *widget,
|
|
const PikaCoords *coords,
|
|
guint32 time,
|
|
GdkModifierType state,
|
|
PikaButtonReleaseType release_type)
|
|
{
|
|
PikaToolTransformGrid *grid = PIKA_TOOL_TRANSFORM_GRID (widget);
|
|
PikaToolTransformGridPrivate *private = grid->private;
|
|
|
|
private->button_down = FALSE;
|
|
}
|
|
|
|
void
|
|
pika_tool_transform_grid_motion (PikaToolWidget *widget,
|
|
const PikaCoords *coords,
|
|
guint32 time,
|
|
GdkModifierType state)
|
|
{
|
|
PikaToolTransformGrid *grid = PIKA_TOOL_TRANSFORM_GRID (widget);
|
|
PikaToolTransformGridPrivate *private = grid->private;
|
|
gdouble *x[4], *y[4];
|
|
gdouble *newpivot_x, *newpivot_y;
|
|
|
|
PikaVector2 oldpos[5], newpos[4];
|
|
PikaVector2 cur = { .x = coords->x,
|
|
.y = coords->y };
|
|
PikaVector2 mouse = { .x = private->mousex,
|
|
.y = private->mousey };
|
|
PikaVector2 d;
|
|
PikaVector2 pivot;
|
|
|
|
gboolean fixedpivot = private->fixedpivot;
|
|
PikaTransformHandle handle = private->handle;
|
|
gint i;
|
|
|
|
private->curx = coords->x;
|
|
private->cury = coords->y;
|
|
|
|
x[0] = &private->tx1;
|
|
y[0] = &private->ty1;
|
|
x[1] = &private->tx2;
|
|
y[1] = &private->ty2;
|
|
x[2] = &private->tx3;
|
|
y[2] = &private->ty3;
|
|
x[3] = &private->tx4;
|
|
y[3] = &private->ty4;
|
|
|
|
newpos[0].x = oldpos[0].x = private->prev_tx1;
|
|
newpos[0].y = oldpos[0].y = private->prev_ty1;
|
|
newpos[1].x = oldpos[1].x = private->prev_tx2;
|
|
newpos[1].y = oldpos[1].y = private->prev_ty2;
|
|
newpos[2].x = oldpos[2].x = private->prev_tx3;
|
|
newpos[2].y = oldpos[2].y = private->prev_ty3;
|
|
newpos[3].x = oldpos[3].x = private->prev_tx4;
|
|
newpos[3].y = oldpos[3].y = private->prev_ty4;
|
|
|
|
/* put center point in this array too */
|
|
oldpos[4].x = private->prev_tcx;
|
|
oldpos[4].y = private->prev_tcy;
|
|
|
|
d = vectorsubtract (cur, mouse);
|
|
|
|
newpivot_x = &private->tpx;
|
|
newpivot_y = &private->tpy;
|
|
|
|
if (private->use_pivot_handle)
|
|
{
|
|
pivot.x = private->prev_tpx;
|
|
pivot.y = private->prev_tpy;
|
|
}
|
|
else
|
|
{
|
|
/* when the transform grid doesn't use a pivot handle, use the center
|
|
* point as the pivot instead.
|
|
*/
|
|
pivot.x = private->prev_tcx;
|
|
pivot.y = private->prev_tcy;
|
|
|
|
fixedpivot = TRUE;
|
|
}
|
|
|
|
/* move */
|
|
if (handle == PIKA_TRANSFORM_HANDLE_CENTER)
|
|
{
|
|
if (private->constrain_move)
|
|
{
|
|
/* snap to 45 degree vectors from starting point */
|
|
gdouble angle = 16.0 * calcangle ((PikaVector2) { 1.0, 0.0 },
|
|
d) / (2.0 * G_PI);
|
|
gdouble dist = norm (d) / sqrt (2);
|
|
|
|
if (angle < 1.0 || angle >= 15.0)
|
|
d.y = 0;
|
|
else if (angle < 3.0)
|
|
d.y = -(d.x = dist);
|
|
else if (angle < 5.0)
|
|
d.x = 0;
|
|
else if (angle < 7.0)
|
|
d.x = d.y = -dist;
|
|
else if (angle < 9.0)
|
|
d.y = 0;
|
|
else if (angle < 11.0)
|
|
d.x = -(d.y = dist);
|
|
else if (angle < 13.0)
|
|
d.x = 0;
|
|
else if (angle < 15.0)
|
|
d.x = d.y = dist;
|
|
}
|
|
|
|
for (i = 0; i < 4; i++)
|
|
newpos[i] = vectoradd (oldpos[i], d);
|
|
}
|
|
|
|
/* rotate */
|
|
if (handle == PIKA_TRANSFORM_HANDLE_ROTATION)
|
|
{
|
|
gdouble angle = calcangle (vectorsubtract (cur, pivot),
|
|
vectorsubtract (mouse, pivot));
|
|
|
|
if (private->constrain_rotate)
|
|
{
|
|
/* round to 15 degree multiple */
|
|
angle /= 2 * G_PI / 24.0;
|
|
angle = round (angle);
|
|
angle *= 2 * G_PI / 24.0;
|
|
}
|
|
|
|
for (i = 0; i < 4; i++)
|
|
newpos[i] = vectoradd (pivot,
|
|
rotate2d (vectorsubtract (oldpos[i], pivot),
|
|
angle));
|
|
|
|
fixedpivot = TRUE;
|
|
}
|
|
|
|
/* move rotation axis */
|
|
if (handle == PIKA_TRANSFORM_HANDLE_PIVOT)
|
|
{
|
|
pivot = vectoradd (pivot, d);
|
|
|
|
if (private->cornersnap)
|
|
{
|
|
/* snap to corner points and center */
|
|
gint closest = 0;
|
|
gdouble closest_dist = G_MAXDOUBLE, dist;
|
|
|
|
for (i = 0; i < 5; i++)
|
|
{
|
|
dist = norm (vectorsubtract (pivot, oldpos[i]));
|
|
if (dist < closest_dist)
|
|
{
|
|
closest_dist = dist;
|
|
closest = i;
|
|
}
|
|
}
|
|
|
|
if (closest_dist *
|
|
pika_tool_widget_get_shell (widget)->scale_x < 50)
|
|
{
|
|
pivot = oldpos[closest];
|
|
}
|
|
}
|
|
|
|
fixedpivot = TRUE;
|
|
}
|
|
|
|
/* scaling via corner */
|
|
if (handle == PIKA_TRANSFORM_HANDLE_NW ||
|
|
handle == PIKA_TRANSFORM_HANDLE_NE ||
|
|
handle == PIKA_TRANSFORM_HANDLE_SE ||
|
|
handle == PIKA_TRANSFORM_HANDLE_SW)
|
|
{
|
|
/* Scaling through scale handles means translating one corner point,
|
|
* with all sides at constant angles.
|
|
*/
|
|
|
|
gint this, left, right, opposite;
|
|
|
|
/* 0: northwest, 1: northeast, 2: southwest, 3: southeast */
|
|
if (handle == PIKA_TRANSFORM_HANDLE_NW)
|
|
{
|
|
this = 0; left = 1; right = 2; opposite = 3;
|
|
}
|
|
else if (handle == PIKA_TRANSFORM_HANDLE_NE)
|
|
{
|
|
this = 1; left = 3; right = 0; opposite = 2;
|
|
}
|
|
else if (handle == PIKA_TRANSFORM_HANDLE_SW)
|
|
{
|
|
this = 2; left = 0; right = 3; opposite = 1;
|
|
}
|
|
else if (handle == PIKA_TRANSFORM_HANDLE_SE)
|
|
{
|
|
this = 3; left = 2; right = 1; opposite = 0;
|
|
}
|
|
else
|
|
pika_assert_not_reached ();
|
|
|
|
/* when the keep aspect transformation constraint is enabled,
|
|
* the translation shall only be along the diagonal that runs
|
|
* through this corner point.
|
|
*/
|
|
if (private->constrain_scale)
|
|
{
|
|
/* restrict to movement along the diagonal */
|
|
PikaVector2 diag = vectorsubtract (oldpos[this], oldpos[opposite]);
|
|
|
|
d = vectorproject (d, diag);
|
|
}
|
|
|
|
/* Move the corner being interacted with */
|
|
/* rp---------tp
|
|
* / /\ <- d, the interaction vector
|
|
* / / tp
|
|
* op----------/
|
|
*
|
|
*/
|
|
newpos[this] = vectoradd (oldpos[this], d);
|
|
|
|
/* Where the corner to the right and left would go, need these to form
|
|
* lines to intersect with the sides */
|
|
/* rp----------/
|
|
* /\ /\
|
|
* / nr / nt
|
|
* op----------lp
|
|
* \
|
|
* nl
|
|
*/
|
|
|
|
newpos[right] = vectoradd (oldpos[right], d);
|
|
newpos[left] = vectoradd (oldpos[left], d);
|
|
|
|
/* Now we just need to find the intersection of op-rp and nr-nt.
|
|
* rp----------/
|
|
* / /
|
|
* / nr==========nt
|
|
* op----------/
|
|
*
|
|
*/
|
|
newpos[right] = lineintersect (newpos[right], newpos[this],
|
|
oldpos[opposite], oldpos[right]);
|
|
newpos[left] = lineintersect (newpos[left], newpos[this],
|
|
oldpos[opposite], oldpos[left]);
|
|
/* /-----------/
|
|
* / /
|
|
* rp============nt
|
|
* op----------/
|
|
*
|
|
*/
|
|
|
|
/*
|
|
*
|
|
* /--------------/
|
|
* /--------------/
|
|
*
|
|
*/
|
|
|
|
if (private->frompivot_scale &&
|
|
transform_is_convex (newpos) &&
|
|
transform_is_convex (oldpos))
|
|
{
|
|
/* transform the pivot point before the interaction and
|
|
* after, and move everything by this difference
|
|
*/
|
|
//TODO the handle doesn't actually end up where the mouse cursor is
|
|
PikaVector2 delta = get_pivot_delta (grid, oldpos, newpos, pivot);
|
|
for (i = 0; i < 4; i++)
|
|
newpos[i] = vectorsubtract (newpos[i], delta);
|
|
|
|
fixedpivot = TRUE;
|
|
}
|
|
}
|
|
|
|
/* scaling via sides */
|
|
if (handle == PIKA_TRANSFORM_HANDLE_N ||
|
|
handle == PIKA_TRANSFORM_HANDLE_E ||
|
|
handle == PIKA_TRANSFORM_HANDLE_S ||
|
|
handle == PIKA_TRANSFORM_HANDLE_W)
|
|
{
|
|
gint this_l, this_r, opp_l, opp_r;
|
|
PikaVector2 side_l, side_r, midline;
|
|
|
|
/* 0: northwest, 1: northeast, 2: southwest, 3: southeast */
|
|
if (handle == PIKA_TRANSFORM_HANDLE_N)
|
|
{
|
|
this_l = 1; this_r = 0;
|
|
}
|
|
else if (handle == PIKA_TRANSFORM_HANDLE_E)
|
|
{
|
|
this_l = 3; this_r = 1;
|
|
}
|
|
else if (handle == PIKA_TRANSFORM_HANDLE_S)
|
|
{
|
|
this_l = 2; this_r = 3;
|
|
}
|
|
else if (handle == PIKA_TRANSFORM_HANDLE_W)
|
|
{
|
|
this_l = 0; this_r = 2;
|
|
}
|
|
else
|
|
pika_assert_not_reached ();
|
|
|
|
opp_l = 3 - this_r; opp_r = 3 - this_l;
|
|
|
|
side_l = vectorsubtract (oldpos[opp_l], oldpos[this_l]);
|
|
side_r = vectorsubtract (oldpos[opp_r], oldpos[this_r]);
|
|
midline = vectoradd (side_l, side_r);
|
|
|
|
/* restrict to movement along the midline */
|
|
d = vectorproject (d, midline);
|
|
|
|
if (private->constrain_scale)
|
|
{
|
|
PikaVector2 before, after, effective_pivot = pivot;
|
|
gdouble distance;
|
|
|
|
if (! private->frompivot_scale)
|
|
{
|
|
/* center of the opposite side is pivot */
|
|
effective_pivot = scalemult (vectoradd (oldpos[opp_l],
|
|
oldpos[opp_r]), 0.5);
|
|
}
|
|
|
|
/* get the difference between the distance from the pivot to
|
|
* where interaction started and the distance from the pivot
|
|
* to where cursor is now, and scale all corners distance
|
|
* from the pivot with this factor
|
|
*/
|
|
before = vectorsubtract (effective_pivot, mouse);
|
|
after = vectorsubtract (effective_pivot, cur);
|
|
after = vectorproject (after, before);
|
|
|
|
distance = 0.5 * (after.x / before.x + after.y / before.y);
|
|
|
|
for (i = 0; i < 4; i++)
|
|
newpos[i] = vectoradd (effective_pivot,
|
|
scalemult (vectorsubtract (oldpos[i],
|
|
effective_pivot),
|
|
distance));
|
|
}
|
|
else
|
|
{
|
|
/* just move the side */
|
|
newpos[this_l] = vectoradd (oldpos[this_l], d);
|
|
newpos[this_r] = vectoradd (oldpos[this_r], d);
|
|
}
|
|
|
|
if (! private->constrain_scale &&
|
|
private->frompivot_scale &&
|
|
transform_is_convex (newpos) &&
|
|
transform_is_convex (oldpos))
|
|
{
|
|
PikaVector2 delta = get_pivot_delta (grid, oldpos, newpos, pivot);
|
|
for (i = 0; i < 4; i++)
|
|
newpos[i] = vectorsubtract (newpos[i], delta);
|
|
|
|
fixedpivot = TRUE;
|
|
}
|
|
}
|
|
|
|
/* shear */
|
|
if (handle == PIKA_TRANSFORM_HANDLE_N_S ||
|
|
handle == PIKA_TRANSFORM_HANDLE_E_S ||
|
|
handle == PIKA_TRANSFORM_HANDLE_S_S ||
|
|
handle == PIKA_TRANSFORM_HANDLE_W_S)
|
|
{
|
|
gint this_l, this_r;
|
|
|
|
/* set up indices for this edge and the opposite edge */
|
|
if (handle == PIKA_TRANSFORM_HANDLE_N_S)
|
|
{
|
|
this_l = 1; this_r = 0;
|
|
}
|
|
else if (handle == PIKA_TRANSFORM_HANDLE_W_S)
|
|
{
|
|
this_l = 0; this_r = 2;
|
|
}
|
|
else if (handle == PIKA_TRANSFORM_HANDLE_S_S)
|
|
{
|
|
this_l = 2; this_r = 3;
|
|
}
|
|
else if (handle == PIKA_TRANSFORM_HANDLE_E_S)
|
|
{
|
|
this_l = 3; this_r = 1;
|
|
}
|
|
else
|
|
pika_assert_not_reached ();
|
|
|
|
if (private->constrain_shear)
|
|
{
|
|
/* restrict to movement along the side */
|
|
PikaVector2 side = vectorsubtract (oldpos[this_r], oldpos[this_l]);
|
|
|
|
d = vectorproject (d, side);
|
|
}
|
|
|
|
newpos[this_l] = vectoradd (oldpos[this_l], d);
|
|
newpos[this_r] = vectoradd (oldpos[this_r], d);
|
|
|
|
if (private->frompivot_shear &&
|
|
transform_is_convex (newpos) &&
|
|
transform_is_convex (oldpos))
|
|
{
|
|
PikaVector2 delta = get_pivot_delta (grid, oldpos, newpos, pivot);
|
|
for (i = 0; i < 4; i++)
|
|
newpos[i] = vectorsubtract (newpos[i], delta);
|
|
|
|
fixedpivot = TRUE;
|
|
}
|
|
}
|
|
|
|
/* perspective transform */
|
|
if (handle == PIKA_TRANSFORM_HANDLE_NW_P ||
|
|
handle == PIKA_TRANSFORM_HANDLE_NE_P ||
|
|
handle == PIKA_TRANSFORM_HANDLE_SE_P ||
|
|
handle == PIKA_TRANSFORM_HANDLE_SW_P)
|
|
{
|
|
gint this, left, right, opposite;
|
|
|
|
/* 0: northwest, 1: northeast, 2: southwest, 3: southeast */
|
|
if (handle == PIKA_TRANSFORM_HANDLE_NW_P)
|
|
{
|
|
this = 0; left = 1; right = 2; opposite = 3;
|
|
}
|
|
else if (handle == PIKA_TRANSFORM_HANDLE_NE_P)
|
|
{
|
|
this = 1; left = 3; right = 0; opposite = 2;
|
|
}
|
|
else if (handle == PIKA_TRANSFORM_HANDLE_SW_P)
|
|
{
|
|
this = 2; left = 0; right = 3; opposite = 1;
|
|
}
|
|
else if (handle == PIKA_TRANSFORM_HANDLE_SE_P)
|
|
{
|
|
this = 3; left = 2; right = 1; opposite = 0;
|
|
}
|
|
else
|
|
pika_assert_not_reached ();
|
|
|
|
if (private->constrain_perspective)
|
|
{
|
|
/* when the constrain transformation constraint is enabled,
|
|
* the translation shall only be either along the side
|
|
* angles of the two sides that run to this corner point, or
|
|
* along the diagonal that runs through this corner point.
|
|
*/
|
|
PikaVector2 proj[4];
|
|
gdouble rej[4];
|
|
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
if (i == this)
|
|
continue;
|
|
|
|
/* get the vectors along the sides and the diagonal */
|
|
proj[i] = vectorsubtract (oldpos[this], oldpos[i]);
|
|
|
|
/* project d on each candidate vector and see which has
|
|
* the shortest rejection
|
|
*/
|
|
proj[i] = vectorproject (d, proj[i]);
|
|
rej[i] = norm (vectorsubtract (d, proj[i]));
|
|
}
|
|
|
|
if (rej[left] < rej[right] && rej[left] < rej[opposite])
|
|
d = proj[left];
|
|
else if (rej[right] < rej[opposite])
|
|
d = proj[right];
|
|
else
|
|
d = proj[opposite];
|
|
}
|
|
|
|
newpos[this] = vectoradd (oldpos[this], d);
|
|
|
|
if (private->frompivot_perspective &&
|
|
transform_is_convex (newpos) &&
|
|
transform_is_convex (oldpos))
|
|
{
|
|
PikaVector2 delta = get_pivot_delta (grid, oldpos, newpos, pivot);
|
|
|
|
for (i = 0; i < 4; i++)
|
|
newpos[i] = vectorsubtract (newpos[i], delta);
|
|
|
|
fixedpivot = TRUE;
|
|
}
|
|
}
|
|
|
|
/* this will have been set to TRUE if an operation used the pivot in
|
|
* addition to being a user option
|
|
*/
|
|
if (! fixedpivot &&
|
|
transform_is_convex (newpos) &&
|
|
transform_is_convex (oldpos) &&
|
|
point_is_inside_polygon_pos (oldpos, pivot))
|
|
{
|
|
PikaVector2 delta = get_pivot_delta (grid, oldpos, newpos, pivot);
|
|
pivot = vectoradd (pivot, delta);
|
|
}
|
|
|
|
/* make sure the new coordinates are valid */
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
if (! isfinite (newpos[i].x) || ! isfinite (newpos[i].y))
|
|
return;
|
|
}
|
|
|
|
if (! isfinite (pivot.x) || ! isfinite (pivot.y))
|
|
return;
|
|
|
|
for (i = 0; i < 4; i++)
|
|
{
|
|
*x[i] = newpos[i].x;
|
|
*y[i] = newpos[i].y;
|
|
}
|
|
|
|
/* set unconditionally: if options get toggled during operation, we
|
|
* have to move pivot back
|
|
*/
|
|
*newpivot_x = pivot.x;
|
|
*newpivot_y = pivot.y;
|
|
|
|
pika_tool_transform_grid_update_matrix (grid);
|
|
}
|
|
|
|
static const gchar *
|
|
get_friendly_operation_name (PikaTransformHandle handle)
|
|
{
|
|
switch (handle)
|
|
{
|
|
case PIKA_TRANSFORM_HANDLE_NONE:
|
|
return "";
|
|
case PIKA_TRANSFORM_HANDLE_NW_P:
|
|
case PIKA_TRANSFORM_HANDLE_NE_P:
|
|
case PIKA_TRANSFORM_HANDLE_SW_P:
|
|
case PIKA_TRANSFORM_HANDLE_SE_P:
|
|
return _("Click-Drag to change perspective");
|
|
case PIKA_TRANSFORM_HANDLE_NW:
|
|
case PIKA_TRANSFORM_HANDLE_NE:
|
|
case PIKA_TRANSFORM_HANDLE_SW:
|
|
case PIKA_TRANSFORM_HANDLE_SE:
|
|
return _("Click-Drag to scale");
|
|
case PIKA_TRANSFORM_HANDLE_N:
|
|
case PIKA_TRANSFORM_HANDLE_S:
|
|
case PIKA_TRANSFORM_HANDLE_E:
|
|
case PIKA_TRANSFORM_HANDLE_W:
|
|
return _("Click-Drag to scale");
|
|
case PIKA_TRANSFORM_HANDLE_CENTER:
|
|
return _("Click-Drag to move");
|
|
case PIKA_TRANSFORM_HANDLE_PIVOT:
|
|
return _("Click-Drag to move the pivot point");
|
|
case PIKA_TRANSFORM_HANDLE_N_S:
|
|
case PIKA_TRANSFORM_HANDLE_S_S:
|
|
case PIKA_TRANSFORM_HANDLE_E_S:
|
|
case PIKA_TRANSFORM_HANDLE_W_S:
|
|
return _("Click-Drag to shear");
|
|
case PIKA_TRANSFORM_HANDLE_ROTATION:
|
|
return _("Click-Drag to rotate");
|
|
default:
|
|
pika_assert_not_reached ();
|
|
}
|
|
}
|
|
|
|
static PikaTransformHandle
|
|
pika_tool_transform_get_area_handle (PikaToolTransformGrid *grid,
|
|
const PikaCoords *coords,
|
|
PikaTransformFunction function)
|
|
{
|
|
PikaToolTransformGridPrivate *private = grid->private;
|
|
PikaTransformHandle handle = PIKA_TRANSFORM_HANDLE_NONE;
|
|
|
|
switch (function)
|
|
{
|
|
case PIKA_TRANSFORM_FUNCTION_NONE:
|
|
break;
|
|
|
|
case PIKA_TRANSFORM_FUNCTION_MOVE:
|
|
handle = PIKA_TRANSFORM_HANDLE_CENTER;
|
|
break;
|
|
|
|
case PIKA_TRANSFORM_FUNCTION_ROTATE:
|
|
handle = PIKA_TRANSFORM_HANDLE_ROTATION;
|
|
break;
|
|
|
|
case PIKA_TRANSFORM_FUNCTION_SCALE:
|
|
case PIKA_TRANSFORM_FUNCTION_PERSPECTIVE:
|
|
{
|
|
gdouble closest_dist;
|
|
gdouble dist;
|
|
|
|
dist = pika_canvas_item_transform_distance_square (private->guides,
|
|
coords->x, coords->y,
|
|
private->tx1,
|
|
private->ty1);
|
|
closest_dist = dist;
|
|
if (function == PIKA_TRANSFORM_FUNCTION_PERSPECTIVE)
|
|
handle = PIKA_TRANSFORM_HANDLE_NW_P;
|
|
else
|
|
handle = PIKA_TRANSFORM_HANDLE_NW;
|
|
|
|
dist = pika_canvas_item_transform_distance_square (private->guides,
|
|
coords->x, coords->y,
|
|
private->tx2,
|
|
private->ty2);
|
|
if (dist < closest_dist)
|
|
{
|
|
closest_dist = dist;
|
|
if (function == PIKA_TRANSFORM_FUNCTION_PERSPECTIVE)
|
|
handle = PIKA_TRANSFORM_HANDLE_NE_P;
|
|
else
|
|
handle = PIKA_TRANSFORM_HANDLE_NE;
|
|
}
|
|
|
|
dist = pika_canvas_item_transform_distance_square (private->guides,
|
|
coords->x, coords->y,
|
|
private->tx3,
|
|
private->ty3);
|
|
if (dist < closest_dist)
|
|
{
|
|
closest_dist = dist;
|
|
if (function == PIKA_TRANSFORM_FUNCTION_PERSPECTIVE)
|
|
handle = PIKA_TRANSFORM_HANDLE_SW_P;
|
|
else
|
|
handle = PIKA_TRANSFORM_HANDLE_SW;
|
|
}
|
|
|
|
dist = pika_canvas_item_transform_distance_square (private->guides,
|
|
coords->x, coords->y,
|
|
private->tx4,
|
|
private->ty4);
|
|
if (dist < closest_dist)
|
|
{
|
|
closest_dist = dist;
|
|
if (function == PIKA_TRANSFORM_FUNCTION_PERSPECTIVE)
|
|
handle = PIKA_TRANSFORM_HANDLE_SE_P;
|
|
else
|
|
handle = PIKA_TRANSFORM_HANDLE_SE;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case PIKA_TRANSFORM_FUNCTION_SHEAR:
|
|
{
|
|
gdouble handle_x;
|
|
gdouble handle_y;
|
|
gdouble closest_dist;
|
|
gdouble dist;
|
|
|
|
pika_canvas_handle_get_position (private->handles[PIKA_TRANSFORM_HANDLE_N],
|
|
&handle_x, &handle_y);
|
|
dist = pika_canvas_item_transform_distance_square (private->guides,
|
|
coords->x, coords->y,
|
|
handle_x, handle_y);
|
|
closest_dist = dist;
|
|
handle = PIKA_TRANSFORM_HANDLE_N_S;
|
|
|
|
pika_canvas_handle_get_position (private->handles[PIKA_TRANSFORM_HANDLE_W],
|
|
&handle_x, &handle_y);
|
|
dist = pika_canvas_item_transform_distance_square (private->guides,
|
|
coords->x, coords->y,
|
|
handle_x, handle_y);
|
|
if (dist < closest_dist)
|
|
{
|
|
closest_dist = dist;
|
|
handle = PIKA_TRANSFORM_HANDLE_W_S;
|
|
}
|
|
|
|
pika_canvas_handle_get_position (private->handles[PIKA_TRANSFORM_HANDLE_E],
|
|
&handle_x, &handle_y);
|
|
dist = pika_canvas_item_transform_distance_square (private->guides,
|
|
coords->x, coords->y,
|
|
handle_x, handle_y);
|
|
if (dist < closest_dist)
|
|
{
|
|
closest_dist = dist;
|
|
handle = PIKA_TRANSFORM_HANDLE_E_S;
|
|
}
|
|
|
|
pika_canvas_handle_get_position (private->handles[PIKA_TRANSFORM_HANDLE_S],
|
|
&handle_x, &handle_y);
|
|
dist = pika_canvas_item_transform_distance_square (private->guides,
|
|
coords->x, coords->y,
|
|
handle_x, handle_y);
|
|
if (dist < closest_dist)
|
|
{
|
|
closest_dist = dist;
|
|
handle = PIKA_TRANSFORM_HANDLE_S_S;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
return handle;
|
|
}
|
|
|
|
PikaHit
|
|
pika_tool_transform_grid_hit (PikaToolWidget *widget,
|
|
const PikaCoords *coords,
|
|
GdkModifierType state,
|
|
gboolean proximity)
|
|
{
|
|
PikaToolTransformGrid *grid = PIKA_TOOL_TRANSFORM_GRID (widget);
|
|
PikaTransformHandle handle;
|
|
|
|
handle = pika_tool_transform_grid_get_handle_for_coords (grid, coords);
|
|
|
|
if (handle != PIKA_TRANSFORM_HANDLE_NONE)
|
|
return PIKA_HIT_DIRECT;
|
|
|
|
return PIKA_HIT_INDIRECT;
|
|
}
|
|
|
|
void
|
|
pika_tool_transform_grid_hover (PikaToolWidget *widget,
|
|
const PikaCoords *coords,
|
|
GdkModifierType state,
|
|
gboolean proximity)
|
|
{
|
|
PikaToolTransformGrid *grid = PIKA_TOOL_TRANSFORM_GRID (widget);
|
|
PikaToolTransformGridPrivate *private = grid->private;
|
|
PikaTransformHandle handle;
|
|
|
|
handle = pika_tool_transform_grid_get_handle_for_coords (grid, coords);
|
|
|
|
if (handle == PIKA_TRANSFORM_HANDLE_NONE)
|
|
{
|
|
/* points passed in clockwise order */
|
|
if (point_is_inside_polygon (4,
|
|
(gdouble[4]){ private->tx1, private->tx2,
|
|
private->tx4, private->tx3 },
|
|
(gdouble[4]){ private->ty1, private->ty2,
|
|
private->ty4, private->ty3 },
|
|
coords->x, coords->y))
|
|
{
|
|
handle = pika_tool_transform_get_area_handle (grid, coords,
|
|
private->inside_function);
|
|
}
|
|
else
|
|
{
|
|
handle = pika_tool_transform_get_area_handle (grid, coords,
|
|
private->outside_function);
|
|
}
|
|
}
|
|
|
|
if (handle != PIKA_TRANSFORM_HANDLE_NONE && proximity)
|
|
{
|
|
pika_tool_widget_set_status (widget,
|
|
get_friendly_operation_name (handle));
|
|
}
|
|
else
|
|
{
|
|
pika_tool_widget_set_status (widget, NULL);
|
|
}
|
|
|
|
private->handle = handle;
|
|
|
|
pika_tool_transform_grid_update_hilight (grid);
|
|
}
|
|
|
|
void
|
|
pika_tool_transform_grid_leave_notify (PikaToolWidget *widget)
|
|
{
|
|
PikaToolTransformGrid *grid = PIKA_TOOL_TRANSFORM_GRID (widget);
|
|
PikaToolTransformGridPrivate *private = grid->private;
|
|
|
|
private->handle = PIKA_TRANSFORM_HANDLE_NONE;
|
|
|
|
pika_tool_transform_grid_update_hilight (grid);
|
|
|
|
PIKA_TOOL_WIDGET_CLASS (parent_class)->leave_notify (widget);
|
|
}
|
|
|
|
static void
|
|
pika_tool_transform_grid_modifier (PikaToolWidget *widget,
|
|
GdkModifierType key)
|
|
{
|
|
PikaToolTransformGrid *grid = PIKA_TOOL_TRANSFORM_GRID (widget);
|
|
PikaToolTransformGridPrivate *private = grid->private;
|
|
|
|
if (key == pika_get_constrain_behavior_mask ())
|
|
{
|
|
g_object_set (widget,
|
|
"frompivot-scale", ! private->frompivot_scale,
|
|
"frompivot-shear", ! private->frompivot_shear,
|
|
"frompivot-perspective", ! private->frompivot_perspective,
|
|
NULL);
|
|
}
|
|
else if (key == pika_get_extend_selection_mask ())
|
|
{
|
|
g_object_set (widget,
|
|
"cornersnap", ! private->cornersnap,
|
|
"constrain-move", ! private->constrain_move,
|
|
"constrain-scale", ! private->constrain_scale,
|
|
"constrain-rotate", ! private->constrain_rotate,
|
|
"constrain-shear", ! private->constrain_shear,
|
|
"constrain-perspective", ! private->constrain_perspective,
|
|
NULL);
|
|
}
|
|
}
|
|
|
|
static void
|
|
pika_tool_transform_grid_hover_modifier (PikaToolWidget *widget,
|
|
GdkModifierType key,
|
|
gboolean press,
|
|
GdkModifierType state)
|
|
{
|
|
PikaToolTransformGrid *grid = PIKA_TOOL_TRANSFORM_GRID (widget);
|
|
PikaToolTransformGridPrivate *private = grid->private;
|
|
PikaCoords coords = { 0.0, };
|
|
|
|
pika_tool_transform_grid_modifier (widget, key);
|
|
|
|
if (private->button_down)
|
|
{
|
|
/* send a non-motion to update the grid with the new constraints */
|
|
coords.x = private->curx;
|
|
coords.y = private->cury;
|
|
pika_tool_transform_grid_motion (widget, &coords, 0, state);
|
|
}
|
|
}
|
|
|
|
static gboolean
|
|
pika_tool_transform_grid_get_cursor (PikaToolWidget *widget,
|
|
const PikaCoords *coords,
|
|
GdkModifierType state,
|
|
PikaCursorType *cursor,
|
|
PikaToolCursorType *tool_cursor,
|
|
PikaCursorModifier *modifier)
|
|
{
|
|
PikaToolTransformGrid *grid = PIKA_TOOL_TRANSFORM_GRID (widget);
|
|
PikaToolTransformGridPrivate *private = grid->private;
|
|
gdouble angle[9];
|
|
gint i;
|
|
PikaCursorType map[8];
|
|
PikaVector2 pos[4], this, that;
|
|
gboolean flip = FALSE;
|
|
gboolean side = FALSE;
|
|
gboolean set_cursor = TRUE;
|
|
|
|
map[0] = PIKA_CURSOR_CORNER_TOP_LEFT;
|
|
map[1] = PIKA_CURSOR_CORNER_TOP;
|
|
map[2] = PIKA_CURSOR_CORNER_TOP_RIGHT;
|
|
map[3] = PIKA_CURSOR_CORNER_RIGHT;
|
|
map[4] = PIKA_CURSOR_CORNER_BOTTOM_RIGHT;
|
|
map[5] = PIKA_CURSOR_CORNER_BOTTOM;
|
|
map[6] = PIKA_CURSOR_CORNER_BOTTOM_LEFT;
|
|
map[7] = PIKA_CURSOR_CORNER_LEFT;
|
|
|
|
get_handle_geometry (grid, pos, angle);
|
|
|
|
for (i = 0; i < 8; i++)
|
|
angle[i] = round (angle[i] * 180.0 / G_PI / 45.0);
|
|
|
|
switch (private->handle)
|
|
{
|
|
case PIKA_TRANSFORM_HANDLE_NW_P:
|
|
case PIKA_TRANSFORM_HANDLE_NW:
|
|
i = (gint) angle[4] + 0;
|
|
this = pos[0];
|
|
that = pos[3];
|
|
break;
|
|
|
|
case PIKA_TRANSFORM_HANDLE_NE_P:
|
|
case PIKA_TRANSFORM_HANDLE_NE:
|
|
i = (gint) angle[5] + 2;
|
|
this = pos[1];
|
|
that = pos[2];
|
|
break;
|
|
|
|
case PIKA_TRANSFORM_HANDLE_SW_P:
|
|
case PIKA_TRANSFORM_HANDLE_SW:
|
|
i = (gint) angle[6] + 6;
|
|
this = pos[2];
|
|
that = pos[1];
|
|
break;
|
|
|
|
case PIKA_TRANSFORM_HANDLE_SE_P:
|
|
case PIKA_TRANSFORM_HANDLE_SE:
|
|
i = (gint) angle[7] + 4;
|
|
this = pos[3];
|
|
that = pos[0];
|
|
break;
|
|
|
|
case PIKA_TRANSFORM_HANDLE_N:
|
|
case PIKA_TRANSFORM_HANDLE_N_S:
|
|
i = (gint) angle[0] + 1;
|
|
this = vectoradd (pos[0], pos[1]);
|
|
that = vectoradd (pos[2], pos[3]);
|
|
side = TRUE;
|
|
break;
|
|
|
|
case PIKA_TRANSFORM_HANDLE_S:
|
|
case PIKA_TRANSFORM_HANDLE_S_S:
|
|
i = (gint) angle[1] + 5;
|
|
this = vectoradd (pos[2], pos[3]);
|
|
that = vectoradd (pos[0], pos[1]);
|
|
side = TRUE;
|
|
break;
|
|
|
|
case PIKA_TRANSFORM_HANDLE_E:
|
|
case PIKA_TRANSFORM_HANDLE_E_S:
|
|
i = (gint) angle[2] + 3;
|
|
this = vectoradd (pos[1], pos[3]);
|
|
that = vectoradd (pos[0], pos[2]);
|
|
side = TRUE;
|
|
break;
|
|
|
|
case PIKA_TRANSFORM_HANDLE_W:
|
|
case PIKA_TRANSFORM_HANDLE_W_S:
|
|
i = (gint) angle[3] + 7;
|
|
this = vectoradd (pos[0], pos[2]);
|
|
that = vectoradd (pos[1], pos[3]);
|
|
side = TRUE;
|
|
break;
|
|
|
|
default:
|
|
set_cursor = FALSE;
|
|
break;
|
|
}
|
|
|
|
if (set_cursor)
|
|
{
|
|
i %= 8;
|
|
|
|
switch (map[i])
|
|
{
|
|
case PIKA_CURSOR_CORNER_TOP_LEFT:
|
|
if (this.x + this.y > that.x + that.y)
|
|
flip = TRUE;
|
|
break;
|
|
case PIKA_CURSOR_CORNER_TOP:
|
|
if (this.y > that.y)
|
|
flip = TRUE;
|
|
break;
|
|
case PIKA_CURSOR_CORNER_TOP_RIGHT:
|
|
if (this.x - this.y < that.x - that.y)
|
|
flip = TRUE;
|
|
break;
|
|
case PIKA_CURSOR_CORNER_RIGHT:
|
|
if (this.x < that.x)
|
|
flip = TRUE;
|
|
break;
|
|
case PIKA_CURSOR_CORNER_BOTTOM_RIGHT:
|
|
if (this.x + this.y < that.x + that.y)
|
|
flip = TRUE;
|
|
break;
|
|
case PIKA_CURSOR_CORNER_BOTTOM:
|
|
if (this.y < that.y)
|
|
flip = TRUE;
|
|
break;
|
|
case PIKA_CURSOR_CORNER_BOTTOM_LEFT:
|
|
if (this.x - this.y > that.x - that.y)
|
|
flip = TRUE;
|
|
break;
|
|
case PIKA_CURSOR_CORNER_LEFT:
|
|
if (this.x > that.x)
|
|
flip = TRUE;
|
|
break;
|
|
default:
|
|
pika_assert_not_reached ();
|
|
}
|
|
|
|
if (flip)
|
|
*cursor = map[(i + 4) % 8];
|
|
else
|
|
*cursor = map[i];
|
|
|
|
if (side)
|
|
*cursor += 8;
|
|
}
|
|
|
|
/* parent class handles *cursor and *modifier for most handles */
|
|
switch (private->handle)
|
|
{
|
|
case PIKA_TRANSFORM_HANDLE_NONE:
|
|
*tool_cursor = PIKA_TOOL_CURSOR_NONE;
|
|
break;
|
|
|
|
case PIKA_TRANSFORM_HANDLE_NW_P:
|
|
case PIKA_TRANSFORM_HANDLE_NE_P:
|
|
case PIKA_TRANSFORM_HANDLE_SW_P:
|
|
case PIKA_TRANSFORM_HANDLE_SE_P:
|
|
*tool_cursor = PIKA_TOOL_CURSOR_PERSPECTIVE;
|
|
break;
|
|
|
|
case PIKA_TRANSFORM_HANDLE_NW:
|
|
case PIKA_TRANSFORM_HANDLE_NE:
|
|
case PIKA_TRANSFORM_HANDLE_SW:
|
|
case PIKA_TRANSFORM_HANDLE_SE:
|
|
case PIKA_TRANSFORM_HANDLE_N:
|
|
case PIKA_TRANSFORM_HANDLE_S:
|
|
case PIKA_TRANSFORM_HANDLE_E:
|
|
case PIKA_TRANSFORM_HANDLE_W:
|
|
*tool_cursor = PIKA_TOOL_CURSOR_RESIZE;
|
|
break;
|
|
|
|
case PIKA_TRANSFORM_HANDLE_CENTER:
|
|
*tool_cursor = PIKA_TOOL_CURSOR_MOVE;
|
|
break;
|
|
|
|
case PIKA_TRANSFORM_HANDLE_PIVOT:
|
|
*tool_cursor = PIKA_TOOL_CURSOR_ROTATE;
|
|
*modifier = PIKA_CURSOR_MODIFIER_MOVE;
|
|
break;
|
|
|
|
case PIKA_TRANSFORM_HANDLE_N_S:
|
|
case PIKA_TRANSFORM_HANDLE_S_S:
|
|
case PIKA_TRANSFORM_HANDLE_E_S:
|
|
case PIKA_TRANSFORM_HANDLE_W_S:
|
|
*tool_cursor = PIKA_TOOL_CURSOR_SHEAR;
|
|
break;
|
|
|
|
case PIKA_TRANSFORM_HANDLE_ROTATION:
|
|
*tool_cursor = PIKA_TOOL_CURSOR_ROTATE;
|
|
break;
|
|
|
|
default:
|
|
g_return_val_if_reached (FALSE);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static PikaTransformHandle
|
|
pika_tool_transform_grid_get_handle_for_coords (PikaToolTransformGrid *grid,
|
|
const PikaCoords *coords)
|
|
{
|
|
PikaToolTransformGridPrivate *private = grid->private;
|
|
PikaTransformHandle i;
|
|
|
|
for (i = PIKA_TRANSFORM_HANDLE_NONE + 1; i < PIKA_N_TRANSFORM_HANDLES; i++)
|
|
{
|
|
if (private->handles[i] &&
|
|
pika_canvas_item_hit (private->handles[i], coords->x, coords->y))
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return PIKA_TRANSFORM_HANDLE_NONE;
|
|
}
|
|
|
|
static void
|
|
pika_tool_transform_grid_update_hilight (PikaToolTransformGrid *grid)
|
|
{
|
|
PikaToolTransformGridPrivate *private = grid->private;
|
|
PikaTransformHandle handle;
|
|
|
|
for (handle = PIKA_TRANSFORM_HANDLE_NONE;
|
|
handle < PIKA_N_TRANSFORM_HANDLES;
|
|
handle++)
|
|
{
|
|
if (private->handles[handle])
|
|
{
|
|
pika_canvas_item_set_highlight (private->handles[handle],
|
|
handle == private->handle);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
pika_tool_transform_grid_update_box (PikaToolTransformGrid *grid)
|
|
{
|
|
PikaToolTransformGridPrivate *private = grid->private;
|
|
|
|
pika_matrix3_transform_point (&private->transform,
|
|
private->x1, private->y1,
|
|
&private->tx1, &private->ty1);
|
|
pika_matrix3_transform_point (&private->transform,
|
|
private->x2, private->y1,
|
|
&private->tx2, &private->ty2);
|
|
pika_matrix3_transform_point (&private->transform,
|
|
private->x1, private->y2,
|
|
&private->tx3, &private->ty3);
|
|
pika_matrix3_transform_point (&private->transform,
|
|
private->x2, private->y2,
|
|
&private->tx4, &private->ty4);
|
|
|
|
/* don't transform pivot */
|
|
private->tpx = private->pivot_x;
|
|
private->tpy = private->pivot_y;
|
|
|
|
if (transform_grid_is_convex (grid))
|
|
{
|
|
pika_matrix3_transform_point (&private->transform,
|
|
(private->x1 + private->x2) / 2.0,
|
|
(private->y1 + private->y2) / 2.0,
|
|
&private->tcx, &private->tcy);
|
|
}
|
|
else
|
|
{
|
|
private->tcx = (private->tx1 +
|
|
private->tx2 +
|
|
private->tx3 +
|
|
private->tx4) / 4.0;
|
|
private->tcy = (private->ty1 +
|
|
private->ty2 +
|
|
private->ty3 +
|
|
private->ty4) / 4.0;
|
|
}
|
|
}
|
|
|
|
static void
|
|
pika_tool_transform_grid_update_matrix (PikaToolTransformGrid *grid)
|
|
{
|
|
PikaToolTransformGridPrivate *private = grid->private;
|
|
|
|
pika_matrix3_identity (&private->transform);
|
|
pika_transform_matrix_perspective (&private->transform,
|
|
private->x1,
|
|
private->y1,
|
|
private->x2 - private->x1,
|
|
private->y2 - private->y1,
|
|
private->tx1,
|
|
private->ty1,
|
|
private->tx2,
|
|
private->ty2,
|
|
private->tx3,
|
|
private->ty3,
|
|
private->tx4,
|
|
private->ty4);
|
|
|
|
private->pivot_x = private->tpx;
|
|
private->pivot_y = private->tpy;
|
|
|
|
g_object_freeze_notify (G_OBJECT (grid));
|
|
g_object_notify (G_OBJECT (grid), "transform");
|
|
g_object_notify (G_OBJECT (grid), "pivot-x");
|
|
g_object_notify (G_OBJECT (grid), "pivot-x");
|
|
g_object_thaw_notify (G_OBJECT (grid));
|
|
}
|
|
|
|
static void
|
|
pika_tool_transform_grid_calc_handles (PikaToolTransformGrid *grid,
|
|
gint *handle_w,
|
|
gint *handle_h)
|
|
{
|
|
PikaToolTransformGridPrivate *private = grid->private;
|
|
gint dx1, dy1;
|
|
gint dx2, dy2;
|
|
gint dx3, dy3;
|
|
gint dx4, dy4;
|
|
gint x1, y1;
|
|
gint x2, y2;
|
|
|
|
if (! private->dynamic_handle_size)
|
|
{
|
|
*handle_w = PIKA_CANVAS_HANDLE_SIZE_LARGE;
|
|
*handle_h = PIKA_CANVAS_HANDLE_SIZE_LARGE;
|
|
|
|
return;
|
|
}
|
|
|
|
pika_canvas_item_transform_xy (private->guides,
|
|
private->tx1, private->ty1,
|
|
&dx1, &dy1);
|
|
pika_canvas_item_transform_xy (private->guides,
|
|
private->tx2, private->ty2,
|
|
&dx2, &dy2);
|
|
pika_canvas_item_transform_xy (private->guides,
|
|
private->tx3, private->ty3,
|
|
&dx3, &dy3);
|
|
pika_canvas_item_transform_xy (private->guides,
|
|
private->tx4, private->ty4,
|
|
&dx4, &dy4);
|
|
|
|
x1 = MIN4 (dx1, dx2, dx3, dx4);
|
|
y1 = MIN4 (dy1, dy2, dy3, dy4);
|
|
x2 = MAX4 (dx1, dx2, dx3, dx4);
|
|
y2 = MAX4 (dy1, dy2, dy3, dy4);
|
|
|
|
*handle_w = CLAMP ((x2 - x1) / 3,
|
|
MIN_HANDLE_SIZE, PIKA_CANVAS_HANDLE_SIZE_LARGE);
|
|
*handle_h = CLAMP ((y2 - y1) / 3,
|
|
MIN_HANDLE_SIZE, PIKA_CANVAS_HANDLE_SIZE_LARGE);
|
|
}
|
|
|
|
|
|
/* public functions */
|
|
|
|
PikaToolWidget *
|
|
pika_tool_transform_grid_new (PikaDisplayShell *shell,
|
|
const PikaMatrix3 *transform,
|
|
gdouble x1,
|
|
gdouble y1,
|
|
gdouble x2,
|
|
gdouble y2)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_DISPLAY_SHELL (shell), NULL);
|
|
|
|
return g_object_new (PIKA_TYPE_TOOL_TRANSFORM_GRID,
|
|
"shell", shell,
|
|
"transform", transform,
|
|
"x1", x1,
|
|
"y1", y1,
|
|
"x2", x2,
|
|
"y2", y2,
|
|
NULL);
|
|
}
|
|
|
|
|
|
/* protected functions */
|
|
|
|
PikaTransformHandle
|
|
pika_tool_transform_grid_get_handle (PikaToolTransformGrid *grid)
|
|
{
|
|
g_return_val_if_fail (PIKA_IS_TOOL_TRANSFORM_GRID (grid),
|
|
PIKA_TRANSFORM_HANDLE_NONE);
|
|
|
|
return grid->private->handle;
|
|
}
|