/* 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 * * 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 . */ #include "config.h" #include #include #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); }