/* 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 * * Vector tool * Copyright (C) 2003 Simon Budig * * 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 "libpikaconfig/pikaconfig.h" #include "libpikawidgets/pikawidgets.h" #include "tools-types.h" #include "config/pikadialogconfig.h" #include "core/pika.h" #include "core/pikaimage.h" #include "core/pikaimage-pick-item.h" #include "core/pikaimage-undo.h" #include "core/pikaimage-undo-push.h" #include "core/pikatoolinfo.h" #include "core/pikaundostack.h" #include "paint/pikapaintoptions.h" /* PIKA_PAINT_OPTIONS_CONTEXT_MASK */ #include "vectors/pikavectors.h" #include "widgets/pikadialogfactory.h" #include "widgets/pikadockcontainer.h" #include "widgets/pikahelp-ids.h" #include "widgets/pikamenufactory.h" #include "widgets/pikauimanager.h" #include "widgets/pikawidgets-utils.h" #include "display/pikadisplay.h" #include "display/pikadisplayshell.h" #include "display/pikatoolpath.h" #include "pikatoolcontrol.h" #include "pikavectoroptions.h" #include "pikavectortool.h" #include "dialogs/fill-dialog.h" #include "dialogs/stroke-dialog.h" #include "pika-intl.h" #define TOGGLE_MASK pika_get_extend_selection_mask () #define MOVE_MASK GDK_MOD1_MASK #define INSDEL_MASK pika_get_toggle_behavior_mask () /* local function prototypes */ static void pika_vector_tool_dispose (GObject *object); static void pika_vector_tool_control (PikaTool *tool, PikaToolAction action, PikaDisplay *display); static void pika_vector_tool_button_press (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonPressType press_type, PikaDisplay *display); static void pika_vector_tool_button_release (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonReleaseType release_type, PikaDisplay *display); static void pika_vector_tool_motion (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaDisplay *display); static void pika_vector_tool_modifier_key (PikaTool *tool, GdkModifierType key, gboolean press, GdkModifierType state, PikaDisplay *display); static void pika_vector_tool_cursor_update (PikaTool *tool, const PikaCoords *coords, GdkModifierType state, PikaDisplay *display); static void pika_vector_tool_start (PikaVectorTool *vector_tool, PikaDisplay *display); static void pika_vector_tool_halt (PikaVectorTool *vector_tool); static void pika_vector_tool_path_changed (PikaToolWidget *path, PikaVectorTool *vector_tool); static void pika_vector_tool_path_begin_change (PikaToolWidget *path, const gchar *desc, PikaVectorTool *vector_tool); static void pika_vector_tool_path_end_change (PikaToolWidget *path, gboolean success, PikaVectorTool *vector_tool); static void pika_vector_tool_path_activate (PikaToolWidget *path, GdkModifierType state, PikaVectorTool *vector_tool); static void pika_vector_tool_vectors_changed (PikaImage *image, PikaVectorTool *vector_tool); static void pika_vector_tool_vectors_removed (PikaVectors *vectors, PikaVectorTool *vector_tool); static void pika_vector_tool_to_selection (PikaVectorTool *vector_tool); static void pika_vector_tool_to_selection_extended (PikaVectorTool *vector_tool, GdkModifierType state); static void pika_vector_tool_fill_vectors (PikaVectorTool *vector_tool, GtkWidget *button); static void pika_vector_tool_fill_callback (GtkWidget *dialog, GList *items, GList *drawables, PikaContext *context, PikaFillOptions *options, gpointer data); static void pika_vector_tool_stroke_vectors (PikaVectorTool *vector_tool, GtkWidget *button); static void pika_vector_tool_stroke_callback (GtkWidget *dialog, GList *items, GList *drawables, PikaContext *context, PikaStrokeOptions *options, gpointer data); G_DEFINE_TYPE (PikaVectorTool, pika_vector_tool, PIKA_TYPE_DRAW_TOOL) #define parent_class pika_vector_tool_parent_class void pika_vector_tool_register (PikaToolRegisterCallback callback, gpointer data) { (* callback) (PIKA_TYPE_VECTOR_TOOL, PIKA_TYPE_VECTOR_OPTIONS, pika_vector_options_gui, PIKA_PAINT_OPTIONS_CONTEXT_MASK | PIKA_CONTEXT_PROP_MASK_PATTERN | PIKA_CONTEXT_PROP_MASK_GRADIENT, /* for stroking */ "pika-vector-tool", _("Paths"), _("Paths Tool: Create and edit paths"), N_("Pat_hs"), "b", NULL, PIKA_HELP_TOOL_PATH, PIKA_ICON_TOOL_PATH, data); } static void pika_vector_tool_class_init (PikaVectorToolClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); PikaToolClass *tool_class = PIKA_TOOL_CLASS (klass); object_class->dispose = pika_vector_tool_dispose; tool_class->control = pika_vector_tool_control; tool_class->button_press = pika_vector_tool_button_press; tool_class->button_release = pika_vector_tool_button_release; tool_class->motion = pika_vector_tool_motion; tool_class->modifier_key = pika_vector_tool_modifier_key; tool_class->cursor_update = pika_vector_tool_cursor_update; } static void pika_vector_tool_init (PikaVectorTool *vector_tool) { PikaTool *tool = PIKA_TOOL (vector_tool); pika_tool_control_set_handle_empty_image (tool->control, TRUE); pika_tool_control_set_precision (tool->control, PIKA_CURSOR_PRECISION_SUBPIXEL); pika_tool_control_set_tool_cursor (tool->control, PIKA_TOOL_CURSOR_PATHS); vector_tool->saved_mode = PIKA_VECTOR_MODE_DESIGN; } static void pika_vector_tool_dispose (GObject *object) { PikaVectorTool *vector_tool = PIKA_VECTOR_TOOL (object); pika_vector_tool_set_vectors (vector_tool, NULL); g_clear_object (&vector_tool->widget); G_OBJECT_CLASS (parent_class)->dispose (object); } static void pika_vector_tool_control (PikaTool *tool, PikaToolAction action, PikaDisplay *display) { PikaVectorTool *vector_tool = PIKA_VECTOR_TOOL (tool); switch (action) { case PIKA_TOOL_ACTION_PAUSE: case PIKA_TOOL_ACTION_RESUME: break; case PIKA_TOOL_ACTION_HALT: pika_vector_tool_halt (vector_tool); break; case PIKA_TOOL_ACTION_COMMIT: break; } PIKA_TOOL_CLASS (parent_class)->control (tool, action, display); } static void pika_vector_tool_button_press (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonPressType press_type, PikaDisplay *display) { PikaVectorTool *vector_tool = PIKA_VECTOR_TOOL (tool); if (tool->display && display != tool->display) pika_tool_control (tool, PIKA_TOOL_ACTION_HALT, tool->display); if (! tool->display) { pika_vector_tool_start (vector_tool, display); pika_tool_widget_hover (vector_tool->widget, coords, state, TRUE); } if (pika_tool_widget_button_press (vector_tool->widget, coords, time, state, press_type)) { vector_tool->grab_widget = vector_tool->widget; } pika_tool_control_activate (tool->control); } static void pika_vector_tool_button_release (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonReleaseType release_type, PikaDisplay *display) { PikaVectorTool *vector_tool = PIKA_VECTOR_TOOL (tool); pika_tool_control_halt (tool->control); if (vector_tool->grab_widget) { pika_tool_widget_button_release (vector_tool->grab_widget, coords, time, state, release_type); vector_tool->grab_widget = NULL; } } static void pika_vector_tool_motion (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaDisplay *display) { PikaVectorTool *vector_tool = PIKA_VECTOR_TOOL (tool); if (vector_tool->grab_widget) { pika_tool_widget_motion (vector_tool->grab_widget, coords, time, state); } } static void pika_vector_tool_modifier_key (PikaTool *tool, GdkModifierType key, gboolean press, GdkModifierType state, PikaDisplay *display) { PikaVectorTool *vector_tool = PIKA_VECTOR_TOOL (tool); PikaVectorOptions *options = PIKA_VECTOR_TOOL_GET_OPTIONS (tool); if (key == TOGGLE_MASK) return; if (key == INSDEL_MASK || key == MOVE_MASK) { PikaVectorMode button_mode = options->edit_mode; if (press) { if (key == (state & (INSDEL_MASK | MOVE_MASK))) { /* first modifier pressed */ vector_tool->saved_mode = options->edit_mode; } } else { if (! (state & (INSDEL_MASK | MOVE_MASK))) { /* last modifier released */ button_mode = vector_tool->saved_mode; } } if (state & MOVE_MASK) { button_mode = PIKA_VECTOR_MODE_MOVE; } else if (state & INSDEL_MASK) { button_mode = PIKA_VECTOR_MODE_EDIT; } if (button_mode != options->edit_mode) { g_object_set (options, "vectors-edit-mode", button_mode, NULL); } } } static void pika_vector_tool_cursor_update (PikaTool *tool, const PikaCoords *coords, GdkModifierType state, PikaDisplay *display) { PikaVectorTool *vector_tool = PIKA_VECTOR_TOOL (tool); PikaDisplayShell *shell = pika_display_get_shell (display); if (display != tool->display || ! vector_tool->widget) { PikaToolCursorType tool_cursor = PIKA_TOOL_CURSOR_PATHS; if (pika_image_pick_vectors (pika_display_get_image (display), coords->x, coords->y, FUNSCALEX (shell, PIKA_TOOL_HANDLE_SIZE_CIRCLE / 2), FUNSCALEY (shell, PIKA_TOOL_HANDLE_SIZE_CIRCLE / 2))) { tool_cursor = PIKA_TOOL_CURSOR_HAND; } pika_tool_control_set_tool_cursor (tool->control, tool_cursor); } PIKA_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display); } static void pika_vector_tool_start (PikaVectorTool *vector_tool, PikaDisplay *display) { PikaTool *tool = PIKA_TOOL (vector_tool); PikaVectorOptions *options = PIKA_VECTOR_TOOL_GET_OPTIONS (tool); PikaDisplayShell *shell = pika_display_get_shell (display); PikaToolWidget *widget; tool->display = display; vector_tool->widget = widget = pika_tool_path_new (shell); pika_draw_tool_set_widget (PIKA_DRAW_TOOL (tool), widget); g_object_bind_property (G_OBJECT (options), "vectors-edit-mode", G_OBJECT (widget), "edit-mode", G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL); g_object_bind_property (G_OBJECT (options), "vectors-polygonal", G_OBJECT (widget), "polygonal", G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL); pika_tool_path_set_vectors (PIKA_TOOL_PATH (widget), vector_tool->vectors); g_signal_connect (widget, "changed", G_CALLBACK (pika_vector_tool_path_changed), vector_tool); g_signal_connect (widget, "begin-change", G_CALLBACK (pika_vector_tool_path_begin_change), vector_tool); g_signal_connect (widget, "end-change", G_CALLBACK (pika_vector_tool_path_end_change), vector_tool); g_signal_connect (widget, "activate", G_CALLBACK (pika_vector_tool_path_activate), vector_tool); pika_draw_tool_start (PIKA_DRAW_TOOL (tool), display); } static void pika_vector_tool_halt (PikaVectorTool *vector_tool) { PikaTool *tool = PIKA_TOOL (vector_tool); if (tool->display) pika_tool_pop_status (tool, tool->display); pika_vector_tool_set_vectors (vector_tool, NULL); if (pika_draw_tool_is_active (PIKA_DRAW_TOOL (tool))) pika_draw_tool_stop (PIKA_DRAW_TOOL (tool)); pika_draw_tool_set_widget (PIKA_DRAW_TOOL (tool), NULL); g_clear_object (&vector_tool->widget); tool->display = NULL; } static void pika_vector_tool_path_changed (PikaToolWidget *path, PikaVectorTool *vector_tool) { PikaDisplayShell *shell = pika_tool_widget_get_shell (path); PikaImage *image = pika_display_get_image (shell->display); PikaVectors *vectors; g_object_get (path, "vectors", &vectors, NULL); if (vectors != vector_tool->vectors) { if (vectors && ! pika_item_is_attached (PIKA_ITEM (vectors))) { pika_image_add_vectors (image, vectors, PIKA_IMAGE_ACTIVE_PARENT, -1, TRUE); pika_image_flush (image); pika_vector_tool_set_vectors (vector_tool, vectors); } else { pika_vector_tool_set_vectors (vector_tool, vectors); if (vectors) { GList *list = g_list_prepend (NULL, vectors); pika_image_set_selected_vectors (image, list); g_list_free (list); } } } if (vectors) g_object_unref (vectors); } static void pika_vector_tool_path_begin_change (PikaToolWidget *path, const gchar *desc, PikaVectorTool *vector_tool) { PikaDisplayShell *shell = pika_tool_widget_get_shell (path); PikaImage *image = pika_display_get_image (shell->display); pika_image_undo_push_vectors_mod (image, desc, vector_tool->vectors); } static void pika_vector_tool_path_end_change (PikaToolWidget *path, gboolean success, PikaVectorTool *vector_tool) { PikaDisplayShell *shell = pika_tool_widget_get_shell (path); PikaImage *image = pika_display_get_image (shell->display); if (! success) { PikaUndo *undo; PikaUndoAccumulator accum = { 0, }; undo = pika_undo_stack_pop_undo (pika_image_get_undo_stack (image), PIKA_UNDO_MODE_UNDO, &accum); pika_image_undo_event (image, PIKA_UNDO_EVENT_UNDO_EXPIRED, undo); pika_undo_free (undo, PIKA_UNDO_MODE_UNDO); g_object_unref (undo); } pika_image_flush (image); } static void pika_vector_tool_path_activate (PikaToolWidget *path, GdkModifierType state, PikaVectorTool *vector_tool) { pika_vector_tool_to_selection_extended (vector_tool, state); } static void pika_vector_tool_vectors_changed (PikaImage *image, PikaVectorTool *vector_tool) { PikaVectors *vectors = NULL; /* The vectors tool can only work on one path at a time. */ if (g_list_length (pika_image_get_selected_vectors (image)) == 1) vectors = pika_image_get_selected_vectors (image)->data; pika_vector_tool_set_vectors (vector_tool, vectors); } static void pika_vector_tool_vectors_removed (PikaVectors *vectors, PikaVectorTool *vector_tool) { pika_vector_tool_set_vectors (vector_tool, NULL); } void pika_vector_tool_set_vectors (PikaVectorTool *vector_tool, PikaVectors *vectors) { PikaTool *tool; PikaItem *item = NULL; PikaVectorOptions *options; g_return_if_fail (PIKA_IS_VECTOR_TOOL (vector_tool)); g_return_if_fail (vectors == NULL || PIKA_IS_VECTORS (vectors)); tool = PIKA_TOOL (vector_tool); options = PIKA_VECTOR_TOOL_GET_OPTIONS (vector_tool); if (vectors) item = PIKA_ITEM (vectors); if (vectors == vector_tool->vectors) return; if (vector_tool->vectors) { PikaImage *old_image; old_image = pika_item_get_image (PIKA_ITEM (vector_tool->vectors)); g_signal_handlers_disconnect_by_func (old_image, pika_vector_tool_vectors_changed, vector_tool); g_signal_handlers_disconnect_by_func (vector_tool->vectors, pika_vector_tool_vectors_removed, vector_tool); g_clear_object (&vector_tool->vectors); if (options->to_selection_button) { gtk_widget_set_sensitive (options->to_selection_button, FALSE); g_signal_handlers_disconnect_by_func (options->to_selection_button, pika_vector_tool_to_selection, tool); g_signal_handlers_disconnect_by_func (options->to_selection_button, pika_vector_tool_to_selection_extended, tool); } if (options->fill_button) { gtk_widget_set_sensitive (options->fill_button, FALSE); g_signal_handlers_disconnect_by_func (options->fill_button, pika_vector_tool_fill_vectors, tool); } if (options->stroke_button) { gtk_widget_set_sensitive (options->stroke_button, FALSE); g_signal_handlers_disconnect_by_func (options->stroke_button, pika_vector_tool_stroke_vectors, tool); } } if (! vectors || (tool->display && pika_display_get_image (tool->display) != pika_item_get_image (item))) { pika_vector_tool_halt (vector_tool); } if (! vectors) return; vector_tool->vectors = g_object_ref (vectors); g_signal_connect_object (pika_item_get_image (item), "selected-vectors-changed", G_CALLBACK (pika_vector_tool_vectors_changed), vector_tool, 0); g_signal_connect_object (vectors, "removed", G_CALLBACK (pika_vector_tool_vectors_removed), vector_tool, 0); if (options->to_selection_button) { g_signal_connect_swapped (options->to_selection_button, "clicked", G_CALLBACK (pika_vector_tool_to_selection), tool); g_signal_connect_swapped (options->to_selection_button, "extended-clicked", G_CALLBACK (pika_vector_tool_to_selection_extended), tool); gtk_widget_set_sensitive (options->to_selection_button, TRUE); } if (options->fill_button) { g_signal_connect_swapped (options->fill_button, "clicked", G_CALLBACK (pika_vector_tool_fill_vectors), tool); gtk_widget_set_sensitive (options->fill_button, TRUE); } if (options->stroke_button) { g_signal_connect_swapped (options->stroke_button, "clicked", G_CALLBACK (pika_vector_tool_stroke_vectors), tool); gtk_widget_set_sensitive (options->stroke_button, TRUE); } if (tool->display) { pika_tool_path_set_vectors (PIKA_TOOL_PATH (vector_tool->widget), vectors); } else { PikaContext *context = pika_get_user_context (tool->tool_info->pika); PikaDisplay *display = pika_context_get_display (context); if (! display || pika_display_get_image (display) != pika_item_get_image (item)) { GList *list; display = NULL; for (list = pika_get_display_iter (pika_item_get_image (item)->pika); list; list = g_list_next (list)) { display = list->data; if (pika_display_get_image (display) == pika_item_get_image (item)) { pika_context_set_display (context, display); break; } display = NULL; } } if (display) pika_vector_tool_start (vector_tool, display); } if (options->edit_mode != PIKA_VECTOR_MODE_DESIGN) g_object_set (options, "vectors-edit-mode", PIKA_VECTOR_MODE_DESIGN, NULL); } static void pika_vector_tool_to_selection (PikaVectorTool *vector_tool) { pika_vector_tool_to_selection_extended (vector_tool, 0); } static void pika_vector_tool_to_selection_extended (PikaVectorTool *vector_tool, GdkModifierType state) { PikaImage *image; if (! vector_tool->vectors) return; image = pika_item_get_image (PIKA_ITEM (vector_tool->vectors)); pika_item_to_selection (PIKA_ITEM (vector_tool->vectors), pika_modifiers_to_channel_op (state), TRUE, FALSE, 0, 0); pika_image_flush (image); } static void pika_vector_tool_fill_vectors (PikaVectorTool *vector_tool, GtkWidget *button) { PikaDialogConfig *config; PikaImage *image; GList *drawables; GList *vectors_list = NULL; GtkWidget *dialog; if (! vector_tool->vectors) return; image = pika_item_get_image (PIKA_ITEM (vector_tool->vectors)); config = PIKA_DIALOG_CONFIG (image->pika->config); drawables = pika_image_get_selected_drawables (image); if (! drawables) { pika_tool_message (PIKA_TOOL (vector_tool), PIKA_TOOL (vector_tool)->display, _("There are no selected layers or channels to fill.")); return; } vectors_list = g_list_prepend (NULL, vector_tool->vectors); dialog = fill_dialog_new (vectors_list, drawables, PIKA_CONTEXT (PIKA_TOOL_GET_OPTIONS (vector_tool)), _("Fill Path"), PIKA_ICON_TOOL_BUCKET_FILL, PIKA_HELP_PATH_FILL, button, config->fill_options, pika_vector_tool_fill_callback, vector_tool); gtk_widget_show (dialog); g_list_free (vectors_list); g_list_free (drawables); } static void pika_vector_tool_fill_callback (GtkWidget *dialog, GList *items, GList *drawables, PikaContext *context, PikaFillOptions *options, gpointer data) { PikaDialogConfig *config = PIKA_DIALOG_CONFIG (context->pika->config); PikaImage *image = pika_item_get_image (items->data); GError *error = NULL; pika_config_sync (G_OBJECT (options), G_OBJECT (config->fill_options), 0); pika_image_undo_group_start (image, PIKA_UNDO_GROUP_DRAWABLE_MOD, "Fill"); for (GList *iter = items; iter; iter = iter->next) if (! pika_item_fill (iter->data, drawables, options, TRUE, NULL, &error)) { pika_message_literal (context->pika, G_OBJECT (dialog), PIKA_MESSAGE_WARNING, error ? error->message : "NULL"); g_clear_error (&error); break; } pika_image_undo_group_end (image); pika_image_flush (image); gtk_widget_destroy (dialog); } static void pika_vector_tool_stroke_vectors (PikaVectorTool *vector_tool, GtkWidget *button) { PikaDialogConfig *config; PikaImage *image; GList *drawables; GList *vectors_list = NULL; GtkWidget *dialog; if (! vector_tool->vectors) return; image = pika_item_get_image (PIKA_ITEM (vector_tool->vectors)); config = PIKA_DIALOG_CONFIG (image->pika->config); drawables = pika_image_get_selected_drawables (image); if (! drawables) { pika_tool_message (PIKA_TOOL (vector_tool), PIKA_TOOL (vector_tool)->display, _("There are no selected layers or channels to stroke to.")); return; } vectors_list = g_list_prepend (NULL, vector_tool->vectors); dialog = stroke_dialog_new (vectors_list, drawables, PIKA_CONTEXT (PIKA_TOOL_GET_OPTIONS (vector_tool)), _("Stroke Path"), PIKA_ICON_PATH_STROKE, PIKA_HELP_PATH_STROKE, button, config->stroke_options, pika_vector_tool_stroke_callback, vector_tool); gtk_widget_show (dialog); g_list_free (vectors_list); g_list_free (drawables); } static void pika_vector_tool_stroke_callback (GtkWidget *dialog, GList *items, GList *drawables, PikaContext *context, PikaStrokeOptions *options, gpointer data) { PikaDialogConfig *config = PIKA_DIALOG_CONFIG (context->pika->config); PikaImage *image = pika_item_get_image (items->data); GError *error = NULL; pika_config_sync (G_OBJECT (options), G_OBJECT (config->stroke_options), 0); pika_image_undo_group_start (image, PIKA_UNDO_GROUP_DRAWABLE_MOD, "Stroke"); for (GList *iter = items; iter; iter = iter->next) if (! pika_item_stroke (iter->data, drawables, context, options, NULL, TRUE, NULL, &error)) { pika_message_literal (context->pika, G_OBJECT (dialog), PIKA_MESSAGE_WARNING, error ? error->message : "NULL"); g_clear_error (&error); break; } pika_image_undo_group_end (image); pika_image_flush (image); gtk_widget_destroy (dialog); }