summaryrefslogtreecommitdiffstats
path: root/app/display/gimptoolpolygon.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 03:13:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 03:13:10 +0000
commit3c57dd931145d43f2b0aef96c4d178135956bf91 (patch)
tree3de698981e9f0cc2c4f9569b19a5f3595e741f6b /app/display/gimptoolpolygon.c
parentInitial commit. (diff)
downloadgimp-3c57dd931145d43f2b0aef96c4d178135956bf91.tar.xz
gimp-3c57dd931145d43f2b0aef96c4d178135956bf91.zip
Adding upstream version 2.10.36.upstream/2.10.36
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'app/display/gimptoolpolygon.c')
-rw-r--r--app/display/gimptoolpolygon.c1495
1 files changed, 1495 insertions, 0 deletions
diff --git a/app/display/gimptoolpolygon.c b/app/display/gimptoolpolygon.c
new file mode 100644
index 0000000..4f2c20b
--- /dev/null
+++ b/app/display/gimptoolpolygon.c
@@ -0,0 +1,1495 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * gimptoolpolygon.c
+ * Copyright (C) 2017 Michael Natterer <mitch@gimp.org>
+ *
+ * Based on GimpFreeSelectTool
+ *
+ * Major improvement to support polygonal segments
+ * Copyright (C) 2008 Martin Nordholts
+ *
+ * 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 <string.h>
+
+#include <gegl.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+
+#include "libgimpbase/gimpbase.h"
+#include "libgimpmath/gimpmath.h"
+
+#include "display-types.h"
+
+#include "core/gimp-utils.h"
+#include "core/gimpmarshal.h"
+
+#include "widgets/gimpwidgets-utils.h"
+
+#include "gimpcanvashandle.h"
+#include "gimpcanvasline.h"
+#include "gimpcanvaspolygon.h"
+#include "gimpdisplayshell.h"
+#include "gimpdisplayshell-utils.h"
+#include "gimptoolpolygon.h"
+
+#include "gimp-intl.h"
+
+
+#define POINT_GRAB_THRESHOLD_SQ SQR (GIMP_CANVAS_HANDLE_SIZE_CIRCLE / 2)
+#define POINT_SHOW_THRESHOLD_SQ SQR (GIMP_CANVAS_HANDLE_SIZE_CIRCLE * 7)
+#define N_ITEMS_PER_ALLOC 1024
+#define INVALID_INDEX (-1)
+#define NO_CLICK_TIME_AVAILABLE 0
+
+
+enum
+{
+ CHANGE_COMPLETE,
+ LAST_SIGNAL
+};
+
+
+struct _GimpToolPolygonPrivate
+{
+ /* Index of grabbed segment index. */
+ gint grabbed_segment_index;
+
+ /* We need to keep track of a number of points when we move a
+ * segment vertex
+ */
+ GimpVector2 *saved_points_lower_segment;
+ GimpVector2 *saved_points_higher_segment;
+ gint max_n_saved_points_lower_segment;
+ gint max_n_saved_points_higher_segment;
+
+ /* Keeps track whether or not a modification of the polygon has been
+ * made between _button_press and _button_release
+ */
+ gboolean polygon_modified;
+
+ /* Point which is used to draw the polygon but which is not part of
+ * it yet
+ */
+ GimpVector2 pending_point;
+ gboolean show_pending_point;
+
+ /* The points of the polygon */
+ GimpVector2 *points;
+ gint max_n_points;
+
+ /* The number of points actually in use */
+ gint n_points;
+
+
+ /* Any int array containing the indices for the points in the
+ * polygon that connects different segments together
+ */
+ gint *segment_indices;
+ gint max_n_segment_indices;
+
+ /* The number of segment indices actually in use */
+ gint n_segment_indices;
+
+ /* Is the polygon closed? */
+ gboolean polygon_closed;
+
+ /* Whether or not to constrain the angle for newly created polygonal
+ * segments.
+ */
+ gboolean constrain_angle;
+
+ /* Whether or not to suppress handles (so that new segments can be
+ * created immediately after an existing segment vertex.
+ */
+ gboolean suppress_handles;
+
+ /* Last _oper_update or _motion coords */
+ gboolean hover;
+ GimpVector2 last_coords;
+
+ /* A double-click commits the selection, keep track of last
+ * click-time and click-position.
+ */
+ guint32 last_click_time;
+ GimpCoords last_click_coord;
+
+ /* Are we in a click-drag-release? */
+ gboolean button_down;
+
+ GimpCanvasItem *polygon;
+ GimpCanvasItem *pending_line;
+ GimpCanvasItem *closing_line;
+ GPtrArray *handles;
+};
+
+
+/* local function prototypes */
+
+static void gimp_tool_polygon_constructed (GObject *object);
+static void gimp_tool_polygon_finalize (GObject *object);
+static void gimp_tool_polygon_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void gimp_tool_polygon_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void gimp_tool_polygon_changed (GimpToolWidget *widget);
+static gint gimp_tool_polygon_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type);
+static void gimp_tool_polygon_button_release (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type);
+static void gimp_tool_polygon_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state);
+static GimpHit gimp_tool_polygon_hit (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity);
+static void gimp_tool_polygon_hover (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity);
+static void gimp_tool_polygon_leave_notify (GimpToolWidget *widget);
+static gboolean gimp_tool_polygon_key_press (GimpToolWidget *widget,
+ GdkEventKey *kevent);
+static void gimp_tool_polygon_motion_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state);
+static void gimp_tool_polygon_hover_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state);
+static gboolean gimp_tool_polygon_get_cursor (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpCursorType *cursor,
+ GimpToolCursorType *tool_cursor,
+ GimpCursorModifier *modifier);
+
+static void gimp_tool_polygon_change_complete (GimpToolPolygon *polygon);
+
+static gint gimp_tool_polygon_get_segment_index (GimpToolPolygon *polygon,
+ const GimpCoords *coords);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (GimpToolPolygon, gimp_tool_polygon,
+ GIMP_TYPE_TOOL_WIDGET)
+
+#define parent_class gimp_tool_polygon_parent_class
+
+static guint polygon_signals[LAST_SIGNAL] = { 0, };
+
+static const GimpVector2 vector2_zero = { 0.0, 0.0 };
+
+
+static void
+gimp_tool_polygon_class_init (GimpToolPolygonClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GimpToolWidgetClass *widget_class = GIMP_TOOL_WIDGET_CLASS (klass);
+
+ object_class->constructed = gimp_tool_polygon_constructed;
+ object_class->finalize = gimp_tool_polygon_finalize;
+ object_class->set_property = gimp_tool_polygon_set_property;
+ object_class->get_property = gimp_tool_polygon_get_property;
+
+ widget_class->changed = gimp_tool_polygon_changed;
+ widget_class->button_press = gimp_tool_polygon_button_press;
+ widget_class->button_release = gimp_tool_polygon_button_release;
+ widget_class->motion = gimp_tool_polygon_motion;
+ widget_class->hit = gimp_tool_polygon_hit;
+ widget_class->hover = gimp_tool_polygon_hover;
+ widget_class->leave_notify = gimp_tool_polygon_leave_notify;
+ widget_class->key_press = gimp_tool_polygon_key_press;
+ widget_class->motion_modifier = gimp_tool_polygon_motion_modifier;
+ widget_class->hover_modifier = gimp_tool_polygon_hover_modifier;
+ widget_class->get_cursor = gimp_tool_polygon_get_cursor;
+
+ polygon_signals[CHANGE_COMPLETE] =
+ g_signal_new ("change-complete",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (GimpToolPolygonClass, change_complete),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__VOID,
+ G_TYPE_NONE, 0);
+}
+
+static void
+gimp_tool_polygon_init (GimpToolPolygon *polygon)
+{
+ polygon->private = gimp_tool_polygon_get_instance_private (polygon);
+
+ polygon->private->grabbed_segment_index = INVALID_INDEX;
+ polygon->private->last_click_time = NO_CLICK_TIME_AVAILABLE;
+}
+
+static void
+gimp_tool_polygon_constructed (GObject *object)
+{
+ GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (object);
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (object);
+ GimpToolPolygonPrivate *private = polygon->private;
+ GimpCanvasGroup *stroke_group;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ stroke_group = gimp_tool_widget_add_stroke_group (widget);
+
+ gimp_tool_widget_push_group (widget, stroke_group);
+
+ private->polygon = gimp_tool_widget_add_polygon (widget, NULL,
+ NULL, 0, FALSE);
+
+ private->pending_line = gimp_tool_widget_add_line (widget, 0, 0, 0, 0);
+ private->closing_line = gimp_tool_widget_add_line (widget, 0, 0, 0, 0);
+
+ gimp_tool_widget_pop_group (widget);
+
+ private->handles = g_ptr_array_new ();
+
+ gimp_tool_polygon_changed (widget);
+}
+
+static void
+gimp_tool_polygon_finalize (GObject *object)
+{
+ GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (object);
+ GimpToolPolygonPrivate *private = polygon->private;
+
+ g_free (private->points);
+ g_free (private->segment_indices);
+ g_free (private->saved_points_lower_segment);
+ g_free (private->saved_points_higher_segment);
+
+ g_clear_pointer (&private->handles, g_ptr_array_unref);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gimp_tool_polygon_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id)
+ {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tool_polygon_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ switch (property_id)
+ {
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gimp_tool_polygon_get_segment (GimpToolPolygon *polygon,
+ GimpVector2 **points,
+ gint *n_points,
+ gint segment_start,
+ gint segment_end)
+{
+ GimpToolPolygonPrivate *priv = polygon->private;
+
+ *points = &priv->points[priv->segment_indices[segment_start]];
+ *n_points = priv->segment_indices[segment_end] -
+ priv->segment_indices[segment_start] +
+ 1;
+}
+
+static void
+gimp_tool_polygon_get_segment_point (GimpToolPolygon *polygon,
+ gdouble *start_point_x,
+ gdouble *start_point_y,
+ gint segment_index)
+{
+ GimpToolPolygonPrivate *priv = polygon->private;
+
+ *start_point_x = priv->points[priv->segment_indices[segment_index]].x;
+ *start_point_y = priv->points[priv->segment_indices[segment_index]].y;
+}
+
+static gboolean
+gimp_tool_polygon_should_close (GimpToolPolygon *polygon,
+ guint32 time,
+ const GimpCoords *coords)
+{
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (polygon);
+ GimpToolPolygonPrivate *priv = polygon->private;
+ gboolean double_click = FALSE;
+ gdouble dist;
+
+ if (priv->polygon_modified ||
+ priv->n_segment_indices < 1 ||
+ priv->n_points < 3 ||
+ priv->polygon_closed)
+ return FALSE;
+
+ dist = gimp_canvas_item_transform_distance_square (priv->polygon,
+ coords->x,
+ coords->y,
+ priv->points[0].x,
+ priv->points[0].y);
+
+ /* Handle double-click. It must be within GTK+ global double-click
+ * time since last click, and it must be within GTK+ global
+ * double-click distance away from the last point
+ */
+ if (time != NO_CLICK_TIME_AVAILABLE)
+ {
+ GimpDisplayShell *shell = gimp_tool_widget_get_shell (widget);
+ GtkSettings *settings = gtk_widget_get_settings (GTK_WIDGET (shell));
+ gint double_click_time;
+ gint double_click_distance;
+ gint click_time_passed;
+ gdouble dist_from_last_point;
+
+ click_time_passed = time - priv->last_click_time;
+
+ dist_from_last_point =
+ gimp_canvas_item_transform_distance_square (priv->polygon,
+ coords->x,
+ coords->y,
+ priv->last_click_coord.x,
+ priv->last_click_coord.y);
+
+ g_object_get (settings,
+ "gtk-double-click-time", &double_click_time,
+ "gtk-double-click-distance", &double_click_distance,
+ NULL);
+
+ double_click = click_time_passed < double_click_time &&
+ dist_from_last_point < double_click_distance;
+ }
+
+ return ((! priv->suppress_handles && dist < POINT_GRAB_THRESHOLD_SQ) ||
+ double_click);
+}
+
+static void
+gimp_tool_polygon_revert_to_last_segment (GimpToolPolygon *polygon)
+{
+ GimpToolPolygonPrivate *priv = polygon->private;
+
+ priv->n_points = priv->segment_indices[priv->n_segment_indices - 1] + 1;
+}
+
+static void
+gimp_tool_polygon_remove_last_segment (GimpToolPolygon *polygon)
+{
+ GimpToolPolygonPrivate *priv = polygon->private;
+
+ if (priv->polygon_closed)
+ {
+ priv->polygon_closed = FALSE;
+
+ gimp_tool_polygon_changed (GIMP_TOOL_WIDGET (polygon));
+
+ return;
+ }
+
+ if (priv->n_segment_indices > 0)
+ {
+ GimpCanvasItem *handle;
+
+ priv->n_segment_indices--;
+
+ handle = g_ptr_array_index (priv->handles,
+ priv->n_segment_indices);
+
+ gimp_tool_widget_remove_item (GIMP_TOOL_WIDGET (polygon), handle);
+ g_ptr_array_remove (priv->handles, handle);
+ }
+
+ if (priv->n_segment_indices <= 0)
+ {
+ priv->grabbed_segment_index = INVALID_INDEX;
+ priv->show_pending_point = FALSE;
+ priv->n_points = 0;
+ priv->n_segment_indices = 0;
+
+ gimp_tool_widget_response (GIMP_TOOL_WIDGET (polygon),
+ GIMP_TOOL_WIDGET_RESPONSE_CANCEL);
+ }
+ else
+ {
+ gimp_tool_polygon_revert_to_last_segment (polygon);
+
+ gimp_tool_polygon_changed (GIMP_TOOL_WIDGET (polygon));
+ }
+}
+
+static void
+gimp_tool_polygon_add_point (GimpToolPolygon *polygon,
+ gdouble x,
+ gdouble y)
+{
+ GimpToolPolygonPrivate *priv = polygon->private;
+
+ if (priv->n_points >= priv->max_n_points)
+ {
+ priv->max_n_points += N_ITEMS_PER_ALLOC;
+
+ priv->points = g_realloc (priv->points,
+ sizeof (GimpVector2) * priv->max_n_points);
+ }
+
+ priv->points[priv->n_points].x = x;
+ priv->points[priv->n_points].y = y;
+
+ priv->n_points++;
+}
+
+static void
+gimp_tool_polygon_add_segment_index (GimpToolPolygon *polygon,
+ gint index)
+{
+ GimpToolPolygonPrivate *priv = polygon->private;
+
+ if (priv->n_segment_indices >= priv->max_n_segment_indices)
+ {
+ priv->max_n_segment_indices += N_ITEMS_PER_ALLOC;
+
+ priv->segment_indices = g_realloc (priv->segment_indices,
+ sizeof (GimpVector2) *
+ priv->max_n_segment_indices);
+ }
+
+ priv->segment_indices[priv->n_segment_indices] = index;
+
+ g_ptr_array_add (priv->handles,
+ gimp_tool_widget_add_handle (GIMP_TOOL_WIDGET (polygon),
+ GIMP_HANDLE_CROSS,
+ 0, 0,
+ GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+ GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+ GIMP_HANDLE_ANCHOR_CENTER));
+
+ priv->n_segment_indices++;
+}
+
+static gboolean
+gimp_tool_polygon_is_point_grabbed (GimpToolPolygon *polygon)
+{
+ GimpToolPolygonPrivate *priv = polygon->private;
+
+ return priv->grabbed_segment_index != INVALID_INDEX;
+}
+
+static void
+gimp_tool_polygon_fit_segment (GimpToolPolygon *polygon,
+ GimpVector2 *dest_points,
+ GimpVector2 dest_start_target,
+ GimpVector2 dest_end_target,
+ const GimpVector2 *source_points,
+ gint n_points)
+{
+ GimpVector2 origo_translation_offset;
+ GimpVector2 untranslation_offset;
+ gdouble rotation;
+ gdouble scale;
+
+ /* Handle some quick special cases */
+ if (n_points <= 0)
+ {
+ return;
+ }
+ else if (n_points == 1)
+ {
+ dest_points[0] = dest_end_target;
+ return;
+ }
+ else if (n_points == 2)
+ {
+ dest_points[0] = dest_start_target;
+ dest_points[1] = dest_end_target;
+ return;
+ }
+
+ /* Copy from source to dest; we work on the dest data */
+ memcpy (dest_points, source_points, sizeof (GimpVector2) * n_points);
+
+ /* Transform the destination end point */
+ {
+ GimpVector2 *dest_end;
+ GimpVector2 origo_translated_end_target;
+ gdouble target_rotation;
+ gdouble current_rotation;
+ gdouble target_length;
+ gdouble current_length;
+
+ dest_end = &dest_points[n_points - 1];
+
+ /* Transate to origin */
+ gimp_vector2_sub (&origo_translation_offset,
+ &vector2_zero,
+ &dest_points[0]);
+ gimp_vector2_add (dest_end,
+ dest_end,
+ &origo_translation_offset);
+
+ /* Calculate origo_translated_end_target */
+ gimp_vector2_sub (&origo_translated_end_target,
+ &dest_end_target,
+ &dest_start_target);
+
+ /* Rotate */
+ target_rotation = atan2 (vector2_zero.y - origo_translated_end_target.y,
+ vector2_zero.x - origo_translated_end_target.x);
+ current_rotation = atan2 (vector2_zero.y - dest_end->y,
+ vector2_zero.x - dest_end->x);
+ rotation = current_rotation - target_rotation;
+
+ gimp_vector2_rotate (dest_end, rotation);
+
+
+ /* Scale */
+ target_length = gimp_vector2_length (&origo_translated_end_target);
+ current_length = gimp_vector2_length (dest_end);
+ scale = target_length / current_length;
+
+ gimp_vector2_mul (dest_end, scale);
+
+
+ /* Untranslate */
+ gimp_vector2_sub (&untranslation_offset,
+ &dest_end_target,
+ dest_end);
+ gimp_vector2_add (dest_end,
+ dest_end,
+ &untranslation_offset);
+ }
+
+ /* Do the same transformation for the rest of the points */
+ {
+ gint i;
+
+ for (i = 0; i < n_points - 1; i++)
+ {
+ /* Translate */
+ gimp_vector2_add (&dest_points[i],
+ &origo_translation_offset,
+ &dest_points[i]);
+
+ /* Rotate */
+ gimp_vector2_rotate (&dest_points[i],
+ rotation);
+
+ /* Scale */
+ gimp_vector2_mul (&dest_points[i],
+ scale);
+
+ /* Untranslate */
+ gimp_vector2_add (&dest_points[i],
+ &dest_points[i],
+ &untranslation_offset);
+ }
+ }
+}
+
+static void
+gimp_tool_polygon_move_segment_vertex_to (GimpToolPolygon *polygon,
+ gint segment_index,
+ gdouble new_x,
+ gdouble new_y)
+{
+ GimpToolPolygonPrivate *priv = polygon->private;
+ GimpVector2 cursor_point = { new_x, new_y };
+ GimpVector2 *dest;
+ GimpVector2 *dest_start_target;
+ GimpVector2 *dest_end_target;
+ gint n_points;
+
+ /* Handle the segment before the grabbed point */
+ if (segment_index > 0)
+ {
+ gimp_tool_polygon_get_segment (polygon,
+ &dest,
+ &n_points,
+ priv->grabbed_segment_index - 1,
+ priv->grabbed_segment_index);
+
+ dest_start_target = &dest[0];
+ dest_end_target = &cursor_point;
+
+ gimp_tool_polygon_fit_segment (polygon,
+ dest,
+ *dest_start_target,
+ *dest_end_target,
+ priv->saved_points_lower_segment,
+ n_points);
+ }
+
+ /* Handle the segment after the grabbed point */
+ if (segment_index < priv->n_segment_indices - 1)
+ {
+ gimp_tool_polygon_get_segment (polygon,
+ &dest,
+ &n_points,
+ priv->grabbed_segment_index,
+ priv->grabbed_segment_index + 1);
+
+ dest_start_target = &cursor_point;
+ dest_end_target = &dest[n_points - 1];
+
+ gimp_tool_polygon_fit_segment (polygon,
+ dest,
+ *dest_start_target,
+ *dest_end_target,
+ priv->saved_points_higher_segment,
+ n_points);
+ }
+
+ /* Handle when there only is one point */
+ if (segment_index == 0 &&
+ priv->n_segment_indices == 1)
+ {
+ priv->points[0].x = new_x;
+ priv->points[0].y = new_y;
+ }
+}
+
+static void
+gimp_tool_polygon_revert_to_saved_state (GimpToolPolygon *polygon)
+{
+ GimpToolPolygonPrivate *priv = polygon->private;
+ GimpVector2 *dest;
+ gint n_points;
+
+ /* Without a point grab we have no sensible information to fall back
+ * on, bail out
+ */
+ if (! gimp_tool_polygon_is_point_grabbed (polygon))
+ {
+ return;
+ }
+
+ if (priv->grabbed_segment_index > 0)
+ {
+ gimp_tool_polygon_get_segment (polygon,
+ &dest,
+ &n_points,
+ priv->grabbed_segment_index - 1,
+ priv->grabbed_segment_index);
+
+ memcpy (dest,
+ priv->saved_points_lower_segment,
+ sizeof (GimpVector2) * n_points);
+ }
+
+ if (priv->grabbed_segment_index < priv->n_segment_indices - 1)
+ {
+ gimp_tool_polygon_get_segment (polygon,
+ &dest,
+ &n_points,
+ priv->grabbed_segment_index,
+ priv->grabbed_segment_index + 1);
+
+ memcpy (dest,
+ priv->saved_points_higher_segment,
+ sizeof (GimpVector2) * n_points);
+ }
+
+ if (priv->grabbed_segment_index == 0 &&
+ priv->n_segment_indices == 1)
+ {
+ priv->points[0] = *priv->saved_points_lower_segment;
+ }
+}
+
+static void
+gimp_tool_polygon_prepare_for_move (GimpToolPolygon *polygon)
+{
+ GimpToolPolygonPrivate *priv = polygon->private;
+ GimpVector2 *source;
+ gint n_points;
+
+ if (priv->grabbed_segment_index > 0)
+ {
+ gimp_tool_polygon_get_segment (polygon,
+ &source,
+ &n_points,
+ priv->grabbed_segment_index - 1,
+ priv->grabbed_segment_index);
+
+ if (n_points > priv->max_n_saved_points_lower_segment)
+ {
+ priv->max_n_saved_points_lower_segment = n_points;
+
+ priv->saved_points_lower_segment =
+ g_realloc (priv->saved_points_lower_segment,
+ sizeof (GimpVector2) * n_points);
+ }
+
+ memcpy (priv->saved_points_lower_segment,
+ source,
+ sizeof (GimpVector2) * n_points);
+ }
+
+ if (priv->grabbed_segment_index < priv->n_segment_indices - 1)
+ {
+ gimp_tool_polygon_get_segment (polygon,
+ &source,
+ &n_points,
+ priv->grabbed_segment_index,
+ priv->grabbed_segment_index + 1);
+
+ if (n_points > priv->max_n_saved_points_higher_segment)
+ {
+ priv->max_n_saved_points_higher_segment = n_points;
+
+ priv->saved_points_higher_segment =
+ g_realloc (priv->saved_points_higher_segment,
+ sizeof (GimpVector2) * n_points);
+ }
+
+ memcpy (priv->saved_points_higher_segment,
+ source,
+ sizeof (GimpVector2) * n_points);
+ }
+
+ /* A special-case when there only is one point */
+ if (priv->grabbed_segment_index == 0 &&
+ priv->n_segment_indices == 1)
+ {
+ if (priv->max_n_saved_points_lower_segment == 0)
+ {
+ priv->max_n_saved_points_lower_segment = 1;
+
+ priv->saved_points_lower_segment = g_new0 (GimpVector2, 1);
+ }
+
+ *priv->saved_points_lower_segment = priv->points[0];
+ }
+}
+
+static void
+gimp_tool_polygon_update_motion (GimpToolPolygon *polygon,
+ gdouble new_x,
+ gdouble new_y)
+{
+ GimpToolPolygonPrivate *priv = polygon->private;
+
+ if (gimp_tool_polygon_is_point_grabbed (polygon))
+ {
+ priv->polygon_modified = TRUE;
+
+ if (priv->constrain_angle &&
+ priv->n_segment_indices > 1)
+ {
+ gdouble start_point_x;
+ gdouble start_point_y;
+ gint segment_index;
+
+ /* Base constraints on the last segment vertex if we move
+ * the first one, otherwise base on the previous segment
+ * vertex
+ */
+ if (priv->grabbed_segment_index == 0)
+ {
+ segment_index = priv->n_segment_indices - 1;
+ }
+ else
+ {
+ segment_index = priv->grabbed_segment_index - 1;
+ }
+
+ gimp_tool_polygon_get_segment_point (polygon,
+ &start_point_x,
+ &start_point_y,
+ segment_index);
+
+ gimp_display_shell_constrain_line (
+ gimp_tool_widget_get_shell (GIMP_TOOL_WIDGET (polygon)),
+ start_point_x,
+ start_point_y,
+ &new_x,
+ &new_y,
+ GIMP_CONSTRAIN_LINE_15_DEGREES);
+ }
+
+ gimp_tool_polygon_move_segment_vertex_to (polygon,
+ priv->grabbed_segment_index,
+ new_x,
+ new_y);
+
+ /* We also must update the pending point if we are moving the
+ * first point
+ */
+ if (priv->grabbed_segment_index == 0)
+ {
+ priv->pending_point.x = new_x;
+ priv->pending_point.y = new_y;
+ }
+ }
+ else
+ {
+ /* Don't show the pending point while we are adding points */
+ priv->show_pending_point = FALSE;
+
+ gimp_tool_polygon_add_point (polygon, new_x, new_y);
+ }
+}
+
+static void
+gimp_tool_polygon_status_update (GimpToolPolygon *polygon,
+ const GimpCoords *coords,
+ gboolean proximity)
+{
+ GimpToolWidget *widget = GIMP_TOOL_WIDGET (polygon);
+ GimpToolPolygonPrivate *priv = polygon->private;
+
+ if (proximity)
+ {
+ const gchar *status_text = NULL;
+
+ if (gimp_tool_polygon_is_point_grabbed (polygon))
+ {
+ if (gimp_tool_polygon_should_close (polygon,
+ NO_CLICK_TIME_AVAILABLE,
+ coords))
+ {
+ status_text = _("Click to close shape");
+ }
+ else
+ {
+ status_text = _("Click-Drag to move segment vertex");
+ }
+ }
+ else if (priv->polygon_closed)
+ {
+ status_text = _("Return commits, Escape cancels, Backspace re-opens shape");
+ }
+ else if (priv->n_segment_indices >= 3)
+ {
+ status_text = _("Return commits, Escape cancels, Backspace removes last segment");
+ }
+ else
+ {
+ status_text = _("Click-Drag adds a free segment, Click adds a polygonal segment");
+ }
+
+ if (status_text)
+ {
+ gimp_tool_widget_set_status (widget, status_text);
+ }
+ }
+ else
+ {
+ gimp_tool_widget_set_status (widget, NULL);
+ }
+}
+
+static void
+gimp_tool_polygon_changed (GimpToolWidget *widget)
+{
+ GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget);
+ GimpToolPolygonPrivate *private = polygon->private;
+ gboolean hovering_first_point = FALSE;
+ gboolean handles_wants_to_show = FALSE;
+ GimpCoords coords = { private->last_coords.x,
+ private->last_coords.y,
+ /* pad with 0 */ };
+ gint i;
+
+ gimp_canvas_polygon_set_points (private->polygon,
+ private->points, private->n_points);
+
+ if (private->show_pending_point)
+ {
+ GimpVector2 last = private->points[private->n_points - 1];
+
+ gimp_canvas_line_set (private->pending_line,
+ last.x,
+ last.y,
+ private->pending_point.x,
+ private->pending_point.y);
+ }
+
+ gimp_canvas_item_set_visible (private->pending_line,
+ private->show_pending_point);
+
+ if (private->polygon_closed)
+ {
+ GimpVector2 first = private->points[0];
+ GimpVector2 last = private->points[private->n_points - 1];
+
+ gimp_canvas_line_set (private->closing_line,
+ first.x, first.y,
+ last.x, last.y);
+ }
+
+ gimp_canvas_item_set_visible (private->closing_line,
+ private->polygon_closed);
+
+ hovering_first_point =
+ gimp_tool_polygon_should_close (polygon,
+ NO_CLICK_TIME_AVAILABLE,
+ &coords);
+
+ /* We always show the handle for the first point, even with button1
+ * down, since releasing the button on the first point will close
+ * the polygon, so it's a significant state which we must give
+ * feedback for
+ */
+ handles_wants_to_show = (hovering_first_point || ! private->button_down);
+
+ for (i = 0; i < private->n_segment_indices; i++)
+ {
+ GimpCanvasItem *handle;
+ GimpVector2 *point;
+
+ handle = g_ptr_array_index (private->handles, i);
+ point = &private->points[private->segment_indices[i]];
+
+ if (private->hover &&
+ handles_wants_to_show &&
+ ! private->suppress_handles &&
+
+ /* If the first point is hovered while button1 is held down,
+ * only draw the first handle, the other handles are not
+ * relevant (see comment a few lines up)
+ */
+ (i == 0 ||
+ ! (private->button_down || hovering_first_point)))
+ {
+ gdouble dist;
+ GimpHandleType handle_type = -1;
+
+ dist =
+ gimp_canvas_item_transform_distance_square (handle,
+ private->last_coords.x,
+ private->last_coords.y,
+ point->x,
+ point->y);
+
+ /* If the cursor is over the point, fill, if it's just
+ * close, draw an outline
+ */
+ if (dist < POINT_GRAB_THRESHOLD_SQ)
+ handle_type = GIMP_HANDLE_FILLED_CIRCLE;
+ else if (dist < POINT_SHOW_THRESHOLD_SQ)
+ handle_type = GIMP_HANDLE_CIRCLE;
+
+ if (handle_type != -1)
+ {
+ gint size;
+
+ gimp_canvas_item_begin_change (handle);
+
+ gimp_canvas_handle_set_position (handle, point->x, point->y);
+
+ g_object_set (handle,
+ "type", handle_type,
+ NULL);
+
+ size = gimp_canvas_handle_calc_size (handle,
+ private->last_coords.x,
+ private->last_coords.y,
+ GIMP_CANVAS_HANDLE_SIZE_CIRCLE,
+ 2 * GIMP_CANVAS_HANDLE_SIZE_CIRCLE);
+ if (FALSE)
+ gimp_canvas_handle_set_size (handle, size, size);
+
+ if (dist < POINT_GRAB_THRESHOLD_SQ)
+ gimp_canvas_item_set_highlight (handle, TRUE);
+ else
+ gimp_canvas_item_set_highlight (handle, FALSE);
+
+ gimp_canvas_item_set_visible (handle, TRUE);
+
+ gimp_canvas_item_end_change (handle);
+ }
+ else
+ {
+ gimp_canvas_item_set_visible (handle, FALSE);
+ }
+ }
+ else
+ {
+ gimp_canvas_item_set_visible (handle, FALSE);
+ }
+ }
+}
+
+static gboolean
+gimp_tool_polygon_button_press (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonPressType press_type)
+{
+ GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget);
+ GimpToolPolygonPrivate *priv = polygon->private;
+
+ if (gimp_tool_polygon_is_point_grabbed (polygon))
+ {
+ gimp_tool_polygon_prepare_for_move (polygon);
+ }
+ else if (priv->polygon_closed)
+ {
+ if (press_type == GIMP_BUTTON_PRESS_DOUBLE &&
+ gimp_canvas_item_hit (priv->polygon, coords->x, coords->y))
+ {
+ gimp_tool_widget_response (widget, GIMP_TOOL_WIDGET_RESPONSE_CONFIRM);
+ }
+
+ return 0;
+ }
+ else
+ {
+ GimpVector2 point_to_add;
+
+ /* Note that we add the pending point (unless it is the first
+ * point we add) because the pending point is setup correctly
+ * with regards to angle constraints.
+ */
+ if (priv->n_points > 0)
+ {
+ point_to_add = priv->pending_point;
+ }
+ else
+ {
+ point_to_add.x = coords->x;
+ point_to_add.y = coords->y;
+ }
+
+ /* No point was grabbed, add a new point and mark this as a
+ * segment divider. For a line segment, this will be the only
+ * new point. For a free segment, this will be the first point
+ * of the free segment.
+ */
+ gimp_tool_polygon_add_point (polygon,
+ point_to_add.x,
+ point_to_add.y);
+ gimp_tool_polygon_add_segment_index (polygon, priv->n_points - 1);
+ }
+
+ priv->button_down = TRUE;
+
+ gimp_tool_polygon_changed (widget);
+
+ return 1;
+}
+
+static void
+gimp_tool_polygon_button_release (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state,
+ GimpButtonReleaseType release_type)
+{
+ GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget);
+ GimpToolPolygonPrivate *priv = polygon->private;
+
+ g_object_ref (widget);
+
+ priv->button_down = FALSE;
+
+ switch (release_type)
+ {
+ case GIMP_BUTTON_RELEASE_CLICK:
+ case GIMP_BUTTON_RELEASE_NO_MOTION:
+ /* If a click was made, we don't consider the polygon modified */
+ priv->polygon_modified = FALSE;
+
+ /* First finish of the line segment if no point was grabbed */
+ if (! gimp_tool_polygon_is_point_grabbed (polygon))
+ {
+ /* Revert any free segment points that might have been added */
+ gimp_tool_polygon_revert_to_last_segment (polygon);
+ }
+
+ /* After the segments are up to date and we have handled
+ * double-click, see if it's committing time
+ */
+ if (gimp_tool_polygon_should_close (polygon, time, coords))
+ {
+ /* We can get a click notification even though the end point
+ * has been moved a few pixels. Since a move will change the
+ * free selection, revert it before doing the commit.
+ */
+ gimp_tool_polygon_revert_to_saved_state (polygon);
+
+ priv->polygon_closed = TRUE;
+
+ gimp_tool_polygon_change_complete (polygon);
+ }
+
+ priv->last_click_time = time;
+ priv->last_click_coord = *coords;
+ break;
+
+ case GIMP_BUTTON_RELEASE_NORMAL:
+ /* First finish of the free segment if no point was grabbed */
+ if (! gimp_tool_polygon_is_point_grabbed (polygon))
+ {
+ /* The points are all setup, just make a segment */
+ gimp_tool_polygon_add_segment_index (polygon, priv->n_points - 1);
+ }
+
+ /* After the segments are up to date, see if it's committing time */
+ if (gimp_tool_polygon_should_close (polygon,
+ NO_CLICK_TIME_AVAILABLE,
+ coords))
+ {
+ priv->polygon_closed = TRUE;
+ }
+
+ gimp_tool_polygon_change_complete (polygon);
+ break;
+
+ case GIMP_BUTTON_RELEASE_CANCEL:
+ if (gimp_tool_polygon_is_point_grabbed (polygon))
+ gimp_tool_polygon_revert_to_saved_state (polygon);
+ else
+ gimp_tool_polygon_remove_last_segment (polygon);
+ break;
+
+ default:
+ break;
+ }
+
+ /* Reset */
+ priv->polygon_modified = FALSE;
+
+ gimp_tool_polygon_changed (widget);
+
+ g_object_unref (widget);
+}
+
+static void
+gimp_tool_polygon_motion (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ guint32 time,
+ GdkModifierType state)
+{
+ GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget);
+ GimpToolPolygonPrivate *priv = polygon->private;
+
+ priv->last_coords.x = coords->x;
+ priv->last_coords.y = coords->y;
+
+ gimp_tool_polygon_update_motion (polygon,
+ coords->x,
+ coords->y);
+
+ gimp_tool_polygon_changed (widget);
+}
+
+static GimpHit
+gimp_tool_polygon_hit (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget);
+ GimpToolPolygonPrivate *priv = polygon->private;
+
+ if ((priv->n_points > 0 && ! priv->polygon_closed) ||
+ gimp_tool_polygon_get_segment_index (polygon, coords) != INVALID_INDEX)
+ {
+ return GIMP_HIT_DIRECT;
+ }
+ else if (priv->polygon_closed &&
+ gimp_canvas_item_hit (priv->polygon, coords->x, coords->y))
+ {
+ return GIMP_HIT_INDIRECT;
+ }
+
+ return GIMP_HIT_NONE;
+}
+
+static void
+gimp_tool_polygon_hover (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ gboolean proximity)
+{
+ GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget);
+ GimpToolPolygonPrivate *priv = polygon->private;
+ gboolean hovering_first_point;
+
+ priv->grabbed_segment_index = gimp_tool_polygon_get_segment_index (polygon,
+ coords);
+ priv->hover = TRUE;
+
+ hovering_first_point =
+ gimp_tool_polygon_should_close (polygon,
+ NO_CLICK_TIME_AVAILABLE,
+ coords);
+
+ priv->last_coords.x = coords->x;
+ priv->last_coords.y = coords->y;
+
+ if (priv->n_points == 0 ||
+ priv->polygon_closed ||
+ (gimp_tool_polygon_is_point_grabbed (polygon) &&
+ ! hovering_first_point) ||
+ ! proximity)
+ {
+ priv->show_pending_point = FALSE;
+ }
+ else
+ {
+ priv->show_pending_point = TRUE;
+
+ if (hovering_first_point)
+ {
+ priv->pending_point = priv->points[0];
+ }
+ else
+ {
+ priv->pending_point.x = coords->x;
+ priv->pending_point.y = coords->y;
+
+ if (priv->constrain_angle && priv->n_points > 0)
+ {
+ gdouble start_point_x;
+ gdouble start_point_y;
+
+ /* the last point is the line's start point */
+ gimp_tool_polygon_get_segment_point (polygon,
+ &start_point_x,
+ &start_point_y,
+ priv->n_segment_indices - 1);
+
+ gimp_display_shell_constrain_line (
+ gimp_tool_widget_get_shell (widget),
+ start_point_x, start_point_y,
+ &priv->pending_point.x,
+ &priv->pending_point.y,
+ GIMP_CONSTRAIN_LINE_15_DEGREES);
+ }
+ }
+ }
+
+ gimp_tool_polygon_status_update (polygon, coords, proximity);
+
+ gimp_tool_polygon_changed (widget);
+}
+
+static void
+gimp_tool_polygon_leave_notify (GimpToolWidget *widget)
+{
+ GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget);
+ GimpToolPolygonPrivate *priv = polygon->private;
+
+ priv->grabbed_segment_index = INVALID_INDEX;
+ priv->hover = FALSE;
+ priv->show_pending_point = FALSE;
+
+ gimp_tool_polygon_changed (widget);
+}
+
+static gboolean
+gimp_tool_polygon_key_press (GimpToolWidget *widget,
+ GdkEventKey *kevent)
+{
+ GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget);
+ GimpToolPolygonPrivate *priv = polygon->private;
+
+ switch (kevent->keyval)
+ {
+ case GDK_KEY_BackSpace:
+ gimp_tool_polygon_remove_last_segment (polygon);
+
+ if (priv->n_segment_indices > 0)
+ gimp_tool_polygon_change_complete (polygon);
+ return TRUE;
+
+ default:
+ break;
+ }
+
+ return GIMP_TOOL_WIDGET_CLASS (parent_class)->key_press (widget, kevent);
+}
+
+static void
+gimp_tool_polygon_motion_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state)
+{
+ GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget);
+ GimpToolPolygonPrivate *priv = polygon->private;
+
+ priv->constrain_angle = ((state & gimp_get_constrain_behavior_mask ()) ?
+ TRUE : FALSE);
+
+ /* If we didn't came here due to a mouse release, immediately update
+ * the position of the thing we move.
+ */
+ if (state & GDK_BUTTON1_MASK)
+ {
+ gimp_tool_polygon_update_motion (polygon,
+ priv->last_coords.x,
+ priv->last_coords.y);
+ }
+
+ gimp_tool_polygon_changed (widget);
+}
+
+static void
+gimp_tool_polygon_hover_modifier (GimpToolWidget *widget,
+ GdkModifierType key,
+ gboolean press,
+ GdkModifierType state)
+{
+ GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget);
+ GimpToolPolygonPrivate *priv = polygon->private;
+
+ priv->constrain_angle = ((state & gimp_get_constrain_behavior_mask ()) ?
+ TRUE : FALSE);
+
+ priv->suppress_handles = ((state & gimp_get_extend_selection_mask ()) ?
+ TRUE : FALSE);
+
+ gimp_tool_polygon_changed (widget);
+}
+
+static gboolean
+gimp_tool_polygon_get_cursor (GimpToolWidget *widget,
+ const GimpCoords *coords,
+ GdkModifierType state,
+ GimpCursorType *cursor,
+ GimpToolCursorType *tool_cursor,
+ GimpCursorModifier *modifier)
+{
+ GimpToolPolygon *polygon = GIMP_TOOL_POLYGON (widget);
+
+ if (gimp_tool_polygon_is_point_grabbed (polygon) &&
+ ! gimp_tool_polygon_should_close (polygon,
+ NO_CLICK_TIME_AVAILABLE,
+ coords))
+ {
+ *modifier = GIMP_CURSOR_MODIFIER_MOVE;
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+gimp_tool_polygon_change_complete (GimpToolPolygon *polygon)
+{
+ g_signal_emit (polygon, polygon_signals[CHANGE_COMPLETE], 0);
+}
+
+static gint
+gimp_tool_polygon_get_segment_index (GimpToolPolygon *polygon,
+ const GimpCoords *coords)
+{
+ GimpToolPolygonPrivate *priv = polygon->private;
+ gint segment_index = INVALID_INDEX;
+
+ if (! priv->suppress_handles)
+ {
+ gdouble shortest_dist = POINT_GRAB_THRESHOLD_SQ;
+ gint i;
+
+ for (i = 0; i < priv->n_segment_indices; i++)
+ {
+ gdouble dist;
+ GimpVector2 *point;
+
+ point = &priv->points[priv->segment_indices[i]];
+
+ dist = gimp_canvas_item_transform_distance_square (priv->polygon,
+ coords->x,
+ coords->y,
+ point->x,
+ point->y);
+
+ if (dist < shortest_dist)
+ {
+ shortest_dist = dist;
+
+ segment_index = i;
+ }
+ }
+ }
+
+ return segment_index;
+}
+
+
+/* public functions */
+
+GimpToolWidget *
+gimp_tool_polygon_new (GimpDisplayShell *shell)
+{
+ g_return_val_if_fail (GIMP_IS_DISPLAY_SHELL (shell), NULL);
+
+ return g_object_new (GIMP_TYPE_TOOL_POLYGON,
+ "shell", shell,
+ NULL);
+}
+
+gboolean
+gimp_tool_polygon_is_closed (GimpToolPolygon *polygon)
+{
+ GimpToolPolygonPrivate *private;
+
+ g_return_val_if_fail (GIMP_IS_TOOL_POLYGON (polygon), FALSE);
+
+ private = polygon->private;
+
+ return private->polygon_closed;
+}
+
+void
+gimp_tool_polygon_get_points (GimpToolPolygon *polygon,
+ const GimpVector2 **points,
+ gint *n_points)
+{
+ GimpToolPolygonPrivate *private;
+
+ g_return_if_fail (GIMP_IS_TOOL_POLYGON (polygon));
+
+ private = polygon->private;
+
+ if (points) *points = private->points;
+ if (n_points) *n_points = private->n_points;
+}