1222 lines
44 KiB
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);
|
|
}
|