PIKApp/app/display/pikatoolhandlegrid.c

1222 lines
44 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
*
* pikatoolhandlegrid.c
* Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
*
* Based on PikaHandleTransformTool
*
* 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 "pikadisplayshell.h"
#include "pikatoolhandlegrid.h"
#include "pika-intl.h"
enum
{
PROP_0,
PROP_HANDLE_MODE,
PROP_N_HANDLES,
PROP_ORIG_X1,
PROP_ORIG_Y1,
PROP_ORIG_X2,
PROP_ORIG_Y2,
PROP_ORIG_X3,
PROP_ORIG_Y3,
PROP_ORIG_X4,
PROP_ORIG_Y4,
PROP_TRANS_X1,
PROP_TRANS_Y1,
PROP_TRANS_X2,
PROP_TRANS_Y2,
PROP_TRANS_X3,
PROP_TRANS_Y3,
PROP_TRANS_X4,
PROP_TRANS_Y4
};
struct _PikaToolHandleGridPrivate
{
PikaTransformHandleMode handle_mode; /* enum to be renamed */
gint n_handles;
PikaVector2 orig[4];
PikaVector2 trans[4];
gint handle;
gdouble last_x;
gdouble last_y;
gboolean hover;
gdouble mouse_x;
gdouble mouse_y;
PikaCanvasItem *handles[5];
};
/* local function prototypes */
static void pika_tool_handle_grid_constructed (GObject *object);
static void pika_tool_handle_grid_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void pika_tool_handle_grid_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
static void pika_tool_handle_grid_changed (PikaToolWidget *widget);
static gint pika_tool_handle_grid_button_press (PikaToolWidget *widget,
const PikaCoords *coords,
guint32 time,
GdkModifierType state,
PikaButtonPressType press_type);
static void pika_tool_handle_grid_button_release (PikaToolWidget *widget,
const PikaCoords *coords,
guint32 time,
GdkModifierType state,
PikaButtonReleaseType release_type);
static void pika_tool_handle_grid_motion (PikaToolWidget *widget,
const PikaCoords *coords,
guint32 time,
GdkModifierType state);
static PikaHit pika_tool_handle_grid_hit (PikaToolWidget *widget,
const PikaCoords *coords,
GdkModifierType state,
gboolean proximity);
static void pika_tool_handle_grid_hover (PikaToolWidget *widget,
const PikaCoords *coords,
GdkModifierType state,
gboolean proximity);
static void pika_tool_handle_grid_leave_notify (PikaToolWidget *widget);
static gboolean pika_tool_handle_grid_get_cursor (PikaToolWidget *widget,
const PikaCoords *coords,
GdkModifierType state,
PikaCursorType *cursor,
PikaToolCursorType *tool_cursor,
PikaCursorModifier *modifier);
static gint pika_tool_handle_grid_get_handle (PikaToolHandleGrid *grid,
const PikaCoords *coords);
static void pika_tool_handle_grid_update_hilight (PikaToolHandleGrid *grid);
static void pika_tool_handle_grid_update_matrix (PikaToolHandleGrid *grid);
static gboolean is_handle_position_valid (PikaToolHandleGrid *grid,
gint handle);
static void handle_micro_move (PikaToolHandleGrid *grid,
gint handle);
static inline gdouble calc_angle (gdouble ax,
gdouble ay,
gdouble bx,
gdouble by);
static inline gdouble calc_len (gdouble a,
gdouble b);
static inline gdouble calc_lineintersect_ratio (gdouble p1x,
gdouble p1y,
gdouble p2x,
gdouble p2y,
gdouble q1x,
gdouble q1y,
gdouble q2x,
gdouble q2y);
G_DEFINE_TYPE_WITH_PRIVATE (PikaToolHandleGrid, pika_tool_handle_grid,
PIKA_TYPE_TOOL_TRANSFORM_GRID)
#define parent_class pika_tool_handle_grid_parent_class
static void
pika_tool_handle_grid_class_init (PikaToolHandleGridClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
PikaToolWidgetClass *widget_class = PIKA_TOOL_WIDGET_CLASS (klass);
object_class->constructed = pika_tool_handle_grid_constructed;
object_class->set_property = pika_tool_handle_grid_set_property;
object_class->get_property = pika_tool_handle_grid_get_property;
widget_class->changed = pika_tool_handle_grid_changed;
widget_class->button_press = pika_tool_handle_grid_button_press;
widget_class->button_release = pika_tool_handle_grid_button_release;
widget_class->motion = pika_tool_handle_grid_motion;
widget_class->hit = pika_tool_handle_grid_hit;
widget_class->hover = pika_tool_handle_grid_hover;
widget_class->leave_notify = pika_tool_handle_grid_leave_notify;
widget_class->get_cursor = pika_tool_handle_grid_get_cursor;
g_object_class_install_property (object_class, PROP_HANDLE_MODE,
g_param_spec_enum ("handle-mode",
NULL, NULL,
PIKA_TYPE_TRANSFORM_HANDLE_MODE,
PIKA_HANDLE_MODE_ADD_TRANSFORM,
PIKA_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_N_HANDLES,
g_param_spec_int ("n-handles",
NULL, NULL,
0, 4, 0,
PIKA_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
g_object_class_install_property (object_class, PROP_ORIG_X1,
g_param_spec_double ("orig-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_ORIG_Y1,
g_param_spec_double ("orig-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_ORIG_X2,
g_param_spec_double ("orig-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_ORIG_Y2,
g_param_spec_double ("orig-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_ORIG_X3,
g_param_spec_double ("orig-x3",
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_ORIG_Y3,
g_param_spec_double ("orig-y3",
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_ORIG_X4,
g_param_spec_double ("orig-x4",
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_ORIG_Y4,
g_param_spec_double ("orig-y4",
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_TRANS_X1,
g_param_spec_double ("trans-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_TRANS_Y1,
g_param_spec_double ("trans-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_TRANS_X2,
g_param_spec_double ("trans-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_TRANS_Y2,
g_param_spec_double ("trans-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_TRANS_X3,
g_param_spec_double ("trans-x3",
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_TRANS_Y3,
g_param_spec_double ("trans-y3",
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_TRANS_X4,
g_param_spec_double ("trans-x4",
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_TRANS_Y4,
g_param_spec_double ("trans-y4",
NULL, NULL,
-PIKA_MAX_IMAGE_SIZE,
PIKA_MAX_IMAGE_SIZE,
0.0,
PIKA_PARAM_READWRITE |
G_PARAM_CONSTRUCT));
}
static void
pika_tool_handle_grid_init (PikaToolHandleGrid *grid)
{
grid->private = pika_tool_handle_grid_get_instance_private (grid);
}
static void
pika_tool_handle_grid_constructed (GObject *object)
{
PikaToolHandleGrid *grid = PIKA_TOOL_HANDLE_GRID (object);
PikaToolWidget *widget = PIKA_TOOL_WIDGET (object);
PikaToolHandleGridPrivate *private = grid->private;
gint i;
G_OBJECT_CLASS (parent_class)->constructed (object);
for (i = 0; i < 4; i++)
{
private->handles[i + 1] =
pika_tool_widget_add_handle (widget,
PIKA_HANDLE_CIRCLE,
0, 0,
PIKA_CANVAS_HANDLE_SIZE_CIRCLE,
PIKA_CANVAS_HANDLE_SIZE_CIRCLE,
PIKA_HANDLE_ANCHOR_CENTER);
}
pika_tool_handle_grid_changed (widget);
}
static void
pika_tool_handle_grid_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
PikaToolHandleGrid *grid = PIKA_TOOL_HANDLE_GRID (object);
PikaToolHandleGridPrivate *private = grid->private;
switch (property_id)
{
case PROP_HANDLE_MODE:
private->handle_mode = g_value_get_enum (value);
break;
case PROP_N_HANDLES:
private->n_handles = g_value_get_int (value);
break;
case PROP_ORIG_X1:
private->orig[0].x = g_value_get_double (value);
break;
case PROP_ORIG_Y1:
private->orig[0].y = g_value_get_double (value);
break;
case PROP_ORIG_X2:
private->orig[1].x = g_value_get_double (value);
break;
case PROP_ORIG_Y2:
private->orig[1].y = g_value_get_double (value);
break;
case PROP_ORIG_X3:
private->orig[2].x = g_value_get_double (value);
break;
case PROP_ORIG_Y3:
private->orig[2].y = g_value_get_double (value);
break;
case PROP_ORIG_X4:
private->orig[3].x = g_value_get_double (value);
break;
case PROP_ORIG_Y4:
private->orig[3].y = g_value_get_double (value);
break;
case PROP_TRANS_X1:
private->trans[0].x = g_value_get_double (value);
break;
case PROP_TRANS_Y1:
private->trans[0].y = g_value_get_double (value);
break;
case PROP_TRANS_X2:
private->trans[1].x = g_value_get_double (value);
break;
case PROP_TRANS_Y2:
private->trans[1].y = g_value_get_double (value);
break;
case PROP_TRANS_X3:
private->trans[2].x = g_value_get_double (value);
break;
case PROP_TRANS_Y3:
private->trans[2].y = g_value_get_double (value);
break;
case PROP_TRANS_X4:
private->trans[3].x = g_value_get_double (value);
break;
case PROP_TRANS_Y4:
private->trans[3].y = g_value_get_double (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
pika_tool_handle_grid_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
PikaToolHandleGrid *grid = PIKA_TOOL_HANDLE_GRID (object);
PikaToolHandleGridPrivate *private = grid->private;
switch (property_id)
{
case PROP_N_HANDLES:
g_value_set_int (value, private->n_handles);
break;
case PROP_HANDLE_MODE:
g_value_set_enum (value, private->handle_mode);
break;
case PROP_ORIG_X1:
g_value_set_double (value, private->orig[0].x);
break;
case PROP_ORIG_Y1:
g_value_set_double (value, private->orig[0].y);
break;
case PROP_ORIG_X2:
g_value_set_double (value, private->orig[1].x);
break;
case PROP_ORIG_Y2:
g_value_set_double (value, private->orig[1].y);
break;
case PROP_ORIG_X3:
g_value_set_double (value, private->orig[2].x);
break;
case PROP_ORIG_Y3:
g_value_set_double (value, private->orig[2].y);
break;
case PROP_ORIG_X4:
g_value_set_double (value, private->orig[3].x);
break;
case PROP_ORIG_Y4:
g_value_set_double (value, private->orig[3].y);
break;
case PROP_TRANS_X1:
g_value_set_double (value, private->trans[0].x);
break;
case PROP_TRANS_Y1:
g_value_set_double (value, private->trans[0].y);
break;
case PROP_TRANS_X2:
g_value_set_double (value, private->trans[1].x);
break;
case PROP_TRANS_Y2:
g_value_set_double (value, private->trans[1].y);
break;
case PROP_TRANS_X3:
g_value_set_double (value, private->trans[2].x);
break;
case PROP_TRANS_Y3:
g_value_set_double (value, private->trans[2].y);
break;
case PROP_TRANS_X4:
g_value_set_double (value, private->trans[3].x);
break;
case PROP_TRANS_Y4:
g_value_set_double (value, private->trans[3].y);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
pika_tool_handle_grid_changed (PikaToolWidget *widget)
{
PikaToolHandleGrid *grid = PIKA_TOOL_HANDLE_GRID (widget);
PikaToolHandleGridPrivate *private = grid->private;
gint i;
PIKA_TOOL_WIDGET_CLASS (parent_class)->changed (widget);
for (i = 0; i < 4; i++)
{
pika_canvas_handle_set_position (private->handles[i + 1],
private->trans[i].x,
private->trans[i].y);
pika_canvas_item_set_visible (private->handles[i + 1],
i < private->n_handles);
}
pika_tool_handle_grid_update_hilight (grid);
}
static gint
pika_tool_handle_grid_button_press (PikaToolWidget *widget,
const PikaCoords *coords,
guint32 time,
GdkModifierType state,
PikaButtonPressType press_type)
{
PikaToolHandleGrid *grid = PIKA_TOOL_HANDLE_GRID (widget);
PikaToolHandleGridPrivate *private = grid->private;
gint n_handles = private->n_handles;
gint active_handle = private->handle - 1;
PikaCanvasItem *dragged_handle = NULL;
switch (private->handle_mode)
{
case PIKA_HANDLE_MODE_ADD_TRANSFORM:
if (n_handles < 4 && active_handle == -1)
{
/* add handle */
PikaMatrix3 *matrix;
active_handle = n_handles;
private->trans[active_handle].x = coords->x;
private->trans[active_handle].y = coords->y;
private->n_handles++;
if (! is_handle_position_valid (grid, active_handle))
{
handle_micro_move (grid, active_handle);
}
/* handle was added, calculate new original position */
g_object_get (grid,
"transform", &matrix,
NULL);
pika_matrix3_invert (matrix);
pika_matrix3_transform_point (matrix,
private->trans[active_handle].x,
private->trans[active_handle].y,
&private->orig[active_handle].x,
&private->orig[active_handle].y);
g_free (matrix);
private->handle = active_handle + 1;
g_object_notify (G_OBJECT (grid), "n-handles");
}
else if (active_handle >= 0 &&
active_handle < 4)
{
/* existing handle is being dragged. don't set dragged_handle for
* newly-created handles, otherwise their snap offset will be wrong
*/
dragged_handle = private->handles[private->handle];
}
break;
case PIKA_HANDLE_MODE_MOVE:
if (active_handle >= 0 &&
active_handle < 4)
{
/* existing handle is being dragged */
dragged_handle = private->handles[private->handle];
}
/* check for valid position and calculating of OX0...OY3 is
* done on button release
*/
break;
case PIKA_HANDLE_MODE_REMOVE:
if (n_handles > 0 &&
active_handle >= 0 &&
active_handle < 4)
{
/* remove handle */
PikaVector2 temp = private->trans[active_handle];
PikaVector2 tempo = private->orig[active_handle];
gint i;
n_handles--;
private->n_handles--;
for (i = active_handle; i < n_handles; i++)
{
private->trans[i] = private->trans[i + 1];
private->orig[i] = private->orig[i + 1];
}
private->trans[n_handles] = temp;
private->orig[n_handles] = tempo;
g_object_notify (G_OBJECT (grid), "n-handles");
}
break;
}
/* ensure dragged handles snap to guides based on the handle center, not where
* the cursor grabbed them
*/
if (dragged_handle)
{
gdouble x, y;
pika_canvas_handle_get_position (dragged_handle,
&x,
&y);
pika_tool_widget_set_snap_offsets (widget,
SIGNED_ROUND (x - coords->x),
SIGNED_ROUND (y - coords->y),
0, 0);
}
private->last_x = coords->x;
private->last_y = coords->y;
return private->handle;
}
static void
pika_tool_handle_grid_button_release (PikaToolWidget *widget,
const PikaCoords *coords,
guint32 time,
GdkModifierType state,
PikaButtonReleaseType release_type)
{
PikaToolHandleGrid *grid = PIKA_TOOL_HANDLE_GRID (widget);
PikaToolHandleGridPrivate *private = grid->private;
gint active_handle = private->handle - 1;
if (private->handle_mode == PIKA_HANDLE_MODE_MOVE &&
active_handle >= 0 &&
active_handle < 4)
{
PikaMatrix3 *matrix;
if (! is_handle_position_valid (grid, active_handle))
{
handle_micro_move (grid, active_handle);
}
/* handle was moved, calculate new original position */
g_object_get (grid,
"transform", &matrix,
NULL);
pika_matrix3_invert (matrix);
pika_matrix3_transform_point (matrix,
private->trans[active_handle].x,
private->trans[active_handle].y,
&private->orig[active_handle].x,
&private->orig[active_handle].y);
g_free (matrix);
pika_tool_handle_grid_update_matrix (grid);
}
}
void
pika_tool_handle_grid_motion (PikaToolWidget *widget,
const PikaCoords *coords,
guint32 time,
GdkModifierType state)
{
PikaToolHandleGrid *grid = PIKA_TOOL_HANDLE_GRID (widget);
PikaToolHandleGridPrivate *private = grid->private;
gint n_handles = private->n_handles;
gint active_handle = private->handle - 1;
gdouble diff_x = coords->x - private->last_x;
gdouble diff_y = coords->y - private->last_y;
private->mouse_x = coords->x;
private->mouse_y = coords->y;
if (active_handle >= 0 && active_handle < 4)
{
if (private->handle_mode == PIKA_HANDLE_MODE_MOVE)
{
private->trans[active_handle].x += diff_x;
private->trans[active_handle].y += diff_y;
/* check for valid position and calculating of OX0...OY3 is
* done on button release hopefully this makes the code run
* faster Moving could be even faster if there was caching
* for the image preview
*/
pika_canvas_handle_set_position (private->handles[active_handle + 1],
private->trans[active_handle].x,
private->trans[active_handle].y);
}
else if (private->handle_mode == PIKA_HANDLE_MODE_ADD_TRANSFORM)
{
gdouble angle, angle_sin, angle_cos, scale;
PikaVector2 fixed_handles[3];
PikaVector2 oldpos[4];
PikaVector2 newpos[4];
gint i, j;
for (i = 0, j = 0; i < 4; i++)
{
/* Find all visible handles that are not being moved */
if (i < n_handles && i != active_handle)
{
fixed_handles[j] = private->trans[i];
j++;
}
newpos[i] = oldpos[i] = private->trans[i];
}
newpos[active_handle].x = oldpos[active_handle].x + diff_x;
newpos[active_handle].y = oldpos[active_handle].y + diff_y;
switch (n_handles)
{
case 1:
/* move */
for (i = 0; i < 4; i++)
{
newpos[i].x = oldpos[i].x + diff_x;
newpos[i].y = oldpos[i].y + diff_y;
}
break;
case 2:
/* rotate and keep-aspect-scale */
scale =
calc_len (newpos[active_handle].x - fixed_handles[0].x,
newpos[active_handle].y - fixed_handles[0].y) /
calc_len (oldpos[active_handle].x - fixed_handles[0].x,
oldpos[active_handle].y - fixed_handles[0].y);
angle = calc_angle (oldpos[active_handle].x - fixed_handles[0].x,
oldpos[active_handle].y - fixed_handles[0].y,
newpos[active_handle].x - fixed_handles[0].x,
newpos[active_handle].y - fixed_handles[0].y);
angle_sin = sin (angle);
angle_cos = cos (angle);
for (i = 2; i < 4; i++)
{
newpos[i].x =
fixed_handles[0].x +
scale * (angle_cos * (oldpos[i].x - fixed_handles[0].x) +
angle_sin * (oldpos[i].y - fixed_handles[0].y));
newpos[i].y =
fixed_handles[0].y +
scale * (-angle_sin * (oldpos[i].x - fixed_handles[0].x) +
angle_cos * (oldpos[i].y - fixed_handles[0].y));
}
break;
case 3:
/* shear and non-aspect-scale */
scale = calc_lineintersect_ratio (oldpos[3].x,
oldpos[3].y,
oldpos[active_handle].x,
oldpos[active_handle].y,
fixed_handles[0].x,
fixed_handles[0].y,
fixed_handles[1].x,
fixed_handles[1].y);
newpos[3].x = oldpos[3].x + scale * diff_x;
newpos[3].y = oldpos[3].y + scale * diff_y;
break;
}
for (i = 0; i < 4; i++)
{
private->trans[i] = newpos[i];
}
pika_tool_handle_grid_update_matrix (grid);
}
}
private->last_x = coords->x;
private->last_y = coords->y;
}
static PikaHit
pika_tool_handle_grid_hit (PikaToolWidget *widget,
const PikaCoords *coords,
GdkModifierType state,
gboolean proximity)
{
PikaToolHandleGrid *grid = PIKA_TOOL_HANDLE_GRID (widget);
PikaToolHandleGridPrivate *private = grid->private;
if (proximity)
{
gint handle = pika_tool_handle_grid_get_handle (grid, coords);
switch (private->handle_mode)
{
case PIKA_HANDLE_MODE_ADD_TRANSFORM:
if (handle > 0)
return PIKA_HIT_DIRECT;
else
return PIKA_HIT_INDIRECT;
break;
case PIKA_HANDLE_MODE_MOVE:
case PIKA_HANDLE_MODE_REMOVE:
if (private->handle > 0)
return PIKA_HIT_DIRECT;
break;
}
}
return PIKA_HIT_NONE;
}
static void
pika_tool_handle_grid_hover (PikaToolWidget *widget,
const PikaCoords *coords,
GdkModifierType state,
gboolean proximity)
{
PikaToolHandleGrid *grid = PIKA_TOOL_HANDLE_GRID (widget);
PikaToolHandleGridPrivate *private = grid->private;
gchar *status = NULL;
private->hover = TRUE;
private->mouse_x = coords->x;
private->mouse_y = coords->y;
private->handle = pika_tool_handle_grid_get_handle (grid, coords);
if (proximity)
{
GdkModifierType extend_mask = pika_get_extend_selection_mask ();
GdkModifierType toggle_mask = pika_get_toggle_behavior_mask ();
switch (private->handle_mode)
{
case PIKA_HANDLE_MODE_ADD_TRANSFORM:
if (private->handle > 0)
{
const gchar *s = NULL;
switch (private->n_handles)
{
case 1:
s = _("Click-Drag to move");
break;
case 2:
s = _("Click-Drag to rotate and scale");
break;
case 3:
s = _("Click-Drag to shear and scale");
break;
case 4:
s = _("Click-Drag to change perspective");
break;
}
status = pika_suggest_modifiers (s,
extend_mask | toggle_mask,
NULL, NULL, NULL);
}
else
{
if (private->n_handles < 4)
status = g_strdup (_("Click to add a handle"));
}
break;
case PIKA_HANDLE_MODE_MOVE:
if (private->handle > 0)
status = g_strdup (_("Click-Drag to move this handle"));
break;
case PIKA_HANDLE_MODE_REMOVE:
if (private->handle > 0)
status = g_strdup (_("Click-Drag to remove this handle"));
break;
}
}
pika_tool_widget_set_status (widget, status);
g_free (status);
pika_tool_handle_grid_update_hilight (grid);
}
static void
pika_tool_handle_grid_leave_notify (PikaToolWidget *widget)
{
PikaToolHandleGrid *grid = PIKA_TOOL_HANDLE_GRID (widget);
PikaToolHandleGridPrivate *private = grid->private;
private->hover = FALSE;
private->handle = 0;
pika_tool_handle_grid_update_hilight (grid);
PIKA_TOOL_WIDGET_CLASS (parent_class)->leave_notify (widget);
}
static gboolean
pika_tool_handle_grid_get_cursor (PikaToolWidget *widget,
const PikaCoords *coords,
GdkModifierType state,
PikaCursorType *cursor,
PikaToolCursorType *tool_cursor,
PikaCursorModifier *modifier)
{
PikaToolHandleGrid *grid = PIKA_TOOL_HANDLE_GRID (widget);
PikaToolHandleGridPrivate *private = grid->private;
*cursor = PIKA_CURSOR_CROSSHAIR_SMALL;
*tool_cursor = PIKA_TOOL_CURSOR_NONE;
*modifier = PIKA_CURSOR_MODIFIER_NONE;
switch (private->handle_mode)
{
case PIKA_HANDLE_MODE_ADD_TRANSFORM:
if (private->handle > 0)
{
switch (private->n_handles)
{
case 1:
*tool_cursor = PIKA_TOOL_CURSOR_MOVE;
break;
case 2:
*tool_cursor = PIKA_TOOL_CURSOR_ROTATE;
break;
case 3:
*tool_cursor = PIKA_TOOL_CURSOR_SHEAR;
break;
case 4:
*tool_cursor = PIKA_TOOL_CURSOR_PERSPECTIVE;
break;
}
}
else
{
if (private->n_handles < 4)
*modifier = PIKA_CURSOR_MODIFIER_PLUS;
else
*modifier = PIKA_CURSOR_MODIFIER_BAD;
}
break;
case PIKA_HANDLE_MODE_MOVE:
if (private->handle > 0)
*modifier = PIKA_CURSOR_MODIFIER_MOVE;
else
*modifier = PIKA_CURSOR_MODIFIER_BAD;
break;
case PIKA_HANDLE_MODE_REMOVE:
if (private->handle > 0)
*modifier = PIKA_CURSOR_MODIFIER_MINUS;
else
*modifier = PIKA_CURSOR_MODIFIER_BAD;
break;
}
return TRUE;
}
static gint
pika_tool_handle_grid_get_handle (PikaToolHandleGrid *grid,
const PikaCoords *coords)
{
PikaToolHandleGridPrivate *private = grid->private;
gint i;
for (i = 0; i < 4; i++)
{
if (private->handles[i + 1] &&
pika_canvas_item_hit (private->handles[i + 1],
coords->x, coords->y))
{
return i + 1;
}
}
return 0;
}
static void
pika_tool_handle_grid_update_hilight (PikaToolHandleGrid *grid)
{
PikaToolHandleGridPrivate *private = grid->private;
gint i;
for (i = 0; i < 4; i++)
{
PikaCanvasItem *item = private->handles[i + 1];
if (item)
{
gdouble diameter = PIKA_CANVAS_HANDLE_SIZE_CIRCLE;
if (private->hover)
{
diameter = pika_canvas_handle_calc_size (
item,
private->mouse_x,
private->mouse_y,
PIKA_CANVAS_HANDLE_SIZE_CIRCLE,
2 * PIKA_CANVAS_HANDLE_SIZE_CIRCLE);
}
pika_canvas_handle_set_size (item, diameter, diameter);
pika_canvas_item_set_highlight (item, (i + 1) == private->handle);
}
}
}
static void
pika_tool_handle_grid_update_matrix (PikaToolHandleGrid *grid)
{
PikaToolHandleGridPrivate *private = grid->private;
PikaMatrix3 transform;
gboolean transform_valid;
pika_matrix3_identity (&transform);
transform_valid = pika_transform_matrix_generic (&transform,
private->orig,
private->trans);
g_object_set (grid,
"transform", &transform,
"show-guides", transform_valid,
NULL);
}
/* check if a handle is not on the connection line of two other handles */
static gboolean
is_handle_position_valid (PikaToolHandleGrid *grid,
gint handle)
{
PikaToolHandleGridPrivate *private = grid->private;
gint i, j, k;
for (i = 0; i < 2; i++)
{
for (j = i + 1; j < 3; j++)
{
for (k = j + 1; i < 4; i++)
{
if (handle == i ||
handle == j ||
handle == k)
{
if ((private->trans[i].x -
private->trans[j].x) *
(private->trans[j].y -
private->trans[k].y) ==
(private->trans[j].x -
private->trans[k].x) *
(private->trans[i].y -
private->trans[j].y))
{
return FALSE;
}
}
}
}
}
return TRUE;
}
/* three handles on a line causes problems.
* Let's move the new handle around a bit to find a better position */
static void
handle_micro_move (PikaToolHandleGrid *grid,
gint handle)
{
PikaToolHandleGridPrivate *private = grid->private;
gdouble posx = private->trans[handle].x;
gdouble posy = private->trans[handle].y;
gdouble dx, dy;
for (dx = -0.1; dx < 0.11; dx += 0.1)
{
private->trans[handle].x = posx + dx;
for (dy = -0.1; dy < 0.11; dy += 0.1)
{
private->trans[handle].y = posy + dy;
if (is_handle_position_valid (grid, handle))
{
return;
}
}
}
}
/* finds the clockwise angle between the vectors given, 0-2π */
static inline gdouble
calc_angle (gdouble ax,
gdouble ay,
gdouble bx,
gdouble by)
{
gdouble angle;
gdouble direction;
gdouble length = sqrt ((ax * ax + ay * ay) * (bx * bx + by * by));
angle = acos ((ax * bx + ay * by) / length);
direction = ax * by - ay * bx;
return ((direction < 0) ? angle : 2 * G_PI - angle);
}
static inline gdouble
calc_len (gdouble a,
gdouble b)
{
return sqrt (a * a + b * b);
}
/* imagine two lines, one through the points p1 and p2, the other one
* through the points q1 and q2. Find the intersection point r.
* Calculate (distance p1 to r)/(distance p2 to r)
*/
static inline gdouble
calc_lineintersect_ratio (gdouble p1x, gdouble p1y,
gdouble p2x, gdouble p2y,
gdouble q1x, gdouble q1y,
gdouble q2x, gdouble q2y)
{
gdouble denom, u;
denom = (q2y - q1y) * (p2x - p1x) - (q2x - q1x) * (p2y - p1y);
if (denom == 0.0)
{
/* u is infinite, so u/(u-1) is 1 */
return 1.0;
}
u = (q2y - q1y) * (q1x - p1x) - (q1y - p1y) * (q2x - q1x);
u /= denom;
return u / (u - 1);
}
/* public functions */
PikaToolWidget *
pika_tool_handle_grid_new (PikaDisplayShell *shell,
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_HANDLE_GRID,
"shell", shell,
"x1", x1,
"y1", y1,
"x2", x2,
"y2", y2,
"clip-guides", TRUE,
NULL);
}