/* 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 "libpikawidgets/pikawidgets.h" #include "tools-types.h" #include "config/pikaguiconfig.h" #include "core/pika.h" #include "core/pikaasync.h" #include "core/pikacancelable.h" #include "core/pikacontainer.h" #include "core/pikadrawable-bucket-fill.h" #include "core/pikadrawable-edit.h" #include "core/pikadrawablefilter.h" #include "core/pikaerror.h" #include "core/pikafilloptions.h" #include "core/pikaimage.h" #include "core/pikaimageproxy.h" #include "core/pikaitem.h" #include "core/pikalineart.h" #include "core/pikapickable.h" #include "core/pikapickable-contiguous-region.h" #include "core/pikaprogress.h" #include "core/pikaprojection.h" #include "core/pikatoolinfo.h" #include "core/pikawaitable.h" #include "gegl/pika-gegl-nodes.h" #include "operations/layer-modes/pika-layer-modes.h" #include "widgets/pikahelp-ids.h" #include "widgets/pikawidgets-utils.h" #include "display/pikadisplay.h" #include "display/pikadisplayshell.h" #include "pikabucketfilloptions.h" #include "pikabucketfilltool.h" #include "pikacoloroptions.h" #include "pikatoolcontrol.h" #include "pikatools-utils.h" #include "pika-intl.h" struct _PikaBucketFillToolPrivate { PikaLineArt *line_art; PikaImage *line_art_image; PikaDisplayShell *line_art_shell; GList *line_art_bindings; /* For preview */ GeglNode *graph; GeglNode *fill_node; GeglNode *offset_node; GeglBuffer *fill_mask; PikaDrawableFilter *filter; /* Temp property save */ PikaBucketFillMode fill_mode; PikaBucketFillArea fill_area; }; /* local function prototypes */ static void pika_bucket_fill_tool_constructed (GObject *object); static void pika_bucket_fill_tool_finalize (GObject *object); static void pika_bucket_fill_tool_button_press (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonPressType press_type, PikaDisplay *display); static void pika_bucket_fill_tool_motion (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaDisplay *display); static void pika_bucket_fill_tool_button_release (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonReleaseType release_type, PikaDisplay *display); static void pika_bucket_fill_tool_modifier_key (PikaTool *tool, GdkModifierType key, gboolean press, GdkModifierType state, PikaDisplay *display); static void pika_bucket_fill_tool_cursor_update (PikaTool *tool, const PikaCoords *coords, GdkModifierType state, PikaDisplay *display); static void pika_bucket_fill_tool_options_notify (PikaTool *tool, PikaToolOptions *options, const GParamSpec *pspec); static void pika_bucket_fill_tool_line_art_computing_start (PikaBucketFillTool *tool); static void pika_bucket_fill_tool_line_art_computing_end (PikaBucketFillTool *tool); static gboolean pika_bucket_fill_tool_coords_in_active_pickable (PikaBucketFillTool *tool, PikaDisplay *display, const PikaCoords *coords); static void pika_bucket_fill_tool_start (PikaBucketFillTool *tool, const PikaCoords *coords, PikaDisplay *display); static void pika_bucket_fill_tool_preview (PikaBucketFillTool *tool, const PikaCoords *coords, PikaDisplay *display, PikaFillOptions *fill_options); static void pika_bucket_fill_tool_commit (PikaBucketFillTool *tool); static void pika_bucket_fill_tool_halt (PikaBucketFillTool *tool); static void pika_bucket_fill_tool_filter_flush (PikaDrawableFilter *filter, PikaTool *tool); static void pika_bucket_fill_tool_create_graph (PikaBucketFillTool *tool); static void pika_bucket_fill_tool_reset_line_art (PikaBucketFillTool *tool); G_DEFINE_TYPE_WITH_PRIVATE (PikaBucketFillTool, pika_bucket_fill_tool, PIKA_TYPE_COLOR_TOOL) #define parent_class pika_bucket_fill_tool_parent_class void pika_bucket_fill_tool_register (PikaToolRegisterCallback callback, gpointer data) { (* callback) (PIKA_TYPE_BUCKET_FILL_TOOL, PIKA_TYPE_BUCKET_FILL_OPTIONS, pika_bucket_fill_options_gui, PIKA_CONTEXT_PROP_MASK_FOREGROUND | PIKA_CONTEXT_PROP_MASK_BACKGROUND | PIKA_CONTEXT_PROP_MASK_OPACITY | PIKA_CONTEXT_PROP_MASK_PAINT_MODE | PIKA_CONTEXT_PROP_MASK_PATTERN, "pika-bucket-fill-tool", _("Bucket Fill"), _("Bucket Fill Tool: Fill selected area with a color or pattern"), N_("_Bucket Fill"), "B", NULL, PIKA_HELP_TOOL_BUCKET_FILL, PIKA_ICON_TOOL_BUCKET_FILL, data); } static void pika_bucket_fill_tool_class_init (PikaBucketFillToolClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); PikaToolClass *tool_class = PIKA_TOOL_CLASS (klass); object_class->constructed = pika_bucket_fill_tool_constructed; object_class->finalize = pika_bucket_fill_tool_finalize; tool_class->button_press = pika_bucket_fill_tool_button_press; tool_class->motion = pika_bucket_fill_tool_motion; tool_class->button_release = pika_bucket_fill_tool_button_release; tool_class->modifier_key = pika_bucket_fill_tool_modifier_key; tool_class->cursor_update = pika_bucket_fill_tool_cursor_update; tool_class->options_notify = pika_bucket_fill_tool_options_notify; } static void pika_bucket_fill_tool_init (PikaBucketFillTool *bucket_fill_tool) { PikaTool *tool = PIKA_TOOL (bucket_fill_tool); pika_tool_control_set_scroll_lock (tool->control, TRUE); pika_tool_control_set_wants_click (tool->control, TRUE); pika_tool_control_set_tool_cursor (tool->control, PIKA_TOOL_CURSOR_BUCKET_FILL); pika_tool_control_set_action_opacity (tool->control, "context-opacity-set"); pika_tool_control_set_action_object_1 (tool->control, "context-pattern-select-set"); bucket_fill_tool->priv = pika_bucket_fill_tool_get_instance_private (bucket_fill_tool); } static void pika_bucket_fill_tool_constructed (GObject *object) { PikaTool *tool = PIKA_TOOL (object); PikaBucketFillTool *bucket_tool = PIKA_BUCKET_FILL_TOOL (object); PikaBucketFillOptions *options = PIKA_BUCKET_FILL_TOOL_GET_OPTIONS (tool); Pika *pika = PIKA_CONTEXT (options)->pika; PikaContext *context = pika_get_user_context (pika); GList *bindings = NULL; GBinding *binding; PikaLineArt *line_art; G_OBJECT_CLASS (parent_class)->constructed (object); pika_tool_control_set_motion_mode (tool->control, options->fill_area == PIKA_BUCKET_FILL_LINE_ART ? PIKA_MOTION_MODE_EXACT : PIKA_MOTION_MODE_COMPRESS); line_art = pika_context_take_line_art (context); binding = g_object_bind_property (options, "fill-transparent", line_art, "select-transparent", G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL); bindings = g_list_prepend (bindings, binding); binding = g_object_bind_property (options, "line-art-threshold", line_art, "threshold", G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL); bindings = g_list_prepend (bindings, binding); binding = g_object_bind_property (options, "line-art-max-grow", line_art, "max-grow", G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL); bindings = g_list_prepend (bindings, binding); binding = g_object_bind_property (options, "line-art-automatic-closure", line_art, "automatic-closure", G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL); bindings = g_list_prepend (bindings, binding); binding = g_object_bind_property (options, "line-art-max-gap-length", line_art, "spline-max-length", G_BINDING_SYNC_CREATE | G_BINDING_DEFAULT); bindings = g_list_prepend (bindings, binding); binding = g_object_bind_property (options, "line-art-max-gap-length", line_art, "segment-max-length", G_BINDING_SYNC_CREATE | G_BINDING_DEFAULT); bindings = g_list_prepend (bindings, binding); g_signal_connect_swapped (line_art, "computing-start", G_CALLBACK (pika_bucket_fill_tool_line_art_computing_start), tool); g_signal_connect_swapped (line_art, "computing-end", G_CALLBACK (pika_bucket_fill_tool_line_art_computing_end), tool); pika_line_art_bind_gap_length (line_art, TRUE); bucket_tool->priv->line_art = line_art; bucket_tool->priv->line_art_bindings = bindings; pika_bucket_fill_tool_reset_line_art (bucket_tool); g_signal_connect_swapped (options, "notify::line-art-source", G_CALLBACK (pika_bucket_fill_tool_reset_line_art), tool); g_signal_connect_swapped (context, "display-changed", G_CALLBACK (pika_bucket_fill_tool_reset_line_art), tool); PIKA_COLOR_TOOL (tool)->pick_target = (options->fill_mode == PIKA_BUCKET_FILL_BG) ? PIKA_COLOR_PICK_TARGET_BACKGROUND : PIKA_COLOR_PICK_TARGET_FOREGROUND; } static void pika_bucket_fill_tool_finalize (GObject *object) { PikaBucketFillTool *tool = PIKA_BUCKET_FILL_TOOL (object); PikaBucketFillOptions *options = PIKA_BUCKET_FILL_TOOL_GET_OPTIONS (tool); Pika *pika = PIKA_CONTEXT (options)->pika; PikaContext *context = pika_get_user_context (pika); if (tool->priv->line_art_image) { g_signal_handlers_disconnect_by_data (pika_image_get_layers (tool->priv->line_art_image), tool); g_signal_handlers_disconnect_by_data (tool->priv->line_art_image, tool); tool->priv->line_art_image = NULL; } if (tool->priv->line_art_shell) { g_signal_handlers_disconnect_by_func ( tool->priv->line_art_shell, pika_bucket_fill_tool_reset_line_art, tool); tool->priv->line_art_shell = NULL; } /* We don't free the line art object, but gives temporary ownership to * the user context which will free it if a timer runs out. * * This way, we allow people to not suffer a new computational delay * if for instance they just needed to switch tools for a few seconds * while the source layer stays the same. */ g_signal_handlers_disconnect_by_data (tool->priv->line_art, tool); g_list_free_full (tool->priv->line_art_bindings, (GDestroyNotify) g_binding_unbind); pika_context_store_line_art (context, tool->priv->line_art); tool->priv->line_art = NULL; g_signal_handlers_disconnect_by_data (options, tool); g_signal_handlers_disconnect_by_data (context, tool); G_OBJECT_CLASS (parent_class)->finalize (object); } static gboolean pika_bucket_fill_tool_coords_in_active_pickable (PikaBucketFillTool *tool, PikaDisplay *display, const PikaCoords *coords) { PikaBucketFillOptions *options = PIKA_BUCKET_FILL_TOOL_GET_OPTIONS (tool); PikaDisplayShell *shell = pika_display_get_shell (display); PikaImage *image = pika_display_get_image (display); gboolean sample_merged = FALSE; switch (options->fill_area) { case PIKA_BUCKET_FILL_SELECTION: break; case PIKA_BUCKET_FILL_SIMILAR_COLORS: sample_merged = options->sample_merged; break; case PIKA_BUCKET_FILL_LINE_ART: sample_merged = options->line_art_source == PIKA_LINE_ART_SOURCE_SAMPLE_MERGED; break; } return pika_image_coords_in_active_pickable (image, coords, shell->show_all, sample_merged, TRUE); } static void pika_bucket_fill_tool_start (PikaBucketFillTool *tool, const PikaCoords *coords, PikaDisplay *display) { PikaBucketFillOptions *options = PIKA_BUCKET_FILL_TOOL_GET_OPTIONS (tool); PikaContext *context = PIKA_CONTEXT (options); PikaImage *image = pika_display_get_image (display); GList *drawables = pika_image_get_selected_drawables (image); g_return_if_fail (! tool->priv->filter); g_return_if_fail (g_list_length (drawables) == 1); pika_line_art_freeze (tool->priv->line_art); PIKA_TOOL (tool)->display = display; g_list_free (PIKA_TOOL (tool)->drawables); PIKA_TOOL (tool)->drawables = drawables; pika_bucket_fill_tool_create_graph (tool); tool->priv->filter = pika_drawable_filter_new (drawables->data, _("Bucket fill"), tool->priv->graph, PIKA_ICON_TOOL_BUCKET_FILL); pika_drawable_filter_set_region (tool->priv->filter, PIKA_FILTER_REGION_DRAWABLE); /* We only set these here, and don't need to update it since we assume * the settings can't change while the fill started. */ pika_drawable_filter_set_mode (tool->priv->filter, pika_context_get_paint_mode (context), PIKA_LAYER_COLOR_SPACE_AUTO, PIKA_LAYER_COLOR_SPACE_AUTO, pika_layer_mode_get_paint_composite_mode (pika_context_get_paint_mode (context))); pika_drawable_filter_set_opacity (tool->priv->filter, pika_context_get_opacity (context)); g_signal_connect (tool->priv->filter, "flush", G_CALLBACK (pika_bucket_fill_tool_filter_flush), tool); } static void pika_bucket_fill_tool_preview (PikaBucketFillTool *tool, const PikaCoords *coords, PikaDisplay *display, PikaFillOptions *fill_options) { PikaBucketFillOptions *options = PIKA_BUCKET_FILL_TOOL_GET_OPTIONS (tool); PikaDisplayShell *shell = pika_display_get_shell (display); PikaImage *image = pika_display_get_image (display); GList *drawables = pika_image_get_selected_drawables (image); PikaDrawable *drawable; g_return_if_fail (g_list_length (drawables) == 1); drawable = drawables->data; g_list_free (drawables); if (tool->priv->filter) { GeglBuffer *fill = NULL; gdouble x = coords->x; gdouble y = coords->y; if (options->fill_area == PIKA_BUCKET_FILL_SIMILAR_COLORS) { if (! options->sample_merged) { gint off_x, off_y; pika_item_get_offset (PIKA_ITEM (drawable), &off_x, &off_y); x -= (gdouble) off_x; y -= (gdouble) off_y; } fill = pika_drawable_get_bucket_fill_buffer (drawable, fill_options, options->fill_transparent, options->fill_criterion, options->threshold / 255.0, shell->show_all, options->sample_merged, options->diagonal_neighbors, x, y, &tool->priv->fill_mask, &x, &y, NULL, NULL); } else { gint source_off_x = 0; gint source_off_y = 0; if (options->line_art_source != PIKA_LINE_ART_SOURCE_SAMPLE_MERGED) { PikaPickable *input; input = pika_line_art_get_input (tool->priv->line_art); g_return_if_fail (PIKA_IS_ITEM (input)); pika_item_get_offset (PIKA_ITEM (input), &source_off_x, &source_off_y); x -= (gdouble) source_off_x; y -= (gdouble) source_off_y; } fill = pika_drawable_get_line_art_fill_buffer (drawable, tool->priv->line_art, fill_options, options->line_art_source == PIKA_LINE_ART_SOURCE_SAMPLE_MERGED, options->fill_as_line_art && options->fill_mode != PIKA_BUCKET_FILL_PATTERN, options->fill_as_line_art_threshold / 255.0, options->line_art_stroke, options->stroke_options, x, y, &tool->priv->fill_mask, &x, &y, NULL, NULL); if (options->line_art_source != PIKA_LINE_ART_SOURCE_SAMPLE_MERGED) { gint off_x, off_y; pika_item_get_offset (PIKA_ITEM (drawable), &off_x, &off_y); x -= (gdouble) off_x - (gdouble) source_off_x; y -= (gdouble) off_y - (gdouble) source_off_y; } } if (fill) { gegl_node_set (tool->priv->fill_node, "buffer", fill, NULL); gegl_node_set (tool->priv->offset_node, "x", x, "y", y, NULL); pika_drawable_filter_apply (tool->priv->filter, NULL); g_object_unref (fill); } } } static void pika_bucket_fill_tool_commit (PikaBucketFillTool *tool) { if (tool->priv->filter) { pika_drawable_filter_commit (tool->priv->filter, PIKA_PROGRESS (tool), FALSE); pika_image_flush (pika_display_get_image (PIKA_TOOL (tool)->display)); } } static void pika_bucket_fill_tool_halt (PikaBucketFillTool *tool) { if (tool->priv->graph) { g_clear_object (&tool->priv->graph); tool->priv->fill_node = NULL; tool->priv->offset_node = NULL; } if (tool->priv->filter) { pika_drawable_filter_abort (tool->priv->filter); g_clear_object (&tool->priv->filter); } g_clear_object (&tool->priv->fill_mask); if (pika_line_art_is_frozen (tool->priv->line_art)) pika_line_art_thaw (tool->priv->line_art); PIKA_TOOL (tool)->display = NULL; g_list_free (PIKA_TOOL (tool)->drawables); PIKA_TOOL (tool)->drawables = NULL; } static void pika_bucket_fill_tool_filter_flush (PikaDrawableFilter *filter, PikaTool *tool) { PikaImage *image = pika_display_get_image (tool->display); pika_projection_flush (pika_image_get_projection (image)); } static void pika_bucket_fill_tool_create_graph (PikaBucketFillTool *tool) { GeglNode *graph; GeglNode *output; GeglNode *fill_node; GeglNode *offset_node; g_return_if_fail (! tool->priv->graph && ! tool->priv->fill_node && ! tool->priv->offset_node); graph = gegl_node_new (); fill_node = gegl_node_new_child (graph, "operation", "gegl:buffer-source", NULL); offset_node = gegl_node_new_child (graph, "operation", "gegl:translate", NULL); output = gegl_node_get_output_proxy (graph, "output"); gegl_node_link_many (fill_node, offset_node, output, NULL); tool->priv->graph = graph; tool->priv->fill_node = fill_node; tool->priv->offset_node = offset_node; } static void pika_bucket_fill_tool_button_press (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonPressType press_type, PikaDisplay *display) { PikaBucketFillTool *bucket_tool = PIKA_BUCKET_FILL_TOOL (tool); PikaBucketFillOptions *options = PIKA_BUCKET_FILL_TOOL_GET_OPTIONS (tool); PikaGuiConfig *config = PIKA_GUI_CONFIG (display->pika->config); PikaImage *image = pika_display_get_image (display); PikaItem *locked_item = NULL; GList *drawables = pika_image_get_selected_drawables (image); PikaDrawable *drawable; if (pika_color_tool_is_enabled (PIKA_COLOR_TOOL (tool))) { PIKA_TOOL_CLASS (parent_class)->button_press (tool, coords, time, state, press_type, display); return; } if (g_list_length (drawables) != 1) { if (g_list_length (drawables) > 1) pika_tool_message_literal (tool, display, _("Cannot fill multiple layers. Select only one layer.")); else pika_tool_message_literal (tool, display, _("No selected drawables.")); g_list_free (drawables); return; } drawable = drawables->data; g_list_free (drawables); if (pika_viewable_get_children (PIKA_VIEWABLE (drawable))) { pika_tool_message_literal (tool, display, _("Cannot modify the pixels of layer groups.")); return; } if (! pika_item_is_visible (PIKA_ITEM (drawable)) && ! config->edit_non_visible) { pika_tool_message_literal (tool, display, _("The active layer is not visible.")); return; } if (pika_item_is_content_locked (PIKA_ITEM (drawable), &locked_item)) { pika_tool_message_literal (tool, display, _("The selected layer's pixels are locked.")); pika_tools_blink_lock_box (display->pika, locked_item); return; } if (options->fill_area == PIKA_BUCKET_FILL_LINE_ART && ! pika_line_art_get_input (bucket_tool->priv->line_art)) { pika_tool_message_literal (tool, display, _("No valid line art source selected.")); pika_blink_dockable (display->pika, "pika-tool-options", "line-art-source", NULL, NULL); return; } if (press_type == PIKA_BUTTON_PRESS_NORMAL && pika_bucket_fill_tool_coords_in_active_pickable (bucket_tool, display, coords)) { PikaContext *context = PIKA_CONTEXT (options); PikaFillOptions *fill_options; GError *error = NULL; fill_options = pika_fill_options_new (image->pika, NULL, FALSE); if (pika_fill_options_set_by_fill_mode (fill_options, context, options->fill_mode, &error)) { pika_fill_options_set_antialias (fill_options, options->antialias); pika_fill_options_set_feather (fill_options, options->feather, options->feather_radius); pika_context_set_opacity (PIKA_CONTEXT (fill_options), pika_context_get_opacity (context)); pika_context_set_paint_mode (PIKA_CONTEXT (fill_options), pika_context_get_paint_mode (context)); if (options->fill_area == PIKA_BUCKET_FILL_SELECTION) { pika_drawable_edit_fill (drawable, fill_options, NULL); pika_image_flush (image); } else /* PIKA_BUCKET_FILL_SIMILAR_COLORS || PIKA_BUCKET_FILL_LINE_ART */ { pika_bucket_fill_tool_start (bucket_tool, coords, display); pika_bucket_fill_tool_preview (bucket_tool, coords, display, fill_options); } } else { pika_message_literal (display->pika, G_OBJECT (display), PIKA_MESSAGE_WARNING, error->message); g_clear_error (&error); } g_object_unref (fill_options); } PIKA_TOOL_CLASS (parent_class)->button_press (tool, coords, time, state, press_type, display); } static void pika_bucket_fill_tool_motion (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaDisplay *display) { PikaBucketFillTool *bucket_tool = PIKA_BUCKET_FILL_TOOL (tool); PikaBucketFillOptions *options = PIKA_BUCKET_FILL_TOOL_GET_OPTIONS (tool); PikaImage *image = pika_display_get_image (display); PIKA_TOOL_CLASS (parent_class)->motion (tool, coords, time, state, display); if (pika_color_tool_is_enabled (PIKA_COLOR_TOOL (tool))) return; if (pika_bucket_fill_tool_coords_in_active_pickable (bucket_tool, display, coords) && /* Fill selection only needs to happen once. */ options->fill_area != PIKA_BUCKET_FILL_SELECTION) { PikaContext *context = PIKA_CONTEXT (options); PikaFillOptions *fill_options; GError *error = NULL; fill_options = pika_fill_options_new (image->pika, NULL, FALSE); if (pika_fill_options_set_by_fill_mode (fill_options, context, options->fill_mode, &error)) { pika_fill_options_set_antialias (fill_options, options->antialias); pika_fill_options_set_feather (fill_options, options->feather, options->feather_radius); pika_context_set_opacity (PIKA_CONTEXT (fill_options), pika_context_get_opacity (context)); pika_context_set_paint_mode (PIKA_CONTEXT (fill_options), pika_context_get_paint_mode (context)); pika_bucket_fill_tool_preview (bucket_tool, coords, display, fill_options); } else { pika_message_literal (display->pika, G_OBJECT (display), PIKA_MESSAGE_WARNING, error->message); g_clear_error (&error); } g_object_unref (fill_options); } } static void pika_bucket_fill_tool_button_release (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonReleaseType release_type, PikaDisplay *display) { PikaBucketFillTool *bucket_tool = PIKA_BUCKET_FILL_TOOL (tool); PikaBucketFillOptions *options = PIKA_BUCKET_FILL_TOOL_GET_OPTIONS (tool); if (pika_color_tool_is_enabled (PIKA_COLOR_TOOL (tool))) { PIKA_TOOL_CLASS (parent_class)->button_release (tool, coords, time, state, release_type, display); return; } if (release_type != PIKA_BUTTON_RELEASE_CANCEL) pika_bucket_fill_tool_commit (bucket_tool); if (options->fill_area != PIKA_BUCKET_FILL_SELECTION) pika_bucket_fill_tool_halt (bucket_tool); PIKA_TOOL_CLASS (parent_class)->button_release (tool, coords, time, state, release_type, display); } static void pika_bucket_fill_tool_modifier_key (PikaTool *tool, GdkModifierType key, gboolean press, GdkModifierType state, PikaDisplay *display) { PikaBucketFillOptions *options = PIKA_BUCKET_FILL_TOOL_GET_OPTIONS (tool); if (key == GDK_MOD1_MASK) { if (press) { PIKA_BUCKET_FILL_TOOL (tool)->priv->fill_mode = options->fill_mode; switch (options->fill_mode) { case PIKA_BUCKET_FILL_FG: g_object_set (options, "fill-mode", PIKA_BUCKET_FILL_BG, NULL); break; default: /* PIKA_BUCKET_FILL_BG || PIKA_BUCKET_FILL_PATTERN */ g_object_set (options, "fill-mode", PIKA_BUCKET_FILL_FG, NULL); break; break; } } else /* release */ { g_object_set (options, "fill-mode", PIKA_BUCKET_FILL_TOOL (tool)->priv->fill_mode, NULL); } } else if (key == pika_get_toggle_behavior_mask ()) { PikaToolInfo *info = pika_get_tool_info (display->pika, "pika-color-picker-tool"); if (! pika_color_tool_is_enabled (PIKA_COLOR_TOOL (tool))) { switch (PIKA_COLOR_TOOL (tool)->pick_target) { case PIKA_COLOR_PICK_TARGET_BACKGROUND: pika_tool_push_status (tool, display, _("Click in any image to pick the " "background color")); break; case PIKA_COLOR_PICK_TARGET_FOREGROUND: default: pika_tool_push_status (tool, display, _("Click in any image to pick the " "foreground color")); break; } PIKA_TOOL (tool)->display = display; pika_color_tool_enable (PIKA_COLOR_TOOL (tool), PIKA_COLOR_OPTIONS (info->tool_options)); } else { pika_tool_pop_status (tool, display); pika_color_tool_disable (PIKA_COLOR_TOOL (tool)); PIKA_TOOL (tool)->display = NULL; } } else if (key == pika_get_extend_selection_mask ()) { if (press) { PIKA_BUCKET_FILL_TOOL (tool)->priv->fill_area = options->fill_area; switch (options->fill_area) { case PIKA_BUCKET_FILL_SIMILAR_COLORS: g_object_set (options, "fill-area", PIKA_BUCKET_FILL_SELECTION, NULL); break; default: /* PIKA_BUCKET_FILL_SELECTION || PIKA_BUCKET_FILL_LINE_ART */ g_object_set (options, "fill-area", PIKA_BUCKET_FILL_SIMILAR_COLORS, NULL); break; } } else /* release */ { g_object_set (options, "fill-area", PIKA_BUCKET_FILL_TOOL (tool)->priv->fill_area, NULL); } } } static void pika_bucket_fill_tool_cursor_update (PikaTool *tool, const PikaCoords *coords, GdkModifierType state, PikaDisplay *display) { PikaBucketFillTool *bucket_tool = PIKA_BUCKET_FILL_TOOL (tool); PikaBucketFillOptions *options = PIKA_BUCKET_FILL_TOOL_GET_OPTIONS (tool); PikaGuiConfig *config = PIKA_GUI_CONFIG (display->pika->config); PikaCursorModifier modifier = PIKA_CURSOR_MODIFIER_BAD; PikaImage *image = pika_display_get_image (display); if (pika_bucket_fill_tool_coords_in_active_pickable (bucket_tool, display, coords)) { GList *drawables = pika_image_get_selected_drawables (image); PikaDrawable *drawable = NULL; if (g_list_length (drawables) == 1) drawable = drawables->data; if (drawable && ! pika_viewable_get_children (PIKA_VIEWABLE (drawable)) && ! pika_item_is_content_locked (PIKA_ITEM (drawable), NULL) && (pika_item_is_visible (PIKA_ITEM (drawable)) || config->edit_non_visible)) { switch (options->fill_mode) { case PIKA_BUCKET_FILL_FG: modifier = PIKA_CURSOR_MODIFIER_FOREGROUND; break; case PIKA_BUCKET_FILL_BG: modifier = PIKA_CURSOR_MODIFIER_BACKGROUND; break; case PIKA_BUCKET_FILL_PATTERN: modifier = PIKA_CURSOR_MODIFIER_PATTERN; break; } } g_list_free (drawables); } pika_tool_control_set_cursor_modifier (tool->control, modifier); PIKA_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display); } static void pika_bucket_fill_tool_options_notify (PikaTool *tool, PikaToolOptions *options, const GParamSpec *pspec) { PikaBucketFillTool *bucket_tool = PIKA_BUCKET_FILL_TOOL (tool); PikaBucketFillOptions *bucket_options = PIKA_BUCKET_FILL_OPTIONS (options); PIKA_TOOL_CLASS (parent_class)->options_notify (tool, options, pspec); if (! strcmp (pspec->name, "fill-area")) { /* We want more motion events when the tool is used in a paint tool * fashion. Unfortunately we only set exact mode in line art fill, * because we can't as easily remove events from the similar color * mode just because a point has already been selected (unless * threshold were 0, but that's an edge case). */ pika_tool_control_set_motion_mode (tool->control, bucket_options->fill_area == PIKA_BUCKET_FILL_LINE_ART ? PIKA_MOTION_MODE_EXACT : PIKA_MOTION_MODE_COMPRESS); pika_bucket_fill_tool_reset_line_art (bucket_tool); } else if (! strcmp (pspec->name, "fill-mode")) { if (pika_color_tool_is_enabled (PIKA_COLOR_TOOL (tool))) pika_tool_pop_status (tool, tool->display); switch (bucket_options->fill_mode) { case PIKA_BUCKET_FILL_BG: PIKA_COLOR_TOOL (tool)->pick_target = PIKA_COLOR_PICK_TARGET_BACKGROUND; if (pika_color_tool_is_enabled (PIKA_COLOR_TOOL (tool))) pika_tool_push_status (tool, tool->display, _("Click in any image to pick the " "background color")); break; case PIKA_BUCKET_FILL_FG: default: PIKA_COLOR_TOOL (tool)->pick_target = PIKA_COLOR_PICK_TARGET_FOREGROUND; if (pika_color_tool_is_enabled (PIKA_COLOR_TOOL (tool))) pika_tool_push_status (tool, tool->display, _("Click in any image to pick the " "foreground color")); break; } } } static void pika_bucket_fill_tool_line_art_computing_start (PikaBucketFillTool *tool) { PikaBucketFillOptions *options = PIKA_BUCKET_FILL_TOOL_GET_OPTIONS (tool); gtk_widget_show (options->line_art_busy_box); } static void pika_bucket_fill_tool_line_art_computing_end (PikaBucketFillTool *tool) { PikaBucketFillOptions *options = PIKA_BUCKET_FILL_TOOL_GET_OPTIONS (tool); gtk_widget_hide (options->line_art_busy_box); } static void pika_bucket_fill_tool_reset_line_art (PikaBucketFillTool *tool) { PikaBucketFillOptions *options = PIKA_BUCKET_FILL_TOOL_GET_OPTIONS (tool); PikaLineArt *line_art = tool->priv->line_art; PikaDisplayShell *shell = NULL; PikaImage *image = NULL; if (options->fill_area == PIKA_BUCKET_FILL_LINE_ART) { PikaContext *context; PikaDisplay *display; context = pika_get_user_context (PIKA_CONTEXT (options)->pika); display = pika_context_get_display (context); if (display) { shell = pika_display_get_shell (display); image = pika_display_get_image (display); } } if (image != tool->priv->line_art_image) { if (tool->priv->line_art_image) { g_signal_handlers_disconnect_by_data (pika_image_get_layers (tool->priv->line_art_image), tool); g_signal_handlers_disconnect_by_data (tool->priv->line_art_image, tool); } tool->priv->line_art_image = image; if (image) { g_signal_connect_swapped (image, "selected-layers-changed", G_CALLBACK (pika_bucket_fill_tool_reset_line_art), tool); g_signal_connect_swapped (image, "selected-channels-changed", G_CALLBACK (pika_bucket_fill_tool_reset_line_art), tool); g_signal_connect_swapped (pika_image_get_layers (image), "add", G_CALLBACK (pika_bucket_fill_tool_reset_line_art), tool); g_signal_connect_swapped (pika_image_get_layers (image), "remove", G_CALLBACK (pika_bucket_fill_tool_reset_line_art), tool); g_signal_connect_swapped (pika_image_get_layers (image), "reorder", G_CALLBACK (pika_bucket_fill_tool_reset_line_art), tool); } } if (shell != tool->priv->line_art_shell) { if (tool->priv->line_art_shell) { g_signal_handlers_disconnect_by_func ( tool->priv->line_art_shell, pika_bucket_fill_tool_reset_line_art, tool); } tool->priv->line_art_shell = shell; if (shell) { g_signal_connect_swapped (shell, "notify::show-all", G_CALLBACK (pika_bucket_fill_tool_reset_line_art), tool); } } if (image) { GList *drawables = pika_image_get_selected_drawables (image); PikaDrawable *drawable = NULL; if (g_list_length (drawables) == 1) { drawable = drawables->data; if (pika_viewable_get_children (PIKA_VIEWABLE (drawable))) drawable = NULL; } g_list_free (drawables); if (options->line_art_source == PIKA_LINE_ART_SOURCE_SAMPLE_MERGED) { PikaImageProxy *image_proxy = pika_image_proxy_new (image); pika_image_proxy_set_show_all (image_proxy, shell->show_all); pika_line_art_set_input (line_art, PIKA_PICKABLE (image_proxy)); g_object_unref (image_proxy); } else if (drawable) { PikaItem *parent; PikaContainer *container; PikaObject *neighbour = NULL; PikaPickable *source = NULL; gint index; parent = pika_item_get_parent (PIKA_ITEM (drawable)); if (parent) container = pika_viewable_get_children (PIKA_VIEWABLE (parent)); else container = pika_image_get_layers (image); index = pika_item_get_index (PIKA_ITEM (drawable)); if (options->line_art_source == PIKA_LINE_ART_SOURCE_ACTIVE_LAYER) source = PIKA_PICKABLE (drawable); else if (options->line_art_source == PIKA_LINE_ART_SOURCE_LOWER_LAYER) neighbour = pika_container_get_child_by_index (container, index + 1); else if (options->line_art_source == PIKA_LINE_ART_SOURCE_UPPER_LAYER) neighbour = pika_container_get_child_by_index (container, index - 1); source = neighbour ? PIKA_PICKABLE (neighbour) : source; pika_line_art_set_input (line_art, source); } else { pika_line_art_set_input (line_art, NULL); } } else { pika_line_art_set_input (line_art, NULL); } }