/* 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 "libpikabase/pikabase.h" #include "libpikamath/pikamath.h" #include "tools-types.h" #include "config/pikadisplayconfig.h" #include "config/pikaguiconfig.h" #include "core/pika.h" #include "core/pika-utils.h" #include "core/pikadrawable.h" #include "core/pikaerror.h" #include "core/pikaimage.h" #include "core/pikalayer.h" #include "core/pikapaintinfo.h" #include "core/pikaprojection.h" #include "core/pikatoolinfo.h" #include "paint/pikapaintcore.h" #include "paint/pikapaintoptions.h" #include "widgets/pikadevices.h" #include "widgets/pikawidgets-utils.h" #include "display/pikadisplay.h" #include "display/pikadisplayshell.h" #include "display/pikadisplayshell-selection.h" #include "display/pikadisplayshell-utils.h" #include "pikacoloroptions.h" #include "pikapaintoptions-gui.h" #include "pikapainttool.h" #include "pikapainttool-paint.h" #include "pikatoolcontrol.h" #include "pikatools-utils.h" #include "pika-intl.h" static void pika_paint_tool_constructed (GObject *object); static void pika_paint_tool_finalize (GObject *object); static void pika_paint_tool_control (PikaTool *tool, PikaToolAction action, PikaDisplay *display); static void pika_paint_tool_button_press (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonPressType press_type, PikaDisplay *display); static void pika_paint_tool_button_release (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonReleaseType release_type, PikaDisplay *display); static void pika_paint_tool_motion (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaDisplay *display); static void pika_paint_tool_modifier_key (PikaTool *tool, GdkModifierType key, gboolean press, GdkModifierType state, PikaDisplay *display); static void pika_paint_tool_cursor_update (PikaTool *tool, const PikaCoords *coords, GdkModifierType state, PikaDisplay *display); static void pika_paint_tool_oper_update (PikaTool *tool, const PikaCoords *coords, GdkModifierType state, gboolean proximity, PikaDisplay *display); static void pika_paint_tool_draw (PikaDrawTool *draw_tool); static void pika_paint_tool_real_paint_prepare (PikaPaintTool *paint_tool, PikaDisplay *display); static PikaCanvasItem * pika_paint_tool_get_outline (PikaPaintTool *paint_tool, PikaDisplay *display, gdouble x, gdouble y); static gboolean pika_paint_tool_check_alpha (PikaPaintTool *paint_tool, PikaDrawable *drawable, PikaDisplay *display, GError **error); static void pika_paint_tool_hard_notify (PikaPaintOptions *options, const GParamSpec *pspec, PikaPaintTool *paint_tool); static void pika_paint_tool_cursor_notify (PikaDisplayConfig *config, GParamSpec *pspec, PikaPaintTool *paint_tool); G_DEFINE_TYPE (PikaPaintTool, pika_paint_tool, PIKA_TYPE_COLOR_TOOL) #define parent_class pika_paint_tool_parent_class static void pika_paint_tool_class_init (PikaPaintToolClass *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->constructed = pika_paint_tool_constructed; object_class->finalize = pika_paint_tool_finalize; tool_class->control = pika_paint_tool_control; tool_class->button_press = pika_paint_tool_button_press; tool_class->button_release = pika_paint_tool_button_release; tool_class->motion = pika_paint_tool_motion; tool_class->modifier_key = pika_paint_tool_modifier_key; tool_class->cursor_update = pika_paint_tool_cursor_update; tool_class->oper_update = pika_paint_tool_oper_update; draw_tool_class->draw = pika_paint_tool_draw; klass->paint_prepare = pika_paint_tool_real_paint_prepare; } static void pika_paint_tool_init (PikaPaintTool *paint_tool) { PikaTool *tool = PIKA_TOOL (paint_tool); pika_tool_control_set_motion_mode (tool->control, PIKA_MOTION_MODE_EXACT); pika_tool_control_set_scroll_lock (tool->control, TRUE); pika_tool_control_set_action_opacity (tool->control, "context-opacity-set"); paint_tool->active = TRUE; paint_tool->pick_colors = FALSE; paint_tool->can_multi_paint = FALSE; paint_tool->draw_line = FALSE; paint_tool->show_cursor = TRUE; paint_tool->draw_brush = TRUE; paint_tool->snap_brush = FALSE; paint_tool->draw_fallback = FALSE; paint_tool->fallback_size = 0.0; paint_tool->draw_circle = FALSE; paint_tool->circle_size = 0.0; paint_tool->status = _("Click to paint"); paint_tool->status_line = _("Click to draw the line"); paint_tool->status_ctrl = _("%s to pick a color"); paint_tool->core = NULL; } static void pika_paint_tool_constructed (GObject *object) { PikaTool *tool = PIKA_TOOL (object); PikaPaintTool *paint_tool = PIKA_PAINT_TOOL (object); PikaPaintOptions *options = PIKA_PAINT_TOOL_GET_OPTIONS (tool); PikaDisplayConfig *display_config; PikaPaintInfo *paint_info; G_OBJECT_CLASS (parent_class)->constructed (object); pika_assert (PIKA_IS_TOOL_INFO (tool->tool_info)); pika_assert (PIKA_IS_PAINT_INFO (tool->tool_info->paint_info)); display_config = PIKA_DISPLAY_CONFIG (tool->tool_info->pika->config); paint_info = tool->tool_info->paint_info; pika_assert (g_type_is_a (paint_info->paint_type, PIKA_TYPE_PAINT_CORE)); paint_tool->core = g_object_new (paint_info->paint_type, "undo-desc", paint_info->blurb, NULL); g_signal_connect_object (options, "notify::hard", G_CALLBACK (pika_paint_tool_hard_notify), paint_tool, 0); pika_paint_tool_hard_notify (options, NULL, paint_tool); paint_tool->show_cursor = display_config->show_paint_tool_cursor; paint_tool->draw_brush = display_config->show_brush_outline; paint_tool->snap_brush = display_config->snap_brush_outline; g_signal_connect_object (display_config, "notify::show-paint-tool-cursor", G_CALLBACK (pika_paint_tool_cursor_notify), paint_tool, 0); g_signal_connect_object (display_config, "notify::show-brush-outline", G_CALLBACK (pika_paint_tool_cursor_notify), paint_tool, 0); g_signal_connect_object (display_config, "notify::snap-brush-outline", G_CALLBACK (pika_paint_tool_cursor_notify), paint_tool, 0); } static void pika_paint_tool_finalize (GObject *object) { PikaPaintTool *paint_tool = PIKA_PAINT_TOOL (object); if (paint_tool->core) { g_object_unref (paint_tool->core); paint_tool->core = NULL; } G_OBJECT_CLASS (parent_class)->finalize (object); } static void pika_paint_tool_control (PikaTool *tool, PikaToolAction action, PikaDisplay *display) { PikaPaintTool *paint_tool = PIKA_PAINT_TOOL (tool); switch (action) { case PIKA_TOOL_ACTION_PAUSE: case PIKA_TOOL_ACTION_RESUME: break; case PIKA_TOOL_ACTION_HALT: pika_paint_core_cleanup (paint_tool->core); break; case PIKA_TOOL_ACTION_COMMIT: break; } PIKA_TOOL_CLASS (parent_class)->control (tool, action, display); } static void pika_paint_tool_button_press (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonPressType press_type, PikaDisplay *display) { PikaDrawTool *draw_tool = PIKA_DRAW_TOOL (tool); PikaPaintTool *paint_tool = PIKA_PAINT_TOOL (tool); PikaPaintOptions *options = PIKA_PAINT_TOOL_GET_OPTIONS (tool); PikaGuiConfig *config = PIKA_GUI_CONFIG (display->pika->config); PikaDisplayShell *shell = pika_display_get_shell (display); PikaImage *image = pika_display_get_image (display); GList *drawables; GList *iter; gboolean constrain; GError *error = NULL; 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; } drawables = pika_image_get_selected_drawables (image); if (drawables == NULL) { pika_tool_message_literal (tool, display, _("No selected drawables.")); return; } else if (! paint_tool->can_multi_paint) { if (g_list_length (drawables) != 1) { pika_tool_message_literal (tool, display, _("Cannot paint on multiple layers. Select only one layer.")); g_list_free (drawables); return; } } for (iter = drawables; iter; iter = iter->next) { PikaDrawable *drawable = iter->data; PikaItem *locked_item = NULL; if (pika_viewable_get_children (PIKA_VIEWABLE (drawable))) { pika_tool_message_literal (tool, display, _("Cannot paint on layer groups.")); g_list_free (drawables); return; } if (pika_item_is_content_locked (PIKA_ITEM (drawable), &locked_item)) { pika_tool_message_literal (tool, display, _("The selected item's pixels are locked.")); pika_tools_blink_lock_box (display->pika, locked_item); g_list_free (drawables); return; } if (! pika_paint_tool_check_alpha (paint_tool, drawable, display, &error)) { GtkWidget *options_gui; GtkWidget *mode_box; pika_tool_message_literal (tool, display, error->message); options_gui = pika_tools_get_tool_options_gui (PIKA_TOOL_OPTIONS (options)); mode_box = pika_paint_options_gui_get_paint_mode_box (options_gui); if (gtk_widget_is_sensitive (mode_box)) { pika_tools_show_tool_options (display->pika); pika_widget_blink (mode_box); } g_clear_error (&error); g_list_free (drawables); return; } if (! pika_item_is_visible (PIKA_ITEM (drawable)) && ! config->edit_non_visible) { pika_tool_message_literal (tool, display, _("A selected layer is not visible.")); g_list_free (drawables); return; } } if (pika_draw_tool_is_active (draw_tool)) pika_draw_tool_stop (draw_tool); if (tool->display && tool->display != display && pika_display_get_image (tool->display) == image) { /* if this is a different display, but the same image, HACK around * in tool internals AFTER stopping the current draw_tool, so * straight line drawing works across different views of the * same image. */ tool->display = display; } constrain = (state & pika_get_constrain_behavior_mask ()) != 0; if (! pika_paint_tool_paint_start (paint_tool, display, coords, time, constrain, &error)) { pika_tool_message_literal (tool, display, error->message); g_clear_error (&error); return; } tool->display = display; tool->drawables = drawables; /* pause the current selection */ pika_display_shell_selection_pause (shell); pika_draw_tool_start (draw_tool, display); pika_tool_control_activate (tool->control); } static void pika_paint_tool_button_release (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonReleaseType release_type, PikaDisplay *display) { PikaPaintTool *paint_tool = PIKA_PAINT_TOOL (tool); PikaDisplayShell *shell = pika_display_get_shell (display); PikaImage *image = pika_display_get_image (display); gboolean cancel; 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; } cancel = (release_type == PIKA_BUTTON_RELEASE_CANCEL); pika_paint_tool_paint_end (paint_tool, time, cancel); pika_tool_control_halt (tool->control); pika_draw_tool_pause (PIKA_DRAW_TOOL (tool)); /* resume the current selection */ pika_display_shell_selection_resume (shell); pika_image_flush (image); pika_draw_tool_resume (PIKA_DRAW_TOOL (tool)); } static void pika_paint_tool_motion (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaDisplay *display) { PikaPaintTool *paint_tool = PIKA_PAINT_TOOL (tool); PIKA_TOOL_CLASS (parent_class)->motion (tool, coords, time, state, display); if (pika_color_tool_is_enabled (PIKA_COLOR_TOOL (tool))) return; if (! paint_tool->snap_brush) pika_draw_tool_pause (PIKA_DRAW_TOOL (tool)); pika_paint_tool_paint_motion (paint_tool, coords, time); if (! paint_tool->snap_brush) pika_draw_tool_resume (PIKA_DRAW_TOOL (tool)); } static void pika_paint_tool_modifier_key (PikaTool *tool, GdkModifierType key, gboolean press, GdkModifierType state, PikaDisplay *display) { PikaPaintTool *paint_tool = PIKA_PAINT_TOOL (tool); PikaDrawTool *draw_tool = PIKA_DRAW_TOOL (tool); if (paint_tool->pick_colors && ! paint_tool->draw_line) { if ((state & pika_get_all_modifiers_mask ()) == pika_get_constrain_behavior_mask ()) { if (! pika_color_tool_is_enabled (PIKA_COLOR_TOOL (tool))) { PikaToolInfo *info = pika_get_tool_info (display->pika, "pika-color-picker-tool"); if (PIKA_IS_TOOL_INFO (info)) { if (pika_draw_tool_is_active (draw_tool)) pika_draw_tool_stop (draw_tool); pika_color_tool_enable (PIKA_COLOR_TOOL (tool), PIKA_COLOR_OPTIONS (info->tool_options)); switch (PIKA_COLOR_TOOL (tool)->pick_target) { case PIKA_COLOR_PICK_TARGET_FOREGROUND: pika_tool_push_status (tool, display, _("Click in any image to pick the " "foreground color")); break; case PIKA_COLOR_PICK_TARGET_BACKGROUND: pika_tool_push_status (tool, display, _("Click in any image to pick the " "background color")); break; default: break; } } } } else { if (pika_color_tool_is_enabled (PIKA_COLOR_TOOL (tool))) { pika_tool_pop_status (tool, display); pika_color_tool_disable (PIKA_COLOR_TOOL (tool)); } } } } static void pika_paint_tool_cursor_update (PikaTool *tool, const PikaCoords *coords, GdkModifierType state, PikaDisplay *display) { PikaPaintTool *paint_tool = PIKA_PAINT_TOOL (tool); PikaGuiConfig *config = PIKA_GUI_CONFIG (display->pika->config); PikaCursorModifier modifier; PikaCursorModifier toggle_modifier; PikaCursorModifier old_modifier; PikaCursorModifier old_toggle_modifier; modifier = tool->control->cursor_modifier; toggle_modifier = tool->control->toggle_cursor_modifier; old_modifier = modifier; old_toggle_modifier = toggle_modifier; if (! pika_color_tool_is_enabled (PIKA_COLOR_TOOL (tool))) { PikaImage *image = pika_display_get_image (display); GList *drawables = pika_image_get_selected_drawables (image); GList *iter; if (! drawables) return; for (iter = drawables; iter; iter = iter->next) { PikaDrawable *drawable = iter->data; if (pika_viewable_get_children (PIKA_VIEWABLE (drawable)) || pika_item_is_content_locked (PIKA_ITEM (drawable), NULL) || ! pika_paint_tool_check_alpha (paint_tool, drawable, display, NULL) || ! (pika_item_is_visible (PIKA_ITEM (drawable)) || config->edit_non_visible)) { modifier = PIKA_CURSOR_MODIFIER_BAD; toggle_modifier = PIKA_CURSOR_MODIFIER_BAD; break; } } g_list_free (drawables); if (! paint_tool->show_cursor && modifier != PIKA_CURSOR_MODIFIER_BAD) { if (paint_tool->draw_brush) pika_tool_set_cursor (tool, display, PIKA_CURSOR_NONE, PIKA_TOOL_CURSOR_NONE, PIKA_CURSOR_MODIFIER_NONE); else pika_tool_set_cursor (tool, display, PIKA_CURSOR_SINGLE_DOT, PIKA_TOOL_CURSOR_NONE, PIKA_CURSOR_MODIFIER_NONE); return; } pika_tool_control_set_cursor_modifier (tool->control, modifier); pika_tool_control_set_toggle_cursor_modifier (tool->control, toggle_modifier); } PIKA_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display); /* reset old stuff here so we are not interfering with the modifiers * set by our subclasses */ pika_tool_control_set_cursor_modifier (tool->control, old_modifier); pika_tool_control_set_toggle_cursor_modifier (tool->control, old_toggle_modifier); } static void pika_paint_tool_oper_update (PikaTool *tool, const PikaCoords *coords, GdkModifierType state, gboolean proximity, PikaDisplay *display) { PikaPaintTool *paint_tool = PIKA_PAINT_TOOL (tool); PikaDrawTool *draw_tool = PIKA_DRAW_TOOL (tool); PikaPaintOptions *paint_options = PIKA_PAINT_TOOL_GET_OPTIONS (tool); PikaPaintCore *core = paint_tool->core; PikaDisplayShell *shell = pika_display_get_shell (display); PikaImage *image = pika_display_get_image (display); GList *drawables; gchar *status = NULL; if (pika_color_tool_is_enabled (PIKA_COLOR_TOOL (tool))) { PIKA_TOOL_CLASS (parent_class)->oper_update (tool, coords, state, proximity, display); return; } pika_draw_tool_pause (draw_tool); if (pika_draw_tool_is_active (draw_tool) && draw_tool->display != display) pika_draw_tool_stop (draw_tool); if (tool->display && tool->display != display && pika_display_get_image (tool->display) == image) { /* if this is a different display, but the same image, HACK around * in tool internals AFTER stopping the current draw_tool, so * straight line drawing works across different views of the * same image. */ tool->display = display; } drawables = pika_image_get_selected_drawables (image); if ((g_list_length (drawables) == 1 || (g_list_length (drawables) > 1 && paint_tool->can_multi_paint)) && proximity) { gboolean constrain_mask = pika_get_constrain_behavior_mask (); core->cur_coords = *coords; if (display == tool->display && (state & PIKA_PAINT_TOOL_LINE_MASK)) { /* If shift is down and this is not the first paint stroke, * draw a line. */ gchar *status_help; gdouble offset_angle; gdouble xres, yres; pika_display_shell_get_constrained_line_params (shell, &offset_angle, &xres, &yres); pika_paint_core_round_line (core, paint_options, (state & constrain_mask) != 0, offset_angle, xres, yres); status_help = pika_suggest_modifiers (paint_tool->status_line, constrain_mask & ~state, NULL, _("%s for constrained angles"), NULL); status = pika_display_shell_get_line_status (shell, status_help, ". ", core->last_coords.x, core->last_coords.y, core->cur_coords.x, core->cur_coords.y); g_free (status_help); paint_tool->draw_line = TRUE; } else { GdkModifierType modifiers = 0; /* HACK: A paint tool may set status_ctrl to NULL to indicate that * it ignores the Ctrl modifier (temporarily or permanently), so * it should not be suggested. This is different from how * pika_suggest_modifiers() would interpret this parameter. */ if (paint_tool->status_ctrl != NULL) modifiers |= constrain_mask; /* suggest drawing lines only after the first point is set */ if (display == tool->display) modifiers |= PIKA_PAINT_TOOL_LINE_MASK; status = pika_suggest_modifiers (paint_tool->status, modifiers & ~state, _("%s for a straight line"), paint_tool->status_ctrl, NULL); paint_tool->draw_line = FALSE; } paint_tool->cursor_x = core->cur_coords.x; paint_tool->cursor_y = core->cur_coords.y; if (! pika_draw_tool_is_active (draw_tool)) pika_draw_tool_start (draw_tool, display); } else if (pika_draw_tool_is_active (draw_tool)) { pika_draw_tool_stop (draw_tool); } if (status != NULL) { pika_tool_push_status (tool, display, "%s", status); g_free (status); } else { pika_tool_pop_status (tool, display); } PIKA_TOOL_CLASS (parent_class)->oper_update (tool, coords, state, proximity, display); pika_draw_tool_resume (draw_tool); g_list_free (drawables); } static void pika_paint_tool_draw (PikaDrawTool *draw_tool) { PikaPaintTool *paint_tool = PIKA_PAINT_TOOL (draw_tool); if (paint_tool->active && ! pika_color_tool_is_enabled (PIKA_COLOR_TOOL (draw_tool))) { PikaPaintCore *core = paint_tool->core; PikaCanvasItem *outline = NULL; gboolean line_drawn = FALSE; gdouble cur_x, cur_y; if (pika_paint_tool_paint_is_active (paint_tool) && paint_tool->snap_brush) { cur_x = paint_tool->paint_x; cur_y = paint_tool->paint_y; } else { cur_x = paint_tool->cursor_x; cur_y = paint_tool->cursor_y; if (paint_tool->draw_line && ! pika_tool_control_is_active (PIKA_TOOL (draw_tool)->control)) { PikaCanvasGroup *group; gdouble last_x, last_y; last_x = core->last_coords.x; last_y = core->last_coords.y; group = pika_draw_tool_add_stroke_group (draw_tool); pika_draw_tool_push_group (draw_tool, group); pika_draw_tool_add_handle (draw_tool, PIKA_HANDLE_CIRCLE, last_x, last_y, PIKA_TOOL_HANDLE_SIZE_CIRCLE, PIKA_TOOL_HANDLE_SIZE_CIRCLE, PIKA_HANDLE_ANCHOR_CENTER); pika_draw_tool_add_line (draw_tool, last_x, last_y, cur_x, cur_y); pika_draw_tool_add_handle (draw_tool, PIKA_HANDLE_CIRCLE, cur_x, cur_y, PIKA_TOOL_HANDLE_SIZE_CIRCLE, PIKA_TOOL_HANDLE_SIZE_CIRCLE, PIKA_HANDLE_ANCHOR_CENTER); pika_draw_tool_pop_group (draw_tool); line_drawn = TRUE; } } pika_paint_tool_set_draw_fallback (paint_tool, FALSE, 0.0); pika_paint_tool_set_draw_circle (paint_tool, FALSE, 0.0); if (paint_tool->draw_brush) outline = pika_paint_tool_get_outline (paint_tool, draw_tool->display, cur_x, cur_y); if (outline) { pika_draw_tool_add_item (draw_tool, outline); g_object_unref (outline); } else if (paint_tool->draw_fallback) { /* Lets make a sensible fallback cursor * * Sensible cursor is * * crossed to indicate draw point * * reactive to options alterations * * not a full circle that would be in the way */ gint size = (gint) paint_tool->fallback_size; #define TICKMARK_ANGLE 48 #define ROTATION_ANGLE G_PI / 4 /* marks for indicating full size */ pika_draw_tool_add_arc (draw_tool, FALSE, cur_x - (size / 2.0), cur_y - (size / 2.0), size, size, ROTATION_ANGLE - (2.0 * G_PI) / (TICKMARK_ANGLE * 2), (2.0 * G_PI) / TICKMARK_ANGLE); pika_draw_tool_add_arc (draw_tool, FALSE, cur_x - (size / 2.0), cur_y - (size / 2.0), size, size, ROTATION_ANGLE + G_PI / 2 - (2.0 * G_PI) / (TICKMARK_ANGLE * 2), (2.0 * G_PI) / TICKMARK_ANGLE); pika_draw_tool_add_arc (draw_tool, FALSE, cur_x - (size / 2.0), cur_y - (size / 2.0), size, size, ROTATION_ANGLE + G_PI - (2.0 * G_PI) / (TICKMARK_ANGLE * 2), (2.0 * G_PI) / TICKMARK_ANGLE); pika_draw_tool_add_arc (draw_tool, FALSE, cur_x - (size / 2.0), cur_y - (size / 2.0), size, size, ROTATION_ANGLE + 3 * G_PI / 2 - (2.0 * G_PI) / (TICKMARK_ANGLE * 2), (2.0 * G_PI) / TICKMARK_ANGLE); } else if (paint_tool->draw_circle) { gint size = (gint) paint_tool->circle_size; /* draw an indicatory circle */ pika_draw_tool_add_arc (draw_tool, FALSE, cur_x - (size / 2.0), cur_y - (size / 2.0), size, size, 0.0, (2.0 * G_PI)); } if (! outline && ! paint_tool->draw_fallback && ! line_drawn && ! paint_tool->show_cursor && ! paint_tool->draw_circle) { /* I am not sure this case can/should ever happen since now we * always set the PIKA_CURSOR_SINGLE_DOT when neither pointer * nor outline options are checked. Yet let's imagine any * weird case where brush outline is wanted, without pointer * cursor, yet we fail to draw the outline while neither * circle nor fallbacks are requested (it depends on per-class * implementation of get_outline()). * * In such a case, we don't want to leave the user without any * indication so we draw a fallback crosshair. */ if (paint_tool->draw_brush) pika_draw_tool_add_handle (draw_tool, PIKA_HANDLE_CIRCLE, cur_x, cur_y, PIKA_TOOL_HANDLE_SIZE_CROSSHAIR, PIKA_TOOL_HANDLE_SIZE_CROSSHAIR, PIKA_HANDLE_ANCHOR_CENTER); } } PIKA_DRAW_TOOL_CLASS (parent_class)->draw (draw_tool); } static void pika_paint_tool_real_paint_prepare (PikaPaintTool *paint_tool, PikaDisplay *display) { PikaDisplayShell *shell = pika_display_get_shell (display); pika_paint_core_set_show_all (paint_tool->core, shell->show_all); } static PikaCanvasItem * pika_paint_tool_get_outline (PikaPaintTool *paint_tool, PikaDisplay *display, gdouble x, gdouble y) { if (PIKA_PAINT_TOOL_GET_CLASS (paint_tool)->get_outline) return PIKA_PAINT_TOOL_GET_CLASS (paint_tool)->get_outline (paint_tool, display, x, y); return NULL; } static gboolean pika_paint_tool_check_alpha (PikaPaintTool *paint_tool, PikaDrawable *drawable, PikaDisplay *display, GError **error) { PikaPaintToolClass *klass = PIKA_PAINT_TOOL_GET_CLASS (paint_tool); if (klass->is_alpha_only && klass->is_alpha_only (paint_tool, drawable)) { PikaLayer *locked_layer = NULL; if (! pika_drawable_has_alpha (drawable)) { g_set_error_literal ( error, PIKA_ERROR, PIKA_FAILED, _("The selected drawable does not have an alpha channel.")); return FALSE; } if (PIKA_IS_LAYER (drawable) && pika_layer_is_alpha_locked (PIKA_LAYER (drawable), &locked_layer)) { g_set_error_literal ( error, PIKA_ERROR, PIKA_FAILED, _("The selected layer's alpha channel is locked.")); if (error) pika_tools_blink_lock_box (display->pika, PIKA_ITEM (locked_layer)); return FALSE; } } return TRUE; } static void pika_paint_tool_hard_notify (PikaPaintOptions *options, const GParamSpec *pspec, PikaPaintTool *paint_tool) { if (paint_tool->active) { PikaTool *tool = PIKA_TOOL (paint_tool); pika_tool_control_set_precision (tool->control, options->hard ? PIKA_CURSOR_PRECISION_PIXEL_CENTER : PIKA_CURSOR_PRECISION_SUBPIXEL); } } static void pika_paint_tool_cursor_notify (PikaDisplayConfig *config, GParamSpec *pspec, PikaPaintTool *paint_tool) { pika_draw_tool_pause (PIKA_DRAW_TOOL (paint_tool)); paint_tool->show_cursor = config->show_paint_tool_cursor; paint_tool->draw_brush = config->show_brush_outline; paint_tool->snap_brush = config->snap_brush_outline; pika_draw_tool_resume (PIKA_DRAW_TOOL (paint_tool)); } void pika_paint_tool_set_active (PikaPaintTool *tool, gboolean active) { g_return_if_fail (PIKA_IS_PAINT_TOOL (tool)); if (active != tool->active) { PikaPaintOptions *options = PIKA_PAINT_TOOL_GET_OPTIONS (tool); pika_draw_tool_pause (PIKA_DRAW_TOOL (tool)); tool->active = active; if (active) pika_paint_tool_hard_notify (options, NULL, tool); pika_draw_tool_resume (PIKA_DRAW_TOOL (tool)); } } /** * pika_paint_tool_enable_color_picker: * @tool: a #PikaPaintTool * @target: the #PikaColorPickTarget to set * * This is a convenience function used from the init method of paint * tools that want the color picking functionality. The @mode that is * set here is used to decide what cursor modifier to draw and if the * picked color goes to the foreground or background color. **/ void pika_paint_tool_enable_color_picker (PikaPaintTool *tool, PikaColorPickTarget target) { g_return_if_fail (PIKA_IS_PAINT_TOOL (tool)); tool->pick_colors = TRUE; PIKA_COLOR_TOOL (tool)->pick_target = target; } /** * pika_paint_tool_enable_multi_paint: * @tool: a #PikaPaintTool * * This is a convenience function used from the init method of paint * tools that want to allow painting with several drawables. **/ void pika_paint_tool_enable_multi_paint (PikaPaintTool *tool) { g_return_if_fail (PIKA_IS_PAINT_TOOL (tool)); tool->can_multi_paint = TRUE; } void pika_paint_tool_set_draw_fallback (PikaPaintTool *tool, gboolean draw_fallback, gint fallback_size) { g_return_if_fail (PIKA_IS_PAINT_TOOL (tool)); tool->draw_fallback = draw_fallback; tool->fallback_size = fallback_size; } void pika_paint_tool_set_draw_circle (PikaPaintTool *tool, gboolean draw_circle, gint circle_size) { g_return_if_fail (PIKA_IS_PAINT_TOOL (tool)); tool->draw_circle = draw_circle; tool->circle_size = circle_size; } /** * pika_paint_tool_force_draw: * @tool: * @force: * * If @force is %TRUE, the brush, or a fallback, or circle will be * drawn, regardless of the Preferences settings. This can be used for * code such as when modifying brush size or shape on-canvas with a * visual feedback, temporarily bypassing the user setting. */ void pika_paint_tool_force_draw (PikaPaintTool *tool, gboolean force) { PikaDisplayConfig *display_config; g_return_if_fail (PIKA_IS_PAINT_TOOL (tool)); display_config = PIKA_DISPLAY_CONFIG (PIKA_TOOL (tool)->tool_info->pika->config); if (force) tool->draw_brush = TRUE; else tool->draw_brush = display_config->show_brush_outline; }