/* 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 "libpikaconfig/pikaconfig.h" #include "tools-types.h" #include "core/pika.h" #include "core/pikacontainer.h" #include "core/pikaimage.h" #include "core/pikatoolgroup.h" #include "core/pikatoolinfo.h" #include "core/pikatooloptions.h" #include "core/pikatoolpreset.h" #include "display/pikadisplay.h" #include "widgets/pikacairo-mascot.h" #include "pikagegltool.h" #include "pikatool.h" #include "pikatoolcontrol.h" #include "pikatransformgridtool.h" #include "tool_manager.h" typedef struct _PikaToolManager PikaToolManager; struct _PikaToolManager { Pika *pika; PikaTool *active_tool; GSList *tool_stack; PikaToolGroup *active_tool_group; PikaImage *image; GQuark image_clean_handler_id; GQuark image_dirty_handler_id; GQuark image_saving_handler_id; }; /* local function prototypes */ static PikaToolManager * tool_manager_get (Pika *pika); static void tool_manager_select_tool (PikaToolManager *tool_manager, PikaTool *tool); static void tool_manager_set_active_tool_group (PikaToolManager *tool_manager, PikaToolGroup *tool_group); static void tool_manager_tool_changed (PikaContext *user_context, PikaToolInfo *tool_info, PikaToolManager *tool_manager); static void tool_manager_preset_changed (PikaContext *user_context, PikaToolPreset *preset, PikaToolManager *tool_manager); static void tool_manager_image_clean_dirty (PikaImage *image, PikaDirtyMask dirty_mask, PikaToolManager *tool_manager); static void tool_manager_image_saving (PikaImage *image, PikaToolManager *tool_manager); static void tool_manager_tool_ancestry_changed (PikaToolInfo *tool_info, PikaToolManager *tool_manager); static void tool_manager_group_active_tool_changed (PikaToolGroup *tool_group, PikaToolManager *tool_manager); static void tool_manager_image_changed (PikaContext *context, PikaImage *image, PikaToolManager *tool_manager); static void tool_manager_selected_layers_changed (PikaImage *image, PikaToolManager *tool_manager); static void tool_manager_active_tool_deactivated (PikaContext *context, PikaToolManager *tool_manager); static void tool_manager_cast_spell (PikaToolInfo *tool_info); static GQuark tool_manager_quark = 0; /* public functions */ void tool_manager_init (Pika *pika) { PikaToolManager *tool_manager; PikaContext *user_context; g_return_if_fail (PIKA_IS_PIKA (pika)); g_return_if_fail (tool_manager_quark == 0); tool_manager_quark = g_quark_from_static_string ("pika-tool-manager"); tool_manager = g_slice_new0 (PikaToolManager); tool_manager->pika = pika; g_object_set_qdata (G_OBJECT (pika), tool_manager_quark, tool_manager); tool_manager->image_clean_handler_id = pika_container_add_handler (pika->images, "clean", G_CALLBACK (tool_manager_image_clean_dirty), tool_manager); tool_manager->image_dirty_handler_id = pika_container_add_handler (pika->images, "dirty", G_CALLBACK (tool_manager_image_clean_dirty), tool_manager); tool_manager->image_saving_handler_id = pika_container_add_handler (pika->images, "saving", G_CALLBACK (tool_manager_image_saving), tool_manager); user_context = pika_get_user_context (pika); g_signal_connect (user_context, "tool-changed", G_CALLBACK (tool_manager_tool_changed), tool_manager); g_signal_connect (user_context, "tool-preset-changed", G_CALLBACK (tool_manager_preset_changed), tool_manager); g_signal_connect (user_context, "image-changed", G_CALLBACK (tool_manager_image_changed), tool_manager); tool_manager_image_changed (user_context, pika_context_get_image (user_context), tool_manager); tool_manager_selected_layers_changed (pika_context_get_image (user_context), tool_manager); tool_manager_tool_changed (user_context, pika_context_get_tool (user_context), tool_manager); } void tool_manager_exit (Pika *pika) { PikaToolManager *tool_manager; PikaContext *user_context; g_return_if_fail (PIKA_IS_PIKA (pika)); tool_manager = tool_manager_get (pika); g_return_if_fail (tool_manager != NULL); user_context = pika_get_user_context (pika); g_signal_handlers_disconnect_by_func (user_context, tool_manager_tool_changed, tool_manager); g_signal_handlers_disconnect_by_func (user_context, tool_manager_preset_changed, tool_manager); g_signal_handlers_disconnect_by_func (user_context, tool_manager_image_changed, tool_manager); pika_container_remove_handler (pika->images, tool_manager->image_clean_handler_id); pika_container_remove_handler (pika->images, tool_manager->image_dirty_handler_id); pika_container_remove_handler (pika->images, tool_manager->image_saving_handler_id); if (tool_manager->active_tool) { g_signal_handlers_disconnect_by_func ( tool_manager->active_tool->tool_info, tool_manager_tool_ancestry_changed, tool_manager); g_clear_object (&tool_manager->active_tool); } tool_manager_set_active_tool_group (tool_manager, NULL); g_slice_free (PikaToolManager, tool_manager); g_object_set_qdata (G_OBJECT (pika), tool_manager_quark, NULL); } PikaTool * tool_manager_get_active (Pika *pika) { PikaToolManager *tool_manager; g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL); tool_manager = tool_manager_get (pika); return tool_manager->active_tool; } void tool_manager_push_tool (Pika *pika, PikaTool *tool) { PikaToolManager *tool_manager; PikaDisplay *focus_display = NULL; g_return_if_fail (PIKA_IS_PIKA (pika)); g_return_if_fail (PIKA_IS_TOOL (tool)); tool_manager = tool_manager_get (pika); if (tool_manager->active_tool) { focus_display = tool_manager->active_tool->focus_display; tool_manager->tool_stack = g_slist_prepend (tool_manager->tool_stack, tool_manager->active_tool); g_object_ref (tool_manager->tool_stack->data); } tool_manager_select_tool (tool_manager, tool); if (focus_display) tool_manager_focus_display_active (pika, focus_display); } void tool_manager_pop_tool (Pika *pika) { PikaToolManager *tool_manager; g_return_if_fail (PIKA_IS_PIKA (pika)); tool_manager = tool_manager_get (pika); if (tool_manager->tool_stack) { PikaTool *tool = tool_manager->tool_stack->data; tool_manager->tool_stack = g_slist_remove (tool_manager->tool_stack, tool); tool_manager_select_tool (tool_manager, tool); g_object_unref (tool); } } gboolean tool_manager_initialize_active (Pika *pika, PikaDisplay *display) { PikaToolManager *tool_manager; g_return_val_if_fail (PIKA_IS_PIKA (pika), FALSE); g_return_val_if_fail (PIKA_IS_DISPLAY (display), FALSE); tool_manager = tool_manager_get (pika); if (tool_manager->active_tool) { PikaTool *tool = tool_manager->active_tool; if (pika_tool_initialize (tool, display)) { PikaImage *image = pika_display_get_image (display); g_list_free (tool->drawables); tool->drawables = pika_image_get_selected_drawables (image); return TRUE; } } return FALSE; } void tool_manager_control_active (Pika *pika, PikaToolAction action, PikaDisplay *display) { PikaToolManager *tool_manager; g_return_if_fail (PIKA_IS_PIKA (pika)); tool_manager = tool_manager_get (pika); if (tool_manager->active_tool) { PikaTool *tool = tool_manager->active_tool; if (display && pika_tool_has_display (tool, display)) { pika_tool_control (tool, action, display); } else if (action == PIKA_TOOL_ACTION_HALT) { if (pika_tool_control_is_active (tool->control)) pika_tool_control_halt (tool->control); } } } void tool_manager_button_press_active (Pika *pika, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonPressType press_type, PikaDisplay *display) { PikaToolManager *tool_manager; g_return_if_fail (PIKA_IS_PIKA (pika)); tool_manager = tool_manager_get (pika); if (tool_manager->active_tool) { pika_tool_button_press (tool_manager->active_tool, coords, time, state, press_type, display); } } void tool_manager_button_release_active (Pika *pika, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaDisplay *display) { PikaToolManager *tool_manager; g_return_if_fail (PIKA_IS_PIKA (pika)); tool_manager = tool_manager_get (pika); if (tool_manager->active_tool) { pika_tool_button_release (tool_manager->active_tool, coords, time, state, display); } } void tool_manager_motion_active (Pika *pika, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaDisplay *display) { PikaToolManager *tool_manager; g_return_if_fail (PIKA_IS_PIKA (pika)); tool_manager = tool_manager_get (pika); if (tool_manager->active_tool) { pika_tool_motion (tool_manager->active_tool, coords, time, state, display); } } gboolean tool_manager_key_press_active (Pika *pika, GdkEventKey *kevent, PikaDisplay *display) { PikaToolManager *tool_manager; g_return_val_if_fail (PIKA_IS_PIKA (pika), FALSE); tool_manager = tool_manager_get (pika); if (tool_manager->active_tool) { return pika_tool_key_press (tool_manager->active_tool, kevent, display); } return FALSE; } gboolean tool_manager_key_release_active (Pika *pika, GdkEventKey *kevent, PikaDisplay *display) { PikaToolManager *tool_manager; g_return_val_if_fail (PIKA_IS_PIKA (pika), FALSE); tool_manager = tool_manager_get (pika); if (tool_manager->active_tool) { return pika_tool_key_release (tool_manager->active_tool, kevent, display); } return FALSE; } void tool_manager_focus_display_active (Pika *pika, PikaDisplay *display) { PikaToolManager *tool_manager; g_return_if_fail (PIKA_IS_PIKA (pika)); tool_manager = tool_manager_get (pika); if (tool_manager->active_tool && ! pika_tool_control_is_active (tool_manager->active_tool->control)) { pika_tool_set_focus_display (tool_manager->active_tool, display); } } void tool_manager_modifier_state_active (Pika *pika, GdkModifierType state, PikaDisplay *display) { PikaToolManager *tool_manager; g_return_if_fail (PIKA_IS_PIKA (pika)); tool_manager = tool_manager_get (pika); if (tool_manager->active_tool && ! pika_tool_control_is_active (tool_manager->active_tool->control)) { pika_tool_set_modifier_state (tool_manager->active_tool, state, display); } } void tool_manager_active_modifier_state_active (Pika *pika, GdkModifierType state, PikaDisplay *display) { PikaToolManager *tool_manager; g_return_if_fail (PIKA_IS_PIKA (pika)); tool_manager = tool_manager_get (pika); if (tool_manager->active_tool) { pika_tool_set_active_modifier_state (tool_manager->active_tool, state, display); } } void tool_manager_oper_update_active (Pika *pika, const PikaCoords *coords, GdkModifierType state, gboolean proximity, PikaDisplay *display) { PikaToolManager *tool_manager; g_return_if_fail (PIKA_IS_PIKA (pika)); tool_manager = tool_manager_get (pika); if (tool_manager->active_tool && ! pika_tool_control_is_active (tool_manager->active_tool->control)) { pika_tool_oper_update (tool_manager->active_tool, coords, state, proximity, display); } } void tool_manager_cursor_update_active (Pika *pika, const PikaCoords *coords, GdkModifierType state, PikaDisplay *display) { PikaToolManager *tool_manager; g_return_if_fail (PIKA_IS_PIKA (pika)); tool_manager = tool_manager_get (pika); if (tool_manager->active_tool && ! pika_tool_control_is_active (tool_manager->active_tool->control)) { pika_tool_cursor_update (tool_manager->active_tool, coords, state, display); } } const gchar * tool_manager_can_undo_active (Pika *pika, PikaDisplay *display) { PikaToolManager *tool_manager; g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL); tool_manager = tool_manager_get (pika); if (tool_manager->active_tool) { return pika_tool_can_undo (tool_manager->active_tool, display); } return NULL; } const gchar * tool_manager_can_redo_active (Pika *pika, PikaDisplay *display) { PikaToolManager *tool_manager; g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL); tool_manager = tool_manager_get (pika); if (tool_manager->active_tool) { return pika_tool_can_redo (tool_manager->active_tool, display); } return NULL; } gboolean tool_manager_undo_active (Pika *pika, PikaDisplay *display) { PikaToolManager *tool_manager; g_return_val_if_fail (PIKA_IS_PIKA (pika), FALSE); tool_manager = tool_manager_get (pika); if (tool_manager->active_tool) { return pika_tool_undo (tool_manager->active_tool, display); } return FALSE; } gboolean tool_manager_redo_active (Pika *pika, PikaDisplay *display) { PikaToolManager *tool_manager; g_return_val_if_fail (PIKA_IS_PIKA (pika), FALSE); tool_manager = tool_manager_get (pika); if (tool_manager->active_tool) { return pika_tool_redo (tool_manager->active_tool, display); } return FALSE; } PikaUIManager * tool_manager_get_popup_active (Pika *pika, const PikaCoords *coords, GdkModifierType state, PikaDisplay *display, const gchar **ui_path) { PikaToolManager *tool_manager; g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL); tool_manager = tool_manager_get (pika); if (tool_manager->active_tool) { return pika_tool_get_popup (tool_manager->active_tool, coords, state, display, ui_path); } return NULL; } /* private functions */ static PikaToolManager * tool_manager_get (Pika *pika) { return g_object_get_qdata (G_OBJECT (pika), tool_manager_quark); } static void tool_manager_select_tool (PikaToolManager *tool_manager, PikaTool *tool) { Pika *pika = tool_manager->pika; /* reset the previously selected tool, but only if it is not only * temporarily pushed to the tool stack */ if (tool_manager->active_tool) { if (! tool_manager->tool_stack || tool_manager->active_tool != tool_manager->tool_stack->data) { PikaTool *active_tool = tool_manager->active_tool; PikaDisplay *display; /* NULL image returns any display (if there is any) */ display = pika_tool_has_image (active_tool, NULL); tool_manager_control_active (pika, PIKA_TOOL_ACTION_HALT, display); tool_manager_focus_display_active (pika, NULL); } } g_set_object (&tool_manager->active_tool, tool); } static void tool_manager_set_active_tool_group (PikaToolManager *tool_manager, PikaToolGroup *tool_group) { if (tool_group != tool_manager->active_tool_group) { if (tool_manager->active_tool_group) { g_signal_handlers_disconnect_by_func ( tool_manager->active_tool_group, tool_manager_group_active_tool_changed, tool_manager); } g_set_weak_pointer (&tool_manager->active_tool_group, tool_group); if (tool_manager->active_tool_group) { g_signal_connect ( tool_manager->active_tool_group, "active-tool-changed", G_CALLBACK (tool_manager_group_active_tool_changed), tool_manager); } } } static void tool_manager_tool_changed (PikaContext *user_context, PikaToolInfo *tool_info, PikaToolManager *tool_manager) { PikaTool *new_tool = NULL; if (! tool_info) return; if (! g_type_is_a (tool_info->tool_type, PIKA_TYPE_TOOL)) { g_warning ("%s: tool_info->tool_type is no PikaTool subclass", G_STRFUNC); return; } /* FIXME: pika_busy HACK */ if (user_context->pika->busy) { /* there may be contexts waiting for the user_context's "tool-changed" * signal, so stop emitting it. */ g_signal_stop_emission_by_name (user_context, "tool-changed"); if (G_TYPE_FROM_INSTANCE (tool_manager->active_tool) != tool_info->tool_type) { g_signal_handlers_block_by_func (user_context, tool_manager_tool_changed, tool_manager); /* explicitly set the current tool */ pika_context_set_tool (user_context, tool_manager->active_tool->tool_info); g_signal_handlers_unblock_by_func (user_context, tool_manager_tool_changed, tool_manager); } return; } g_return_if_fail (tool_manager->tool_stack == NULL); if (tool_manager->active_tool) { tool_manager_active_tool_deactivated (user_context, tool_manager); } g_signal_connect (tool_info, "ancestry-changed", G_CALLBACK (tool_manager_tool_ancestry_changed), tool_manager); tool_manager_tool_ancestry_changed (tool_info, tool_manager); new_tool = g_object_new (tool_info->tool_type, "tool-info", tool_info, NULL); tool_manager_select_tool (tool_manager, new_tool); /* Auto-activate any transform or GEGL operation tools */ if (PIKA_IS_TRANSFORM_GRID_TOOL (new_tool) || PIKA_IS_GEGL_TOOL (new_tool)) { PikaDisplay *new_display; new_display = pika_context_get_display (user_context); if (new_display && pika_display_get_image (new_display)) tool_manager_initialize_active (user_context->pika, new_display); } g_object_unref (new_tool); /* ??? */ tool_manager_cast_spell (tool_info); } static void tool_manager_copy_tool_options (GObject *src, GObject *dest) { GList *diff; diff = pika_config_diff (src, dest, G_PARAM_READWRITE); if (diff) { GList *list; g_object_freeze_notify (dest); for (list = diff; list; list = list->next) { GParamSpec *prop_spec = list->data; if (g_type_is_a (prop_spec->owner_type, PIKA_TYPE_TOOL_OPTIONS) && ! (prop_spec->flags & G_PARAM_CONSTRUCT_ONLY)) { GValue value = G_VALUE_INIT; g_value_init (&value, prop_spec->value_type); g_object_get_property (src, prop_spec->name, &value); g_object_set_property (dest, prop_spec->name, &value); g_value_unset (&value); } } g_object_thaw_notify (dest); g_list_free (diff); } } static void tool_manager_preset_changed (PikaContext *user_context, PikaToolPreset *preset, PikaToolManager *tool_manager) { PikaToolInfo *preset_tool; if (! preset || user_context->pika->busy) return; preset_tool = pika_context_get_tool (PIKA_CONTEXT (preset->tool_options)); /* first, select the preset's tool, even if it's already the active * tool */ pika_context_set_tool (user_context, preset_tool); /* then, copy the context properties the preset remembers, possibly * changing some tool options due to the "link brush stuff to brush * defaults" settings in pikatooloptions.c */ pika_context_copy_properties (PIKA_CONTEXT (preset->tool_options), user_context, pika_tool_preset_get_prop_mask (preset)); /* finally, copy all tool options properties, overwriting any * changes resulting from setting the context properties above, we * really want exactly what is in the preset and nothing else */ tool_manager_copy_tool_options (G_OBJECT (preset->tool_options), G_OBJECT (preset_tool->tool_options)); } static void tool_manager_image_clean_dirty (PikaImage *image, PikaDirtyMask dirty_mask, PikaToolManager *tool_manager) { PikaTool *tool = tool_manager->active_tool; if (tool && ! pika_tool_control_get_preserve (tool->control) && (pika_tool_control_get_dirty_mask (tool->control) & dirty_mask)) { PikaDisplay *display = pika_tool_has_image (tool, image); if (display) { tool_manager_control_active ( image->pika, pika_tool_control_get_dirty_action (tool->control), display); } } } static void tool_manager_image_saving (PikaImage *image, PikaToolManager *tool_manager) { PikaTool *tool = tool_manager->active_tool; if (tool && ! pika_tool_control_get_preserve (tool->control)) { PikaDisplay *display = pika_tool_has_image (tool, image); if (display) tool_manager_control_active (image->pika, PIKA_TOOL_ACTION_COMMIT, display); } } static void tool_manager_tool_ancestry_changed (PikaToolInfo *tool_info, PikaToolManager *tool_manager) { PikaViewable *parent; parent = pika_viewable_get_parent (PIKA_VIEWABLE (tool_info)); if (parent) { pika_tool_group_set_active_tool_info (PIKA_TOOL_GROUP (parent), tool_info); } tool_manager_set_active_tool_group (tool_manager, PIKA_TOOL_GROUP (parent)); } static void tool_manager_group_active_tool_changed (PikaToolGroup *tool_group, PikaToolManager *tool_manager) { pika_context_set_tool (tool_manager->pika->user_context, pika_tool_group_get_active_tool_info (tool_group)); } static void tool_manager_image_changed (PikaContext *context, PikaImage *image, PikaToolManager *tool_manager) { if (tool_manager->image) { g_signal_handlers_disconnect_by_func (tool_manager->image, tool_manager_selected_layers_changed, tool_manager); } tool_manager->image = image; /* Turn off auto-activated tools when switching images */ if (image && (PIKA_IS_TRANSFORM_GRID_TOOL (tool_manager->active_tool) || PIKA_IS_GEGL_TOOL (tool_manager->active_tool))) { pika_context_set_tool (context, tool_manager->active_tool->tool_info); tool_manager_active_tool_deactivated (context, tool_manager); } if (image) g_signal_connect (tool_manager->image, "selected-layers-changed", G_CALLBACK (tool_manager_selected_layers_changed), tool_manager); } static void tool_manager_selected_layers_changed (PikaImage *image, PikaToolManager *tool_manager) { /* Re-activate transform tools when changing selected layers */ if (image && (PIKA_IS_TRANSFORM_GRID_TOOL (tool_manager->active_tool) || PIKA_IS_GEGL_TOOL (tool_manager->active_tool))) { pika_context_set_tool (tool_manager->pika->user_context, tool_manager->active_tool->tool_info); tool_manager_tool_changed (tool_manager->pika->user_context, pika_context_get_tool ( tool_manager->pika->user_context), tool_manager); } } static void tool_manager_active_tool_deactivated (PikaContext *context, PikaToolManager *tool_manager) { PikaDisplay *display; display = pika_tool_has_image (tool_manager->active_tool, NULL); /* NULL image returns any display (if there is any) */ if (display) tool_manager_control_active (context->pika, PIKA_TOOL_ACTION_COMMIT, display); /* commit the old tool's operation before creating the new tool * because creating a tool might mess with the old tool's * options (old and new tool might be the same) */ g_signal_handlers_disconnect_by_func (tool_manager->active_tool->tool_info, tool_manager_tool_ancestry_changed, tool_manager); } static void tool_manager_cast_spell (PikaToolInfo *tool_info) { typedef struct { const gchar *sequence; GCallback func; } Spell; static const Spell spells[] = { { .sequence = "pika-warp-tool\0" "pika-iscissors-tool\0" "pika-gradient-tool\0" "pika-vector-tool\0" "pika-ellipse-select-tool\0" "pika-rect-select-tool\0", .func = pika_cairo_mascot_toggle_pointer_eyes } }; static const gchar *spell_progress[G_N_ELEMENTS (spells)]; const gchar *tool_name; gint i; tool_name = pika_object_get_name (PIKA_OBJECT (tool_info)); for (i = 0; i < G_N_ELEMENTS (spells); i++) { if (! spell_progress[i]) spell_progress[i] = spells[i].sequence; while (spell_progress[i]) { if (! strcmp (tool_name, spell_progress[i])) { spell_progress[i] += strlen (spell_progress[i]) + 1; if (! *spell_progress[i]) { spell_progress[i] = NULL; spells[i].func (); } break; } else { if (spell_progress[i] == spells[i].sequence) spell_progress[i] = NULL; else spell_progress[i] = spells[i].sequence; } } } }