/* 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 * * Major improvement to support polygonal segments * Copyright (C) 2008 Martin Nordholts * * This program is polygon software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Polygon 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 "libpikawidgets/pikawidgets.h" #include "tools-types.h" #include "core/pikachannel.h" #include "core/pikachannel-select.h" #include "core/pikaimage.h" #include "core/pikalayer-floating-selection.h" #include "widgets/pikahelp-ids.h" #include "widgets/pikawidgets-utils.h" #include "display/pikadisplay.h" #include "display/pikatoolpolygon.h" #include "pikapolygonselecttool.h" #include "pikaselectionoptions.h" #include "pikatoolcontrol.h" struct _PikaPolygonSelectToolPrivate { PikaToolWidget *widget; PikaToolWidget *grab_widget; gboolean pending_response; gint pending_response_id; }; /* local function prototypes */ static void pika_polygon_select_tool_finalize (GObject *object); static void pika_polygon_select_tool_control (PikaTool *tool, PikaToolAction action, PikaDisplay *display); static void pika_polygon_select_tool_button_press (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonPressType press_type, PikaDisplay *display); static void pika_polygon_select_tool_button_release (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonReleaseType release_type, PikaDisplay *display); static void pika_polygon_select_tool_motion (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaDisplay *display); static gboolean pika_polygon_select_tool_key_press (PikaTool *tool, GdkEventKey *kevent, PikaDisplay *display); static void pika_polygon_select_tool_modifier_key (PikaTool *tool, GdkModifierType key, gboolean press, GdkModifierType state, PikaDisplay *display); static void pika_polygon_select_tool_oper_update (PikaTool *tool, const PikaCoords *coords, GdkModifierType state, gboolean proximity, PikaDisplay *display); static void pika_polygon_select_tool_cursor_update (PikaTool *tool, const PikaCoords *coords, GdkModifierType state, PikaDisplay *display); static void pika_polygon_select_tool_real_confirm (PikaPolygonSelectTool *poly_sel, PikaDisplay *display); static void pika_polygon_select_tool_polygon_change_complete (PikaToolWidget *polygon, PikaPolygonSelectTool *poly_sel); static void pika_polygon_select_tool_polygon_response (PikaToolWidget *polygon, gint response_id, PikaPolygonSelectTool *poly_sel); static void pika_polygon_select_tool_start (PikaPolygonSelectTool *poly_sel, PikaDisplay *display); G_DEFINE_TYPE_WITH_PRIVATE (PikaPolygonSelectTool, pika_polygon_select_tool, PIKA_TYPE_SELECTION_TOOL) #define parent_class pika_polygon_select_tool_parent_class /* private functions */ static void pika_polygon_select_tool_class_init (PikaPolygonSelectToolClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); PikaToolClass *tool_class = PIKA_TOOL_CLASS (klass); object_class->finalize = pika_polygon_select_tool_finalize; tool_class->control = pika_polygon_select_tool_control; tool_class->button_press = pika_polygon_select_tool_button_press; tool_class->button_release = pika_polygon_select_tool_button_release; tool_class->motion = pika_polygon_select_tool_motion; tool_class->key_press = pika_polygon_select_tool_key_press; tool_class->modifier_key = pika_polygon_select_tool_modifier_key; tool_class->oper_update = pika_polygon_select_tool_oper_update; tool_class->cursor_update = pika_polygon_select_tool_cursor_update; klass->change_complete = NULL; klass->confirm = pika_polygon_select_tool_real_confirm; } static void pika_polygon_select_tool_init (PikaPolygonSelectTool *poly_sel) { PikaTool *tool = PIKA_TOOL (poly_sel); PikaSelectionTool *sel_tool = PIKA_SELECTION_TOOL (tool); poly_sel->priv = pika_polygon_select_tool_get_instance_private (poly_sel); pika_tool_control_set_motion_mode (tool->control, PIKA_MOTION_MODE_EXACT); pika_tool_control_set_wants_click (tool->control, TRUE); pika_tool_control_set_wants_double_click (tool->control, TRUE); pika_tool_control_set_active_modifiers (tool->control, PIKA_TOOL_ACTIVE_MODIFIERS_SEPARATE); pika_tool_control_set_precision (tool->control, PIKA_CURSOR_PRECISION_SUBPIXEL); sel_tool->allow_move = FALSE; } static void pika_polygon_select_tool_finalize (GObject *object) { PikaPolygonSelectTool *poly_sel = PIKA_POLYGON_SELECT_TOOL (object); PikaPolygonSelectToolPrivate *priv = poly_sel->priv; g_clear_object (&priv->widget); G_OBJECT_CLASS (parent_class)->finalize (object); } static void pika_polygon_select_tool_control (PikaTool *tool, PikaToolAction action, PikaDisplay *display) { PikaPolygonSelectTool *poly_sel = PIKA_POLYGON_SELECT_TOOL (tool); switch (action) { case PIKA_TOOL_ACTION_PAUSE: case PIKA_TOOL_ACTION_RESUME: break; case PIKA_TOOL_ACTION_HALT: pika_polygon_select_tool_halt (poly_sel); break; case PIKA_TOOL_ACTION_COMMIT: break; } PIKA_TOOL_CLASS (parent_class)->control (tool, action, display); } static void pika_polygon_select_tool_button_press (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonPressType press_type, PikaDisplay *display) { PikaPolygonSelectTool *poly_sel = PIKA_POLYGON_SELECT_TOOL (tool); PikaPolygonSelectToolPrivate *priv = poly_sel->priv; if (tool->display && tool->display != display) pika_tool_control (tool, PIKA_TOOL_ACTION_COMMIT, tool->display); if (! priv->widget) /* not tool->display, we have a subclass */ { /* First of all handle delegation to the selection mask edit logic * if appropriate. */ if (pika_selection_tool_start_edit (PIKA_SELECTION_TOOL (poly_sel), display, coords)) { return; } pika_polygon_select_tool_start (poly_sel, display); pika_tool_widget_hover (priv->widget, coords, state, TRUE); } if (pika_tool_widget_button_press (priv->widget, coords, time, state, press_type)) { priv->grab_widget = priv->widget; } if (press_type == PIKA_BUTTON_PRESS_NORMAL) pika_tool_control_activate (tool->control); } static void pika_polygon_select_tool_button_release (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonReleaseType release_type, PikaDisplay *display) { PikaPolygonSelectTool *poly_sel = PIKA_POLYGON_SELECT_TOOL (tool); PikaPolygonSelectToolPrivate *priv = poly_sel->priv; PikaImage *image = pika_display_get_image (display); pika_tool_control_halt (tool->control); switch (release_type) { case PIKA_BUTTON_RELEASE_CLICK: case PIKA_BUTTON_RELEASE_NO_MOTION: /* If there is a floating selection, anchor it */ if (pika_image_get_floating_selection (image)) { floating_sel_anchor (pika_image_get_floating_selection (image)); pika_tool_control (tool, PIKA_TOOL_ACTION_HALT, display); return; } /* fallthru */ default: if (priv->grab_widget) { pika_tool_widget_button_release (priv->grab_widget, coords, time, state, release_type); priv->grab_widget = NULL; } } if (priv->pending_response) { pika_polygon_select_tool_polygon_response (priv->widget, priv->pending_response_id, poly_sel); priv->pending_response = FALSE; } } static void pika_polygon_select_tool_motion (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaDisplay *display) { PikaPolygonSelectTool *poly_sel = PIKA_POLYGON_SELECT_TOOL (tool); PikaPolygonSelectToolPrivate *priv = poly_sel->priv; if (priv->grab_widget) { pika_tool_widget_motion (priv->grab_widget, coords, time, state); } } static gboolean pika_polygon_select_tool_key_press (PikaTool *tool, GdkEventKey *kevent, PikaDisplay *display) { PikaPolygonSelectTool *poly_sel = PIKA_POLYGON_SELECT_TOOL (tool); PikaPolygonSelectToolPrivate *priv = poly_sel->priv; if (priv->widget && display == tool->display) { return pika_tool_widget_key_press (priv->widget, kevent); } return FALSE; } static void pika_polygon_select_tool_modifier_key (PikaTool *tool, GdkModifierType key, gboolean press, GdkModifierType state, PikaDisplay *display) { PikaPolygonSelectTool *poly_sel = PIKA_POLYGON_SELECT_TOOL (tool); PikaPolygonSelectToolPrivate *priv = poly_sel->priv; if (priv->widget && display == tool->display) { pika_tool_widget_hover_modifier (priv->widget, key, press, state); /* let PikaSelectTool handle alt+ */ if (! (state & GDK_MOD1_MASK)) { /* otherwise, shift/ctrl are handled by the widget */ state &= ~(pika_get_extend_selection_mask () | pika_get_modify_selection_mask ()); } } PIKA_TOOL_CLASS (parent_class)->modifier_key (tool, key, press, state, display); } static void pika_polygon_select_tool_oper_update (PikaTool *tool, const PikaCoords *coords, GdkModifierType state, gboolean proximity, PikaDisplay *display) { PikaPolygonSelectTool *poly_sel = PIKA_POLYGON_SELECT_TOOL (tool); PikaPolygonSelectToolPrivate *priv = poly_sel->priv; if (priv->widget && display == tool->display) { pika_tool_widget_hover (priv->widget, coords, state, proximity); /* let PikaSelectTool handle alt+ */ if (! (state & GDK_MOD1_MASK)) { /* otherwise, shift/ctrl are handled by the widget */ state &= ~(pika_get_extend_selection_mask () | pika_get_modify_selection_mask ()); } } PIKA_TOOL_CLASS (parent_class)->oper_update (tool, coords, state, proximity, display); } static void pika_polygon_select_tool_cursor_update (PikaTool *tool, const PikaCoords *coords, GdkModifierType state, PikaDisplay *display) { PikaPolygonSelectTool *poly_sel = PIKA_POLYGON_SELECT_TOOL (tool); PikaPolygonSelectToolPrivate *priv = poly_sel->priv; PikaCursorModifier modifier = PIKA_CURSOR_MODIFIER_NONE; if (tool->display) { if (priv->widget && display == tool->display) { pika_tool_widget_get_cursor (priv->widget, coords, state, NULL, NULL, &modifier); /* let PikaSelectTool handle alt+ */ if (! (state & GDK_MOD1_MASK)) { /* otherwise, shift/ctrl are handled by the widget */ state &= ~(pika_get_extend_selection_mask () | pika_get_modify_selection_mask ()); } } pika_tool_set_cursor (tool, display, pika_tool_control_get_cursor (tool->control), pika_tool_control_get_tool_cursor (tool->control), modifier); } PIKA_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display); } static void pika_polygon_select_tool_real_confirm (PikaPolygonSelectTool *poly_sel, PikaDisplay *display) { pika_tool_control (PIKA_TOOL (poly_sel), PIKA_TOOL_ACTION_COMMIT, display); } static void pika_polygon_select_tool_polygon_change_complete (PikaToolWidget *polygon, PikaPolygonSelectTool *poly_sel) { if (PIKA_POLYGON_SELECT_TOOL_GET_CLASS (poly_sel)->change_complete) { PIKA_POLYGON_SELECT_TOOL_GET_CLASS (poly_sel)->change_complete ( poly_sel, PIKA_TOOL (poly_sel)->display); } } static void pika_polygon_select_tool_polygon_response (PikaToolWidget *polygon, gint response_id, PikaPolygonSelectTool *poly_sel) { PikaTool *tool = PIKA_TOOL (poly_sel); PikaPolygonSelectToolPrivate *priv = poly_sel->priv; /* if we're in the middle of a click, defer the response to the * button_release() event */ if (pika_tool_control_is_active (tool->control)) { priv->pending_response = TRUE; priv->pending_response_id = response_id; return; } switch (response_id) { case PIKA_TOOL_WIDGET_RESPONSE_CONFIRM: /* don't pika_tool_control(COMMIT) here because we don't always * want to HALT the tool after committing because we have a * subclass, we do that in the default implementation of * confirm(). */ if (PIKA_POLYGON_SELECT_TOOL_GET_CLASS (poly_sel)->confirm) { PIKA_POLYGON_SELECT_TOOL_GET_CLASS (poly_sel)->confirm ( poly_sel, tool->display); } break; case PIKA_TOOL_WIDGET_RESPONSE_CANCEL: pika_tool_control (tool, PIKA_TOOL_ACTION_HALT, tool->display); break; } } static void pika_polygon_select_tool_start (PikaPolygonSelectTool *poly_sel, PikaDisplay *display) { PikaTool *tool = PIKA_TOOL (poly_sel); PikaPolygonSelectToolPrivate *priv = poly_sel->priv; PikaDisplayShell *shell = pika_display_get_shell (display); tool->display = display; priv->widget = pika_tool_polygon_new (shell); pika_draw_tool_set_widget (PIKA_DRAW_TOOL (tool), priv->widget); g_signal_connect (priv->widget, "change-complete", G_CALLBACK (pika_polygon_select_tool_polygon_change_complete), poly_sel); g_signal_connect (priv->widget, "response", G_CALLBACK (pika_polygon_select_tool_polygon_response), poly_sel); pika_draw_tool_start (PIKA_DRAW_TOOL (tool), display); } /* public functions */ gboolean pika_polygon_select_tool_is_closed (PikaPolygonSelectTool *poly_sel) { PikaPolygonSelectToolPrivate *priv; g_return_val_if_fail (PIKA_IS_POLYGON_SELECT_TOOL (poly_sel), FALSE); priv = poly_sel->priv; if (priv->widget) return pika_tool_polygon_is_closed (PIKA_TOOL_POLYGON (priv->widget)); return FALSE; } void pika_polygon_select_tool_get_points (PikaPolygonSelectTool *poly_sel, const PikaVector2 **points, gint *n_points) { PikaPolygonSelectToolPrivate *priv; g_return_if_fail (PIKA_IS_POLYGON_SELECT_TOOL (poly_sel)); priv = poly_sel->priv; if (priv->widget) { pika_tool_polygon_get_points (PIKA_TOOL_POLYGON (priv->widget), points, n_points); } else { if (points) *points = NULL; if (n_points) *n_points = 0; } } /* protected functions */ gboolean pika_polygon_select_tool_is_grabbed (PikaPolygonSelectTool *poly_sel) { PikaPolygonSelectToolPrivate *priv; g_return_val_if_fail (PIKA_IS_POLYGON_SELECT_TOOL (poly_sel), FALSE); priv = poly_sel->priv; return priv->grab_widget != NULL; } void pika_polygon_select_tool_halt (PikaPolygonSelectTool *poly_sel) { PikaPolygonSelectToolPrivate *priv; g_return_if_fail (PIKA_IS_POLYGON_SELECT_TOOL (poly_sel)); priv = poly_sel->priv; pika_draw_tool_set_widget (PIKA_DRAW_TOOL (poly_sel), NULL); g_clear_object (&priv->widget); }