summaryrefslogtreecommitdiffstats
path: root/app/display/gimptoolhandlegrid.c
diff options
context:
space:
mode:
Diffstat (limited to 'app/display/gimptoolhandlegrid.c')
-rw-r--r--app/display/gimptoolhandlegrid.c1217
1 files changed, 1217 insertions, 0 deletions
diff --git a/app/display/gimptoolhandlegrid.c b/app/display/gimptoolhandlegrid.c
new file mode 100644
index 0000000..06dc156
--- /dev/null
+++ b/app/display/gimptoolhandlegrid.c
@@ -0,0 +1,1217 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolhandlegrid.c
+ * Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
+ *
+ * Based on GimpHandleTransformTool
+ *
+ * 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 "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "core/gimp-transform-utils.h"
+#include "core/gimp-utils.h"
+
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpcanvashandle.h"
+#include "gimpdisplayshell.h"
+#include "gimptoolhandlegrid.h"
+
+#include "gimp-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 _GimpToolHandleGridPrivate
+{
+ GimpTransformHandleMode handle_mode; /* enum to be renamed */
+
+ gint n_handles;
+ GimpVector2 orig[4];
+ GimpVector2 trans[4];
+
+ gint handle;
+ gdouble last_x;
+ gdouble last_y;
+
+ gboolean hover;
+ gdouble mouse_x;
+ gdouble mouse_y;
+
+ GimpCanvasItem *handles[5];
+};
+
+
+/* local function prototypes */
+
+static void gimp_tool_handle_grid_constructed (GObject *object);
+static void gimp_tool_handle_grid_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_tool_handle_grid_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_tool_handle_grid_changed (GimpToolWidget *widget);
+static gint gimp_tool_handle_grid_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type);
+static void gimp_tool_handle_grid_button_release (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type);
+static void gimp_tool_handle_grid_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state);
+static GimpHit gimp_tool_handle_grid_hit (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity);
+static void gimp_tool_handle_grid_hover (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity);
+static void gimp_tool_handle_grid_leave_notify (GimpToolWidget *widget);
+static gboolean gimp_tool_handle_grid_get_cursor (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpCursorType *cursor,
+ GimpToolCursorType *tool_cursor,
+ GimpCursorModifier *modifier);
+
+static gint gimp_tool_handle_grid_get_handle (GimpToolHandleGrid *grid,
+ const GimpCoords *coords);
+static void gimp_tool_handle_grid_update_hilight (GimpToolHandleGrid *grid);
+static void gimp_tool_handle_grid_update_matrix (GimpToolHandleGrid *grid);
+
+static gboolean is_handle_position_valid (GimpToolHandleGrid *grid,
+ gint handle);
+static void handle_micro_move (GimpToolHandleGrid *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 (GimpToolHandleGrid, gimp_tool_handle_grid,
+ GIMP_TYPE_TOOL_TRANSFORM_GRID)
+
+#define parent_class gimp_tool_handle_grid_parent_class
+
+
+static void
+gimp_tool_handle_grid_class_init (GimpToolHandleGridClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass);
+
+ object_class->constructed = gimp_tool_handle_grid_constructed;
+ object_class->set_property = gimp_tool_handle_grid_set_property;
+ object_class->get_property = gimp_tool_handle_grid_get_property;
+
+ widget_class->changed = gimp_tool_handle_grid_changed;
+ widget_class->button_press = gimp_tool_handle_grid_button_press;
+ widget_class->button_release = gimp_tool_handle_grid_button_release;
+ widget_class->motion = gimp_tool_handle_grid_motion;
+ widget_class->hit = gimp_tool_handle_grid_hit;
+ widget_class->hover = gimp_tool_handle_grid_hover;
+ widget_class->leave_notify = gimp_tool_handle_grid_leave_notify;
+ widget_class->get_cursor = gimp_tool_handle_grid_get_cursor;
+
+ g_object_class_install_property (object_class, PROP_HANDLE_MODE,
+ g_param_spec_enum ("handle-mode",
+ NULL, NULL,
+ GIMP_TYPE_TRANSFORM_HANDLE_MODE,
+ GIMP_HANDLE_MODE_ADD_TRANSFORM,
+ GIMP_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,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_ORIG_X1,
+ g_param_spec_double ("orig-x1",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_ORIG_Y1,
+ g_param_spec_double ("orig-y1",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_ORIG_X2,
+ g_param_spec_double ("orig-x2",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_ORIG_Y2,
+ g_param_spec_double ("orig-y2",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_ORIG_X3,
+ g_param_spec_double ("orig-x3",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_ORIG_Y3,
+ g_param_spec_double ("orig-y3",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_ORIG_X4,
+ g_param_spec_double ("orig-x4",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_ORIG_Y4,
+ g_param_spec_double ("orig-y4",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_TRANS_X1,
+ g_param_spec_double ("trans-x1",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_TRANS_Y1,
+ g_param_spec_double ("trans-y1",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_TRANS_X2,
+ g_param_spec_double ("trans-x2",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_TRANS_Y2,
+ g_param_spec_double ("trans-y2",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_TRANS_X3,
+ g_param_spec_double ("trans-x3",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_TRANS_Y3,
+ g_param_spec_double ("trans-y3",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_TRANS_X4,
+ g_param_spec_double ("trans-x4",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+
+ g_object_class_install_property (object_class, PROP_TRANS_Y4,
+ g_param_spec_double ("trans-y4",
+ NULL, NULL,
+ -GIMP_MAX_IMAGE_SIZE,
+ GIMP_MAX_IMAGE_SIZE,
+ 0.0,
+ GIMP_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+}
+
+static void
+gimp_tool_handle_grid_init (GimpToolHandleGrid *grid)
+{
+ grid->private = gimp_tool_handle_grid_get_instance_private (grid);
+}
+
+static void
+gimp_tool_handle_grid_constructed (GObject *object)
+{
+ GimpToolHandleGrid *grid = GIMP_TOOL_HANDLE_GRID (object);
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (object);
+ GimpToolHandleGridPrivate *private = grid->private;
+ gint i;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ for (i = 0; i < 4; i++)
+ {
+ private->handles[i + 1] =
+ gimp_tool_widget_add_handle (widget,
+ GIMP_HANDLE_CIRCLE,
+ 0, 0,
+ GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+ GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+ GIMP_HANDLE_ANCHOR_CENTER);
+ }
+
+ gimp_tool_handle_grid_changed (widget);
+}
+
+static void
+gimp_tool_handle_grid_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolHandleGrid *grid = GIMP_TOOL_HANDLE_GRID (object);
+ GimpToolHandleGridPrivate *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
+gimp_tool_handle_grid_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GimpToolHandleGrid *grid = GIMP_TOOL_HANDLE_GRID (object);
+ GimpToolHandleGridPrivate *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
+gimp_tool_handle_grid_changed (GimpToolWidget *widget)
+{
+ GimpToolHandleGrid *grid = GIMP_TOOL_HANDLE_GRID (widget);
+ GimpToolHandleGridPrivate *private = grid->private;
+ gint i;
+
+ GIMP_TOOL_WIDGET_CLASS (parent_class)->changed (widget);
+
+ for (i = 0; i < 4; i++)
+ {
+ gimp_canvas_handle_set_position (private->handles[i + 1],
+ private->trans[i].x,
+ private->trans[i].y);
+ gimp_canvas_item_set_visible (private->handles[i + 1],
+ i < private->n_handles);
+ }
+
+ gimp_tool_handle_grid_update_hilight (grid);
+}
+
+static gint
+gimp_tool_handle_grid_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type)
+{
+ GimpToolHandleGrid *grid = GIMP_TOOL_HANDLE_GRID (widget);
+ GimpToolHandleGridPrivate *private = grid->private;
+ gint n_handles = private->n_handles;
+ gint active_handle = private->handle - 1;
+ GimpCanvasItem *dragged_handle = NULL;
+
+ switch (private->handle_mode)
+ {
+ case GIMP_HANDLE_MODE_ADD_TRANSFORM:
+ if (n_handles < 4 && active_handle == -1)
+ {
+ /* add handle */
+
+ GimpMatrix3 *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);
+
+ gimp_matrix3_invert (matrix);
+ gimp_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 GIMP_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 GIMP_HANDLE_MODE_REMOVE:
+ if (n_handles > 0 &&
+ active_handle >= 0 &&
+ active_handle < 4)
+ {
+ /* remove handle */
+
+ GimpVector2 temp = private->trans[active_handle];
+ GimpVector2 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;
+
+ gimp_canvas_handle_get_position (dragged_handle,
+ &x,
+ &y);
+ gimp_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
+gimp_tool_handle_grid_button_release (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type)
+{
+ GimpToolHandleGrid *grid = GIMP_TOOL_HANDLE_GRID (widget);
+ GimpToolHandleGridPrivate *private = grid->private;
+ gint active_handle = private->handle - 1;
+
+ if (private->handle_mode == GIMP_HANDLE_MODE_MOVE &&
+ active_handle >= 0 &&
+ active_handle < 4)
+ {
+ GimpMatrix3 *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);
+
+ gimp_matrix3_invert (matrix);
+ gimp_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);
+
+ gimp_tool_handle_grid_update_matrix (grid);
+ }
+}
+
+void
+gimp_tool_handle_grid_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state)
+{
+ GimpToolHandleGrid *grid = GIMP_TOOL_HANDLE_GRID (widget);
+ GimpToolHandleGridPrivate *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 == GIMP_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
+ */
+ gimp_canvas_handle_set_position (private->handles[active_handle + 1],
+ private->trans[active_handle].x,
+ private->trans[active_handle].y);
+ }
+ else if (private->handle_mode == GIMP_HANDLE_MODE_ADD_TRANSFORM)
+ {
+ gdouble angle, angle_sin, angle_cos, scale;
+ GimpVector2 fixed_handles[3];
+ GimpVector2 oldpos[4];
+ GimpVector2 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];
+ }
+
+ gimp_tool_handle_grid_update_matrix (grid);
+ }
+ }
+
+ private->last_x = coords->x;
+ private->last_y = coords->y;
+}
+
+static GimpHit
+gimp_tool_handle_grid_hit (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ GimpToolHandleGrid *grid = GIMP_TOOL_HANDLE_GRID (widget);
+ GimpToolHandleGridPrivate *private = grid->private;
+
+ if (proximity)
+ {
+ gint handle = gimp_tool_handle_grid_get_handle (grid, coords);
+
+ switch (private->handle_mode)
+ {
+ case GIMP_HANDLE_MODE_ADD_TRANSFORM:
+ if (handle > 0)
+ return GIMP_HIT_DIRECT;
+ else
+ return GIMP_HIT_INDIRECT;
+ break;
+
+ case GIMP_HANDLE_MODE_MOVE:
+ case GIMP_HANDLE_MODE_REMOVE:
+ if (private->handle > 0)
+ return GIMP_HIT_DIRECT;
+ break;
+ }
+ }
+
+ return GIMP_HIT_NONE;
+}
+
+static void
+gimp_tool_handle_grid_hover (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ GimpToolHandleGrid *grid = GIMP_TOOL_HANDLE_GRID (widget);
+ GimpToolHandleGridPrivate *private = grid->private;
+ gchar *status = NULL;
+
+ private->hover = TRUE;
+ private->mouse_x = coords->x;
+ private->mouse_y = coords->y;
+
+ private->handle = gimp_tool_handle_grid_get_handle (grid, coords);
+
+ if (proximity)
+ {
+ GdkModifierType extend_mask = gimp_get_extend_selection_mask ();
+ GdkModifierType toggle_mask = gimp_get_toggle_behavior_mask ();
+
+ switch (private->handle_mode)
+ {
+ case GIMP_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 = gimp_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 GIMP_HANDLE_MODE_MOVE:
+ if (private->handle > 0)
+ status = g_strdup (_("Click-Drag to move this handle"));
+ break;
+
+ case GIMP_HANDLE_MODE_REMOVE:
+ if (private->handle > 0)
+ status = g_strdup (_("Click-Drag to remove this handle"));
+ break;
+ }
+ }
+
+ gimp_tool_widget_set_status (widget, status);
+ g_free (status);
+
+ gimp_tool_handle_grid_update_hilight (grid);
+}
+
+static void
+gimp_tool_handle_grid_leave_notify (GimpToolWidget *widget)
+{
+ GimpToolHandleGrid *grid = GIMP_TOOL_HANDLE_GRID (widget);
+ GimpToolHandleGridPrivate *private = grid->private;
+
+ private->hover = FALSE;
+ private->handle = 0;
+
+ gimp_tool_handle_grid_update_hilight (grid);
+
+ GIMP_TOOL_WIDGET_CLASS (parent_class)->leave_notify (widget);
+}
+
+static gboolean
+gimp_tool_handle_grid_get_cursor (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpCursorType *cursor,
+ GimpToolCursorType *tool_cursor,
+ GimpCursorModifier *modifier)
+{
+ GimpToolHandleGrid *grid = GIMP_TOOL_HANDLE_GRID (widget);
+ GimpToolHandleGridPrivate *private = grid->private;
+
+ *cursor = GIMP_CURSOR_CROSSHAIR_SMALL;
+ *tool_cursor = GIMP_TOOL_CURSOR_NONE;
+ *modifier = GIMP_CURSOR_MODIFIER_NONE;
+
+ switch (private->handle_mode)
+ {
+ case GIMP_HANDLE_MODE_ADD_TRANSFORM:
+ if (private->handle > 0)
+ {
+ switch (private->n_handles)
+ {
+ case 1:
+ *tool_cursor = GIMP_TOOL_CURSOR_MOVE;
+ break;
+ case 2:
+ *tool_cursor = GIMP_TOOL_CURSOR_ROTATE;
+ break;
+ case 3:
+ *tool_cursor = GIMP_TOOL_CURSOR_SHEAR;
+ break;
+ case 4:
+ *tool_cursor = GIMP_TOOL_CURSOR_PERSPECTIVE;
+ break;
+ }
+ }
+ else
+ {
+ if (private->n_handles < 4)
+ *modifier = GIMP_CURSOR_MODIFIER_PLUS;
+ else
+ *modifier = GIMP_CURSOR_MODIFIER_BAD;
+ }
+ break;
+
+ case GIMP_HANDLE_MODE_MOVE:
+ if (private->handle > 0)
+ *modifier = GIMP_CURSOR_MODIFIER_MOVE;
+ else
+ *modifier = GIMP_CURSOR_MODIFIER_BAD;
+ break;
+
+ case GIMP_HANDLE_MODE_REMOVE:
+ if (private->handle > 0)
+ *modifier = GIMP_CURSOR_MODIFIER_MINUS;
+ else
+ *modifier = GIMP_CURSOR_MODIFIER_BAD;
+ break;
+ }
+
+ return TRUE;
+}
+
+static gint
+gimp_tool_handle_grid_get_handle (GimpToolHandleGrid *grid,
+ const GimpCoords *coords)
+{
+ GimpToolHandleGridPrivate *private = grid->private;
+ gint i;
+
+ for (i = 0; i < 4; i++)
+ {
+ if (private->handles[i + 1] &&
+ gimp_canvas_item_hit (private->handles[i + 1],
+ coords->x, coords->y))
+ {
+ return i + 1;
+ }
+ }
+
+ return 0;
+}
+
+static void
+gimp_tool_handle_grid_update_hilight (GimpToolHandleGrid *grid)
+{
+ GimpToolHandleGridPrivate *private = grid->private;
+ gint i;
+
+ for (i = 0; i < 4; i++)
+ {
+ GimpCanvasItem *item = private->handles[i + 1];
+
+ if (item)
+ {
+ gdouble diameter = GIMP_CANVAS_HANDLE_SIZE_CIRCLE;
+
+ if (private->hover)
+ {
+ diameter = gimp_canvas_handle_calc_size (
+ item,
+ private->mouse_x,
+ private->mouse_y,
+ GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+ 2 * GIMP_CANVAS_HANDLE_SIZE_CIRCLE);
+ }
+
+ gimp_canvas_handle_set_size (item, diameter, diameter);
+ gimp_canvas_item_set_highlight (item, (i + 1) == private->handle);
+ }
+ }
+}
+
+static void
+gimp_tool_handle_grid_update_matrix (GimpToolHandleGrid *grid)
+{
+ GimpToolHandleGridPrivate *private = grid->private;
+ GimpMatrix3 transform;
+ gboolean transform_valid;
+
+ gimp_matrix3_identity (&transform);
+ transform_valid = gimp_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 (GimpToolHandleGrid *grid,
+ gint handle)
+{
+ GimpToolHandleGridPrivate *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 (GimpToolHandleGrid *grid,
+ gint handle)
+{
+ GimpToolHandleGridPrivate *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 */
+
+GimpToolWidget *
+gimp_tool_handle_grid_new (GimpDisplayShell *shell,
+ gdouble x1,
+ gdouble y1,
+ gdouble x2,
+ gdouble y2)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return g_object_new (GIMP_TYPE_TOOL_HANDLE_GRID,
+ "shell", shell,
+ "x1", x1,
+ "y1", y1,
+ "x2", x2,
+ "y2", y2,
+ "clip-guides", TRUE,
+ NULL);
+}