/* 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 * * 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 #include "libpikamath/pikamath.h" #include "libpikawidgets/pikawidgets.h" #include "tools-types.h" #include "config/pikadisplayconfig.h" #include "config/pikaguiconfig.h" #include "core/pika.h" #include "core/pika-cairo.h" #include "core/pika-utils.h" #include "core/pikaguide.h" #include "core/pikaimage.h" #include "core/pikaimage-pick-item.h" #include "core/pikalayer.h" #include "core/pikaimage-undo.h" #include "core/pikalayermask.h" #include "core/pikalayer-floating-selection.h" #include "core/pikaundostack.h" #include "widgets/pikahelp-ids.h" #include "widgets/pikawidgets-utils.h" #include "display/pikacanvasitem.h" #include "display/pikadisplay.h" #include "display/pikadisplayshell.h" #include "display/pikadisplayshell-appearance.h" #include "display/pikadisplayshell-selection.h" #include "display/pikadisplayshell-transform.h" #include "pikaeditselectiontool.h" #include "pikaguidetool.h" #include "pikamoveoptions.h" #include "pikamovetool.h" #include "pikatoolcontrol.h" #include "pikatools-utils.h" #include "pika-intl.h" /* local function prototypes */ static void pika_move_tool_finalize (GObject *object); static void pika_move_tool_button_press (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonPressType press_type, PikaDisplay *display); static void pika_move_tool_button_release (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonReleaseType release_type, PikaDisplay *display); static gboolean pika_move_tool_key_press (PikaTool *tool, GdkEventKey *kevent, PikaDisplay *display); static void pika_move_tool_modifier_key (PikaTool *tool, GdkModifierType key, gboolean press, GdkModifierType state, PikaDisplay *display); static void pika_move_tool_oper_update (PikaTool *tool, const PikaCoords *coords, GdkModifierType state, gboolean proximity, PikaDisplay *display); static void pika_move_tool_cursor_update (PikaTool *tool, const PikaCoords *coords, GdkModifierType state, PikaDisplay *display); static void pika_move_tool_draw (PikaDrawTool *draw_tool); G_DEFINE_TYPE (PikaMoveTool, pika_move_tool, PIKA_TYPE_DRAW_TOOL) #define parent_class pika_move_tool_parent_class void pika_move_tool_register (PikaToolRegisterCallback callback, gpointer data) { (* callback) (PIKA_TYPE_MOVE_TOOL, PIKA_TYPE_MOVE_OPTIONS, pika_move_options_gui, 0, "pika-move-tool", C_("tool", "Move"), _("Move Tool: Move layers, selections, and other objects"), N_("_Move"), "M", NULL, PIKA_HELP_TOOL_MOVE, PIKA_ICON_TOOL_MOVE, data); } static void pika_move_tool_class_init (PikaMoveToolClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); PikaToolClass *tool_class = PIKA_TOOL_CLASS (klass); PikaDrawToolClass *draw_tool_class = PIKA_DRAW_TOOL_CLASS (klass); object_class->finalize = pika_move_tool_finalize; tool_class->button_press = pika_move_tool_button_press; tool_class->button_release = pika_move_tool_button_release; tool_class->key_press = pika_move_tool_key_press; tool_class->modifier_key = pika_move_tool_modifier_key; tool_class->oper_update = pika_move_tool_oper_update; tool_class->cursor_update = pika_move_tool_cursor_update; draw_tool_class->draw = pika_move_tool_draw; } static void pika_move_tool_init (PikaMoveTool *move_tool) { PikaTool *tool = PIKA_TOOL (move_tool); pika_tool_control_set_snap_to (tool->control, FALSE); pika_tool_control_set_handle_empty_image (tool->control, TRUE); pika_tool_control_set_tool_cursor (tool->control, PIKA_TOOL_CURSOR_MOVE); move_tool->floating_layer = NULL; move_tool->guides = NULL; move_tool->saved_type = PIKA_TRANSFORM_TYPE_LAYER; move_tool->old_selected_layers = NULL; move_tool->old_selected_vectors = NULL; } static void pika_move_tool_finalize (GObject *object) { PikaMoveTool *move = PIKA_MOVE_TOOL (object); g_clear_pointer (&move->guides, g_list_free); g_clear_pointer (&move->old_selected_layers, g_list_free); g_clear_pointer (&move->old_selected_vectors, g_list_free); G_OBJECT_CLASS (parent_class)->finalize (object); } static void pika_move_tool_button_press (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonPressType press_type, PikaDisplay *display) { PikaMoveTool *move = PIKA_MOVE_TOOL (tool); PikaMoveOptions *options = PIKA_MOVE_TOOL_GET_OPTIONS (tool); PikaDisplayShell *shell = pika_display_get_shell (display); PikaImage *image = pika_display_get_image (display); PikaItem *active_item = NULL; GList *selected_items = NULL; GList *iter; PikaTranslateMode translate_mode = PIKA_TRANSLATE_MODE_MASK; const gchar *null_message = NULL; const gchar *locked_message = NULL; PikaItem *locked_item = NULL; tool->display = display; move->floating_layer = NULL; g_clear_pointer (&move->guides, g_list_free); if (! options->move_current) { const gint snap_distance = display->config->snap_distance; if (options->move_type == PIKA_TRANSFORM_TYPE_PATH) { PikaVectors *vectors; vectors = pika_image_pick_vectors (image, coords->x, coords->y, FUNSCALEX (shell, snap_distance), FUNSCALEY (shell, snap_distance)); if (vectors) { GList *new_selected_vectors = g_list_prepend (NULL, vectors); move->old_selected_vectors = g_list_copy (pika_image_get_selected_vectors (image)); pika_image_set_selected_vectors (image, new_selected_vectors); g_list_free (new_selected_vectors); } else { /* no path picked */ return; } } else if (options->move_type == PIKA_TRANSFORM_TYPE_LAYER) { GList *guides; PikaLayer *layer; if (pika_display_shell_get_show_guides (shell) && (guides = pika_image_pick_guides (image, coords->x, coords->y, FUNSCALEX (shell, snap_distance), FUNSCALEY (shell, snap_distance)))) { move->guides = guides; pika_guide_tool_start_edit_many (tool, display, guides); return; } else if ((layer = pika_image_pick_layer (image, coords->x, coords->y, NULL))) { if (pika_image_get_floating_selection (image) && ! pika_layer_is_floating_sel (layer)) { /* If there is a floating selection, and this aint it, * use the move tool to anchor it. */ move->floating_layer = pika_image_get_floating_selection (image); pika_tool_control_activate (tool->control); return; } else { GList *new_selected_layers = g_list_prepend (NULL, layer); move->old_selected_layers = g_list_copy (pika_image_get_selected_layers (image)); pika_image_set_selected_layers (image, new_selected_layers); g_list_free (new_selected_layers); } } else { /* no guide and no layer picked */ return; } } } switch (options->move_type) { case PIKA_TRANSFORM_TYPE_PATH: { selected_items = pika_image_get_selected_vectors (image); selected_items = g_list_copy (selected_items); translate_mode = PIKA_TRANSLATE_MODE_VECTORS; if (! selected_items) { null_message = _("There are no paths to move."); } else { gint n_items = 0; for (iter = selected_items; iter; iter = iter->next) { if (! pika_item_is_position_locked (iter->data, &locked_item)) n_items++; } if (n_items == 0) locked_message = _("All selected path's position are locked."); } } break; case PIKA_TRANSFORM_TYPE_SELECTION: { active_item = PIKA_ITEM (pika_image_get_mask (image)); if (pika_channel_is_empty (PIKA_CHANNEL (active_item))) active_item = NULL; translate_mode = PIKA_TRANSLATE_MODE_MASK; if (! active_item) { /* cannot happen, don't translate this message */ null_message = "There is no selection to move."; } else if (pika_item_is_position_locked (active_item, &locked_item)) { locked_message = "The selection's position is locked."; } } break; case PIKA_TRANSFORM_TYPE_LAYER: { selected_items = pika_image_get_selected_drawables (image); if (! selected_items) { null_message = _("There is no layer to move."); } else if (PIKA_IS_LAYER_MASK (selected_items->data)) { g_return_if_fail (g_list_length (selected_items) == 1); translate_mode = PIKA_TRANSLATE_MODE_LAYER_MASK; if (pika_item_is_position_locked (selected_items->data, &locked_item)) locked_message = _("The selected layer's position is locked."); else if (pika_item_is_content_locked (selected_items->data, NULL)) locked_message = _("The selected layer's pixels are locked."); } else if (PIKA_IS_CHANNEL (selected_items->data)) { translate_mode = PIKA_TRANSLATE_MODE_CHANNEL; for (iter = selected_items; iter; iter = iter->next) if (pika_item_is_position_locked (iter->data, &locked_item) || pika_item_is_content_locked (iter->data, &locked_item)) locked_message = _("A selected channel's position or pixels are locked."); } else { translate_mode = PIKA_TRANSLATE_MODE_LAYER; for (iter = selected_items; iter; iter = iter->next) if (pika_item_is_position_locked (iter->data, &locked_item)) locked_message = _("A selected layer's position is locked."); } } break; case PIKA_TRANSFORM_TYPE_IMAGE: g_return_if_reached (); } if (! active_item && ! selected_items) { pika_tool_message_literal (tool, display, null_message); pika_tools_show_tool_options (display->pika); pika_widget_blink (options->type_box); pika_tool_control (tool, PIKA_TOOL_ACTION_HALT, display); return; } else if (locked_message) { pika_tool_message_literal (tool, display, locked_message); if (locked_item == NULL) locked_item = active_item ? active_item : selected_items->data; pika_tools_blink_lock_box (display->pika, locked_item); pika_tool_control (tool, PIKA_TOOL_ACTION_HALT, display); g_list_free (selected_items); return; } pika_tool_control_activate (tool->control); pika_edit_selection_tool_start (tool, display, coords, translate_mode, TRUE); g_list_free (selected_items); } static void pika_move_tool_button_release (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonReleaseType release_type, PikaDisplay *display) { PikaMoveTool *move = PIKA_MOVE_TOOL (tool); PikaGuiConfig *config = PIKA_GUI_CONFIG (display->pika->config); PikaImage *image = pika_display_get_image (display); gboolean flush = FALSE; pika_tool_control_halt (tool->control); if (! config->move_tool_changes_active || (release_type == PIKA_BUTTON_RELEASE_CANCEL)) { if (move->old_selected_layers) { pika_image_set_selected_layers (image, move->old_selected_layers); g_clear_pointer (&move->old_selected_layers, g_list_free); flush = TRUE; } if (move->old_selected_vectors) { pika_image_set_selected_vectors (image, move->old_selected_vectors); g_clear_pointer (&move->old_selected_vectors, g_list_free); flush = TRUE; } } if (release_type != PIKA_BUTTON_RELEASE_CANCEL) { if (move->floating_layer) { floating_sel_anchor (move->floating_layer); flush = TRUE; } } if (flush) pika_image_flush (image); } static gboolean pika_move_tool_key_press (PikaTool *tool, GdkEventKey *kevent, PikaDisplay *display) { PikaMoveOptions *options = PIKA_MOVE_TOOL_GET_OPTIONS (tool); return pika_edit_selection_tool_translate (tool, kevent, options->move_type, display, &options->type_box); } static void pika_move_tool_modifier_key (PikaTool *tool, GdkModifierType key, gboolean press, GdkModifierType state, PikaDisplay *display) { PikaMoveTool *move = PIKA_MOVE_TOOL (tool); PikaMoveOptions *options = PIKA_MOVE_TOOL_GET_OPTIONS (tool); if (key == pika_get_extend_selection_mask ()) { g_object_set (options, "move-current", ! options->move_current, NULL); } else if (key == GDK_MOD1_MASK || key == pika_get_toggle_behavior_mask ()) { PikaTransformType button_type; button_type = options->move_type; if (press) { if (key == (state & (GDK_MOD1_MASK | pika_get_toggle_behavior_mask ()))) { /* first modifier pressed */ move->saved_type = options->move_type; } } else { if (! (state & (GDK_MOD1_MASK | pika_get_toggle_behavior_mask ()))) { /* last modifier released */ button_type = move->saved_type; } } if (state & GDK_MOD1_MASK) { button_type = PIKA_TRANSFORM_TYPE_SELECTION; } else if (state & pika_get_toggle_behavior_mask ()) { button_type = PIKA_TRANSFORM_TYPE_PATH; } if (button_type != options->move_type) { g_object_set (options, "move-type", button_type, NULL); } } } static void pika_move_tool_oper_update (PikaTool *tool, const PikaCoords *coords, GdkModifierType state, gboolean proximity, PikaDisplay *display) { PikaMoveTool *move = PIKA_MOVE_TOOL (tool); PikaMoveOptions *options = PIKA_MOVE_TOOL_GET_OPTIONS (tool); PikaDisplayShell *shell = pika_display_get_shell (display); PikaImage *image = pika_display_get_image (display); GList *guides = NULL; if (options->move_type == PIKA_TRANSFORM_TYPE_LAYER && ! options->move_current && pika_display_shell_get_show_guides (shell) && proximity) { gint snap_distance = display->config->snap_distance; guides = pika_image_pick_guides (image, coords->x, coords->y, FUNSCALEX (shell, snap_distance), FUNSCALEY (shell, snap_distance)); } if (pika_g_list_compare (guides, move->guides)) { PikaDrawTool *draw_tool = PIKA_DRAW_TOOL (tool); pika_draw_tool_pause (draw_tool); if (pika_draw_tool_is_active (draw_tool) && draw_tool->display != display) pika_draw_tool_stop (draw_tool); g_clear_pointer (&move->guides, g_list_free); move->guides = guides; if (! pika_draw_tool_is_active (draw_tool)) pika_draw_tool_start (draw_tool, display); pika_draw_tool_resume (draw_tool); } else { g_list_free (guides); } } static void pika_move_tool_cursor_update (PikaTool *tool, const PikaCoords *coords, GdkModifierType state, PikaDisplay *display) { PikaMoveOptions *options = PIKA_MOVE_TOOL_GET_OPTIONS (tool); PikaDisplayShell *shell = pika_display_get_shell (display); PikaImage *image = pika_display_get_image (display); PikaCursorType cursor = PIKA_CURSOR_MOUSE; PikaToolCursorType tool_cursor = PIKA_TOOL_CURSOR_MOVE; PikaCursorModifier modifier = PIKA_CURSOR_MODIFIER_NONE; gint snap_distance = display->config->snap_distance; if (options->move_type == PIKA_TRANSFORM_TYPE_PATH) { tool_cursor = PIKA_TOOL_CURSOR_PATHS; modifier = PIKA_CURSOR_MODIFIER_MOVE; if (options->move_current) { GList *selected = pika_image_get_selected_vectors (image); GList *iter; gint n_items = 0; for (iter = selected; iter; iter = iter->next) { if (! pika_item_is_position_locked (iter->data, NULL)) n_items++; } if (n_items == 0) modifier = PIKA_CURSOR_MODIFIER_BAD; } else { if (pika_image_pick_vectors (image, coords->x, coords->y, FUNSCALEX (shell, snap_distance), FUNSCALEY (shell, snap_distance))) { tool_cursor = PIKA_TOOL_CURSOR_HAND; } else { modifier = PIKA_CURSOR_MODIFIER_BAD; } } } else if (options->move_type == PIKA_TRANSFORM_TYPE_SELECTION) { tool_cursor = PIKA_TOOL_CURSOR_RECT_SELECT; modifier = PIKA_CURSOR_MODIFIER_MOVE; if (pika_channel_is_empty (pika_image_get_mask (image))) modifier = PIKA_CURSOR_MODIFIER_BAD; } else if (options->move_current) { GList *items = pika_image_get_selected_drawables (image); GList *iter; gint n_items = 0; for (iter = items; iter; iter = iter->next) if (! pika_item_is_position_locked (iter->data, NULL)) n_items++; if (n_items == 0) modifier = PIKA_CURSOR_MODIFIER_BAD; g_list_free (items); } else { PikaLayer *layer; if (pika_display_shell_get_show_guides (shell) && pika_image_pick_guide (image, coords->x, coords->y, FUNSCALEX (shell, snap_distance), FUNSCALEY (shell, snap_distance))) { tool_cursor = PIKA_TOOL_CURSOR_HAND; modifier = PIKA_CURSOR_MODIFIER_MOVE; } else if ((layer = pika_image_pick_layer (image, coords->x, coords->y, NULL))) { /* if there is a floating selection, and this aint it... */ if (pika_image_get_floating_selection (image) && ! pika_layer_is_floating_sel (layer)) { tool_cursor = PIKA_TOOL_CURSOR_MOVE; modifier = PIKA_CURSOR_MODIFIER_ANCHOR; } else if (pika_item_is_position_locked (PIKA_ITEM (layer), NULL)) { modifier = PIKA_CURSOR_MODIFIER_BAD; } else if (! g_list_find (pika_image_get_selected_layers (image), layer)) { tool_cursor = PIKA_TOOL_CURSOR_HAND; modifier = PIKA_CURSOR_MODIFIER_MOVE; } } else { modifier = PIKA_CURSOR_MODIFIER_BAD; } } pika_tool_control_set_cursor (tool->control, cursor); pika_tool_control_set_tool_cursor (tool->control, tool_cursor); pika_tool_control_set_cursor_modifier (tool->control, modifier); PIKA_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display); } static void pika_move_tool_draw (PikaDrawTool *draw_tool) { PikaMoveTool *move = PIKA_MOVE_TOOL (draw_tool); GList *iter; for (iter = move->guides; iter; iter = g_list_next (iter)) { PikaGuide *guide = iter->data; PikaCanvasItem *item; PikaGuideStyle style; style = pika_guide_get_style (guide); item = pika_draw_tool_add_guide (draw_tool, pika_guide_get_orientation (guide), pika_guide_get_position (guide), style); pika_canvas_item_set_highlight (item, TRUE); } }