/* 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 . */ /* This file contains a base class for tools that implement on canvas * preview for non destructive editing. The processing of the pixels can * be done either by a gegl op or by a C function (apply_func). * * For the core side of this, please see app/core/pikadrawablefilter.c. */ #include "config.h" #include #include #include #include #include "libpikabase/pikabase.h" #include "libpikaconfig/pikaconfig.h" #include "libpikamath/pikamath.h" #include "libpikawidgets/pikawidgets.h" #include "tools-types.h" #include "config/pikaguiconfig.h" #include "operations/pika-operation-config.h" #include "operations/pikaoperationsettings.h" #include "gegl/pika-gegl-utils.h" #include "core/pika.h" #include "core/pikachannel.h" #include "core/pikadrawable.h" #include "core/pikadrawablefilter.h" #include "core/pikaerror.h" #include "core/pikaguide.h" #include "core/pikaimage.h" #include "core/pikaimage-guides.h" #include "core/pikaimage-pick-color.h" #include "core/pikalayer.h" #include "core/pikalist.h" #include "core/pikapickable.h" #include "core/pikaprogress.h" #include "core/pikaprojection.h" #include "core/pikasettings.h" #include "core/pikatoolinfo.h" #include "widgets/pikalayermodebox.h" #include "widgets/pikapropwidgets.h" #include "widgets/pikasettingsbox.h" #include "widgets/pikawidgets-utils.h" #include "display/pikadisplay.h" #include "display/pikadisplayshell.h" #include "display/pikadisplayshell-appearance.h" #include "display/pikadisplayshell-transform.h" #include "display/pikatoolgui.h" #include "display/pikatoolwidget.h" #include "pikafilteroptions.h" #include "pikafiltertool.h" #include "pikafiltertool-settings.h" #include "pikafiltertool-widgets.h" #include "pikaguidetool.h" #include "pikatoolcontrol.h" #include "pikatools-utils.h" #include "tool_manager.h" #include "pika-intl.h" /* local function prototypes */ static void pika_filter_tool_finalize (GObject *object); static gboolean pika_filter_tool_initialize (PikaTool *tool, PikaDisplay *display, GError **error); static void pika_filter_tool_control (PikaTool *tool, PikaToolAction action, PikaDisplay *display); static void pika_filter_tool_button_press (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonPressType press_type, PikaDisplay *display); static void pika_filter_tool_button_release (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonReleaseType release_type, PikaDisplay *display); static void pika_filter_tool_motion (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaDisplay *display); static gboolean pika_filter_tool_key_press (PikaTool *tool, GdkEventKey *kevent, PikaDisplay *display); static void pika_filter_tool_oper_update (PikaTool *tool, const PikaCoords *coords, GdkModifierType state, gboolean proximity, PikaDisplay *display); static void pika_filter_tool_cursor_update (PikaTool *tool, const PikaCoords *coords, GdkModifierType state, PikaDisplay *display); static void pika_filter_tool_options_notify (PikaTool *tool, PikaToolOptions *options, const GParamSpec *pspec); static gboolean pika_filter_tool_can_pick_color (PikaColorTool *color_tool, const PikaCoords *coords, PikaDisplay *display); static gboolean pika_filter_tool_pick_color (PikaColorTool *color_tool, const PikaCoords *coords, PikaDisplay *display, const Babl **sample_format, gpointer pixel, PikaRGB *color); static void pika_filter_tool_color_picked (PikaColorTool *color_tool, const PikaCoords *coords, PikaDisplay *display, PikaColorPickState pick_state, const Babl *sample_format, gpointer pixel, const PikaRGB *color); static void pika_filter_tool_real_reset (PikaFilterTool *filter_tool); static void pika_filter_tool_real_set_config(PikaFilterTool *filter_tool, PikaConfig *config); static void pika_filter_tool_real_config_notify (PikaFilterTool *filter_tool, PikaConfig *config, const GParamSpec *pspec); static void pika_filter_tool_halt (PikaFilterTool *filter_tool); static void pika_filter_tool_commit (PikaFilterTool *filter_tool); static void pika_filter_tool_dialog (PikaFilterTool *filter_tool); static void pika_filter_tool_reset (PikaFilterTool *filter_tool); static void pika_filter_tool_create_filter (PikaFilterTool *filter_tool); static void pika_filter_tool_update_dialog (PikaFilterTool *filter_tool); static void pika_filter_tool_update_dialog_operation_settings (PikaFilterTool *filter_tool); static void pika_filter_tool_region_changed (PikaFilterTool *filter_tool); static void pika_filter_tool_flush (PikaDrawableFilter *filter, PikaFilterTool *filter_tool); static void pika_filter_tool_config_notify (GObject *object, const GParamSpec *pspec, PikaFilterTool *filter_tool); static void pika_filter_tool_unset_setting (GObject *object, const GParamSpec *pspec, PikaFilterTool *filter_tool); static void pika_filter_tool_lock_position_changed (PikaDrawable *drawable, PikaFilterTool *filter_tool); static void pika_filter_tool_mask_changed (PikaImage *image, PikaFilterTool *filter_tool); static void pika_filter_tool_add_guide (PikaFilterTool *filter_tool); static void pika_filter_tool_remove_guide (PikaFilterTool *filter_tool); static void pika_filter_tool_move_guide (PikaFilterTool *filter_tool); static void pika_filter_tool_guide_removed (PikaGuide *guide, PikaFilterTool *filter_tool); static void pika_filter_tool_guide_moved (PikaGuide *guide, const GParamSpec *pspec, PikaFilterTool *filter_tool); static void pika_filter_tool_response (PikaToolGui *gui, gint response_id, PikaFilterTool *filter_tool); static void pika_filter_tool_update_filter (PikaFilterTool *filter_tool); static void pika_filter_tool_set_has_settings (PikaFilterTool *filter_tool, gboolean has_settings); G_DEFINE_TYPE (PikaFilterTool, pika_filter_tool, PIKA_TYPE_COLOR_TOOL) #define parent_class pika_filter_tool_parent_class static void pika_filter_tool_class_init (PikaFilterToolClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); PikaToolClass *tool_class = PIKA_TOOL_CLASS (klass); PikaColorToolClass *color_tool_class = PIKA_COLOR_TOOL_CLASS (klass); object_class->finalize = pika_filter_tool_finalize; tool_class->initialize = pika_filter_tool_initialize; tool_class->control = pika_filter_tool_control; tool_class->button_press = pika_filter_tool_button_press; tool_class->button_release = pika_filter_tool_button_release; tool_class->motion = pika_filter_tool_motion; tool_class->key_press = pika_filter_tool_key_press; tool_class->oper_update = pika_filter_tool_oper_update; tool_class->cursor_update = pika_filter_tool_cursor_update; tool_class->options_notify = pika_filter_tool_options_notify; color_tool_class->can_pick = pika_filter_tool_can_pick_color; color_tool_class->pick = pika_filter_tool_pick_color; color_tool_class->picked = pika_filter_tool_color_picked; klass->get_operation = NULL; klass->dialog = NULL; klass->reset = pika_filter_tool_real_reset; klass->set_config = pika_filter_tool_real_set_config; klass->config_notify = pika_filter_tool_real_config_notify; klass->settings_import = pika_filter_tool_real_settings_import; klass->settings_export = pika_filter_tool_real_settings_export; } static void pika_filter_tool_init (PikaFilterTool *filter_tool) { PikaTool *tool = PIKA_TOOL (filter_tool); pika_tool_control_set_scroll_lock (tool->control, TRUE); pika_tool_control_set_preserve (tool->control, FALSE); pika_tool_control_set_dirty_mask (tool->control, PIKA_DIRTY_IMAGE | PIKA_DIRTY_IMAGE_STRUCTURE | PIKA_DIRTY_DRAWABLE | PIKA_DIRTY_ACTIVE_DRAWABLE); pika_tool_control_set_active_modifiers (tool->control, PIKA_TOOL_ACTIVE_MODIFIERS_SEPARATE); } static void pika_filter_tool_finalize (GObject *object) { PikaFilterTool *filter_tool = PIKA_FILTER_TOOL (object); g_clear_object (&filter_tool->operation); g_clear_object (&filter_tool->config); g_clear_object (&filter_tool->default_config); g_clear_object (&filter_tool->settings); g_clear_pointer (&filter_tool->description, g_free); g_clear_object (&filter_tool->gui); filter_tool->settings_box = NULL; filter_tool->controller_toggle = NULL; filter_tool->clip_combo = NULL; filter_tool->region_combo = NULL; G_OBJECT_CLASS (parent_class)->finalize (object); } #define RESPONSE_RESET 1 static gboolean pika_filter_tool_initialize (PikaTool *tool, PikaDisplay *display, GError **error) { PikaFilterTool *filter_tool = PIKA_FILTER_TOOL (tool); PikaToolInfo *tool_info = tool->tool_info; PikaGuiConfig *config = PIKA_GUI_CONFIG (display->pika->config); PikaImage *image = pika_display_get_image (display); PikaDisplayShell *shell = pika_display_get_shell (display); PikaItem *locked_item = NULL; GList *drawables = pika_image_get_selected_drawables (image); PikaDrawable *drawable; if (g_list_length (drawables) != 1) { if (g_list_length (drawables) > 1) g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED, _("Cannot modify multiple drawables. Select only one.")); else g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED, _("No selected drawables.")); g_list_free (drawables); return FALSE; } drawable = drawables->data; if (pika_viewable_get_children (PIKA_VIEWABLE (drawable))) { g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED, _("Cannot modify the pixels of layer groups.")); g_list_free (drawables); return FALSE; } if (pika_item_is_content_locked (PIKA_ITEM (drawable), &locked_item)) { g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED, _("A selected item's pixels are locked.")); if (error) pika_tools_blink_lock_box (display->pika, locked_item); g_list_free (drawables); return FALSE; } if (! pika_item_is_visible (PIKA_ITEM (drawable)) && ! config->edit_non_visible) { g_set_error_literal (error, PIKA_ERROR, PIKA_FAILED, _("A selected layer is not visible.")); g_list_free (drawables); return FALSE; } pika_filter_tool_get_operation (filter_tool); pika_filter_tool_disable_color_picking (filter_tool); tool->display = display; g_list_free (tool->drawables); tool->drawables = drawables; if (filter_tool->config) pika_config_reset (PIKA_CONFIG (filter_tool->config)); if (! filter_tool->gui) { GtkWidget *vbox; GtkWidget *hbox; GtkWidget *toggle; /* disabled for at least PIKA 2.8 */ filter_tool->overlay = FALSE; filter_tool->gui = pika_tool_gui_new (tool_info, pika_tool_get_label (tool), filter_tool->description, pika_tool_get_icon_name (tool), pika_tool_get_help_id (tool), pika_widget_get_monitor (GTK_WIDGET (shell)), filter_tool->overlay, _("_Reset"), RESPONSE_RESET, _("_Cancel"), GTK_RESPONSE_CANCEL, _("_OK"), GTK_RESPONSE_OK, NULL); pika_tool_gui_set_default_response (filter_tool->gui, GTK_RESPONSE_OK); pika_tool_gui_set_alternative_button_order (filter_tool->gui, RESPONSE_RESET, GTK_RESPONSE_OK, GTK_RESPONSE_CANCEL, -1); vbox = pika_tool_gui_get_vbox (filter_tool->gui); g_signal_connect_object (filter_tool->gui, "response", G_CALLBACK (pika_filter_tool_response), G_OBJECT (filter_tool), 0); if (filter_tool->config) { filter_tool->settings_box = pika_filter_tool_get_settings_box (filter_tool); gtk_box_pack_start (GTK_BOX (vbox), filter_tool->settings_box, FALSE, FALSE, 0); if (filter_tool->has_settings) gtk_widget_show (filter_tool->settings_box); } /* The preview and split view toggles */ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); gtk_widget_show (hbox); toggle = pika_prop_check_button_new (G_OBJECT (tool_info->tool_options), "preview", NULL); gtk_box_pack_start (GTK_BOX (hbox), toggle, TRUE, TRUE, 0); toggle = pika_prop_check_button_new (G_OBJECT (tool_info->tool_options), "preview-split", NULL); gtk_box_pack_start (GTK_BOX (hbox), toggle, FALSE, FALSE, 0); g_object_bind_property (G_OBJECT (tool_info->tool_options), "preview", toggle, "sensitive", G_BINDING_SYNC_CREATE); /* The show-controller toggle */ filter_tool->controller_toggle = pika_prop_check_button_new (G_OBJECT (tool_info->tool_options), "controller", NULL); gtk_box_pack_end (GTK_BOX (vbox), filter_tool->controller_toggle, FALSE, FALSE, 0); if (! filter_tool->widget) gtk_widget_hide (filter_tool->controller_toggle); /* The operation-settings box */ filter_tool->operation_settings_box = gtk_box_new ( GTK_ORIENTATION_VERTICAL, 2); gtk_box_pack_end (GTK_BOX (vbox), filter_tool->operation_settings_box, FALSE, FALSE, 0); gtk_widget_show (filter_tool->operation_settings_box); pika_filter_tool_update_dialog_operation_settings (filter_tool); /* Fill in subclass widgets */ pika_filter_tool_dialog (filter_tool); } else { pika_tool_gui_set_title (filter_tool->gui, pika_tool_get_label (tool)); pika_tool_gui_set_description (filter_tool->gui, filter_tool->description); pika_tool_gui_set_icon_name (filter_tool->gui, pika_tool_get_icon_name (tool)); pika_tool_gui_set_help_id (filter_tool->gui, pika_tool_get_help_id (tool)); } pika_tool_gui_set_shell (filter_tool->gui, shell); pika_tool_gui_set_viewable (filter_tool->gui, PIKA_VIEWABLE (drawable)); pika_tool_gui_show (filter_tool->gui); g_signal_connect_object (drawable, "lock-position-changed", G_CALLBACK (pika_filter_tool_lock_position_changed), filter_tool, 0); g_signal_connect_object (image, "mask-changed", G_CALLBACK (pika_filter_tool_mask_changed), filter_tool, 0); pika_filter_tool_mask_changed (image, filter_tool); pika_filter_tool_create_filter (filter_tool); return TRUE; } static void pika_filter_tool_control (PikaTool *tool, PikaToolAction action, PikaDisplay *display) { PikaFilterTool *filter_tool = PIKA_FILTER_TOOL (tool); switch (action) { case PIKA_TOOL_ACTION_PAUSE: case PIKA_TOOL_ACTION_RESUME: break; case PIKA_TOOL_ACTION_HALT: pika_filter_tool_halt (filter_tool); break; case PIKA_TOOL_ACTION_COMMIT: pika_filter_tool_commit (filter_tool); break; } PIKA_TOOL_CLASS (parent_class)->control (tool, action, display); } static void pika_filter_tool_button_press (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonPressType press_type, PikaDisplay *display) { PikaFilterTool *filter_tool = PIKA_FILTER_TOOL (tool); if (pika_filter_tool_on_guide (filter_tool, coords, display)) { PikaFilterOptions *options = PIKA_FILTER_TOOL_GET_OPTIONS (tool); if (state & pika_get_extend_selection_mask ()) { pika_filter_options_switch_preview_side (options); } else if (state & pika_get_toggle_behavior_mask ()) { PikaItem *item = PIKA_ITEM (tool->drawables->data); gint pos_x; gint pos_y; pos_x = CLAMP (RINT (coords->x) - pika_item_get_offset_x (item), 0, pika_item_get_width (item)); pos_y = CLAMP (RINT (coords->y) - pika_item_get_offset_y (item), 0, pika_item_get_height (item)); pika_filter_options_switch_preview_orientation (options, pos_x, pos_y); } else { pika_guide_tool_start_edit (tool, display, filter_tool->preview_guide); } } else if (pika_color_tool_is_enabled (PIKA_COLOR_TOOL (tool))) { PIKA_TOOL_CLASS (parent_class)->button_press (tool, coords, time, state, press_type, display); } else if (filter_tool->widget) { if (pika_tool_widget_button_press (filter_tool->widget, coords, time, state, press_type)) { filter_tool->grab_widget = filter_tool->widget; pika_tool_control_activate (tool->control); } } } static void pika_filter_tool_button_release (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaButtonReleaseType release_type, PikaDisplay *display) { PikaFilterTool *filter_tool = PIKA_FILTER_TOOL (tool); if (filter_tool->grab_widget) { pika_tool_control_halt (tool->control); pika_tool_widget_button_release (filter_tool->grab_widget, coords, time, state, release_type); filter_tool->grab_widget = NULL; } else { PIKA_TOOL_CLASS (parent_class)->button_release (tool, coords, time, state, release_type, display); } } static void pika_filter_tool_motion (PikaTool *tool, const PikaCoords *coords, guint32 time, GdkModifierType state, PikaDisplay *display) { PikaFilterTool *filter_tool = PIKA_FILTER_TOOL (tool); if (filter_tool->grab_widget) { pika_tool_widget_motion (filter_tool->grab_widget, coords, time, state); } else { PIKA_TOOL_CLASS (parent_class)->motion (tool, coords, time, state, display); } } static gboolean pika_filter_tool_key_press (PikaTool *tool, GdkEventKey *kevent, PikaDisplay *display) { PikaFilterTool *filter_tool = PIKA_FILTER_TOOL (tool); if (filter_tool->gui && display == tool->display) { switch (kevent->keyval) { case GDK_KEY_Return: case GDK_KEY_KP_Enter: case GDK_KEY_ISO_Enter: pika_filter_tool_response (filter_tool->gui, GTK_RESPONSE_OK, filter_tool); return TRUE; case GDK_KEY_BackSpace: pika_filter_tool_response (filter_tool->gui, RESPONSE_RESET, filter_tool); return TRUE; case GDK_KEY_Escape: pika_filter_tool_response (filter_tool->gui, GTK_RESPONSE_CANCEL, filter_tool); return TRUE; } } return PIKA_TOOL_CLASS (parent_class)->key_press (tool, kevent, display); } static void pika_filter_tool_oper_update (PikaTool *tool, const PikaCoords *coords, GdkModifierType state, gboolean proximity, PikaDisplay *display) { PikaFilterTool *filter_tool = PIKA_FILTER_TOOL (tool); pika_tool_pop_status (tool, display); if (pika_filter_tool_on_guide (filter_tool, coords, display)) { GdkModifierType extend_mask = pika_get_extend_selection_mask (); GdkModifierType toggle_mask = pika_get_toggle_behavior_mask (); gchar *status = NULL; if (state & extend_mask) { status = g_strdup (_("Click to switch the original and filtered sides")); } else if (state & toggle_mask) { status = g_strdup (_("Click to switch between vertical and horizontal")); } else { status = pika_suggest_modifiers (_("Click to move the split guide"), (extend_mask | toggle_mask) & ~state, _("%s: switch original and filtered"), _("%s: switch horizontal and vertical"), NULL); } if (proximity) pika_tool_push_status (tool, display, "%s", status); g_free (status); } else { PIKA_TOOL_CLASS (parent_class)->oper_update (tool, coords, state, proximity, display); } } static void pika_filter_tool_cursor_update (PikaTool *tool, const PikaCoords *coords, GdkModifierType state, PikaDisplay *display) { PikaFilterTool *filter_tool = PIKA_FILTER_TOOL (tool); if (pika_filter_tool_on_guide (filter_tool, coords, display)) { pika_tool_set_cursor (tool, display, PIKA_CURSOR_MOUSE, PIKA_TOOL_CURSOR_HAND, PIKA_CURSOR_MODIFIER_MOVE); } else { PIKA_TOOL_CLASS (parent_class)->cursor_update (tool, coords, state, display); } } static void pika_filter_tool_options_notify (PikaTool *tool, PikaToolOptions *options, const GParamSpec *pspec) { PikaFilterTool *filter_tool = PIKA_FILTER_TOOL (tool); PikaFilterOptions *filter_options = PIKA_FILTER_OPTIONS (options); if (! strcmp (pspec->name, "preview") && filter_tool->filter) { pika_filter_tool_update_filter (filter_tool); if (filter_options->preview) { pika_drawable_filter_apply (filter_tool->filter, NULL); if (filter_options->preview_split) pika_filter_tool_add_guide (filter_tool); } else { if (filter_options->preview_split) pika_filter_tool_remove_guide (filter_tool); } } else if (! strcmp (pspec->name, "preview-split") && filter_tool->filter) { if (filter_options->preview_split) { PikaDisplayShell *shell = pika_display_get_shell (tool->display); PikaItem *item = PIKA_ITEM (tool->drawables->data); gint x, y, width, height; gint position; pika_display_shell_untransform_viewport (shell, TRUE, &x, &y, &width, &height); if (! pika_rectangle_intersect (pika_item_get_offset_x (item), pika_item_get_offset_y (item), pika_item_get_width (item), pika_item_get_height (item), x, y, width, height, &x, &y, &width, &height)) { x = pika_item_get_offset_x (item); y = pika_item_get_offset_y (item); width = pika_item_get_width (item); height = pika_item_get_height (item); } if (filter_options->preview_split_alignment == PIKA_ALIGN_LEFT || filter_options->preview_split_alignment == PIKA_ALIGN_RIGHT) { position = (x + width / 2) - pika_item_get_offset_x (item); } else { position = (y + height / 2) - pika_item_get_offset_y (item); } g_object_set ( options, "preview-split-position", position, NULL); } pika_filter_tool_update_filter (filter_tool); if (filter_options->preview_split) pika_filter_tool_add_guide (filter_tool); else pika_filter_tool_remove_guide (filter_tool); } else if (! strcmp (pspec->name, "preview-split-alignment") || ! strcmp (pspec->name, "preview-split-position")) { pika_filter_tool_update_filter (filter_tool); if (filter_options->preview_split) pika_filter_tool_move_guide (filter_tool); } else if (! strcmp (pspec->name, "controller") && filter_tool->widget) { pika_tool_widget_set_visible (filter_tool->widget, filter_options->controller); } } static gboolean pika_filter_tool_can_pick_color (PikaColorTool *color_tool, const PikaCoords *coords, PikaDisplay *display) { PikaTool *tool = PIKA_TOOL (color_tool); PikaFilterTool *filter_tool = PIKA_FILTER_TOOL (color_tool); PikaImage *image = pika_display_get_image (display); if (! pika_image_equal_selected_drawables (image, tool->drawables)) return FALSE; return filter_tool->pick_abyss || PIKA_COLOR_TOOL_CLASS (parent_class)->can_pick (color_tool, coords, display); } static gboolean pika_filter_tool_pick_color (PikaColorTool *color_tool, const PikaCoords *coords, PikaDisplay *display, const Babl **sample_format, gpointer pixel, PikaRGB *color) { PikaTool *tool = PIKA_TOOL (color_tool); PikaFilterTool *filter_tool = PIKA_FILTER_TOOL (color_tool); gboolean picked; g_return_val_if_fail (g_list_length (tool->drawables) == 1, FALSE); picked = PIKA_COLOR_TOOL_CLASS (parent_class)->pick (color_tool, coords, display, sample_format, pixel, color); if (! picked && filter_tool->pick_abyss) { color->r = 0.0; color->g = 0.0; color->b = 0.0; color->a = 0.0; picked = TRUE; } return picked; } static void pika_filter_tool_color_picked (PikaColorTool *color_tool, const PikaCoords *coords, PikaDisplay *display, PikaColorPickState pick_state, const Babl *sample_format, gpointer pixel, const PikaRGB *color) { PikaFilterTool *filter_tool = PIKA_FILTER_TOOL (color_tool); if (filter_tool->active_picker) { PikaPickerCallback callback; gpointer callback_data; callback = g_object_get_data (G_OBJECT (filter_tool->active_picker), "picker-callback"); callback_data = g_object_get_data (G_OBJECT (filter_tool->active_picker), "picker-callback-data"); if (callback) { callback (callback_data, filter_tool->pick_identifier, coords->x, coords->y, sample_format, color); return; } } PIKA_FILTER_TOOL_GET_CLASS (filter_tool)->color_picked (filter_tool, filter_tool->pick_identifier, coords->x, coords->y, sample_format, color); } static void pika_filter_tool_real_reset (PikaFilterTool *filter_tool) { if (filter_tool->config) { if (filter_tool->default_config) { pika_config_copy (PIKA_CONFIG (filter_tool->default_config), PIKA_CONFIG (filter_tool->config), 0); } else { pika_config_reset (PIKA_CONFIG (filter_tool->config)); } } } static void pika_filter_tool_real_set_config (PikaFilterTool *filter_tool, PikaConfig *config) { PikaFilterRegion region; /* copy the "pika-region" property first, to avoid incorrectly adjusting the * copied operation properties in pika_operation_tool_region_changed(). */ g_object_get (config, "pika-region", ®ion, NULL); g_object_set (filter_tool->config, "pika-region", region, NULL); pika_config_copy (PIKA_CONFIG (config), PIKA_CONFIG (filter_tool->config), 0); /* reset the "time" property, otherwise explicitly storing the * config as setting will also copy the time, and the stored object * will be considered to be among the automatically stored recently * used settings */ g_object_set (filter_tool->config, "time", (gint64) 0, NULL); } static void pika_filter_tool_real_config_notify (PikaFilterTool *filter_tool, PikaConfig *config, const GParamSpec *pspec) { PikaFilterOptions *options = PIKA_FILTER_TOOL_GET_OPTIONS (filter_tool); if (filter_tool->filter) { /* note that we may be called with a NULL pspec. see * pika_operation_tool_aux_input_notify(). */ if (pspec) { if (! strcmp (pspec->name, "pika-clip") || ! strcmp (pspec->name, "pika-mode") || ! strcmp (pspec->name, "pika-opacity") || ! strcmp (pspec->name, "pika-gamma-hack")) { pika_filter_tool_update_filter (filter_tool); } else if (! strcmp (pspec->name, "pika-region")) { pika_filter_tool_update_filter (filter_tool); pika_filter_tool_region_changed (filter_tool); } } if (options->preview) pika_drawable_filter_apply (filter_tool->filter, NULL); } } static void pika_filter_tool_halt (PikaFilterTool *filter_tool) { PikaTool *tool = PIKA_TOOL (filter_tool); pika_filter_tool_disable_color_picking (filter_tool); if (tool->display) { PikaImage *image = pika_display_get_image (tool->display); GList *iter; for (iter = tool->drawables; iter; iter = iter->next) g_signal_handlers_disconnect_by_func (iter->data, pika_filter_tool_lock_position_changed, filter_tool); g_signal_handlers_disconnect_by_func (image, pika_filter_tool_mask_changed, filter_tool); } if (filter_tool->gui) { /* explicitly clear the dialog contents first, since we might be called * during the dialog's delete event, in which case the dialog will be * externally reffed, and will only die *after* pika_filter_tool_halt() * returns, and, in particular, after filter_tool->config has been * cleared. we want to make sure the gui is destroyed while * filter_tool->config is still alive, since the gui's destruction may * fire signals whose handlers rely on it. */ pika_gtk_container_clear ( GTK_CONTAINER (pika_filter_tool_dialog_get_vbox (filter_tool))); g_clear_object (&filter_tool->gui); filter_tool->settings_box = NULL; filter_tool->controller_toggle = NULL; filter_tool->clip_combo = NULL; filter_tool->region_combo = NULL; } if (filter_tool->filter) { pika_drawable_filter_abort (filter_tool->filter); g_clear_object (&filter_tool->filter); pika_filter_tool_remove_guide (filter_tool); } g_clear_object (&filter_tool->operation); if (filter_tool->config) { g_signal_handlers_disconnect_by_func (filter_tool->config, pika_filter_tool_config_notify, filter_tool); g_signal_handlers_disconnect_by_func (filter_tool->config, pika_filter_tool_unset_setting, filter_tool); g_clear_object (&filter_tool->config); } g_clear_object (&filter_tool->default_config); g_clear_object (&filter_tool->settings); if (pika_draw_tool_is_active (PIKA_DRAW_TOOL (tool))) pika_draw_tool_stop (PIKA_DRAW_TOOL (tool)); pika_filter_tool_set_widget (filter_tool, NULL); tool->display = NULL; g_list_free (tool->drawables); tool->drawables = NULL; } static void pika_filter_tool_commit (PikaFilterTool *filter_tool) { PikaTool *tool = PIKA_TOOL (filter_tool); if (filter_tool->gui) pika_tool_gui_hide (filter_tool->gui); if (filter_tool->filter) { PikaFilterOptions *options = PIKA_FILTER_TOOL_GET_OPTIONS (tool); if (! options->preview) pika_drawable_filter_apply (filter_tool->filter, NULL); pika_tool_control_push_preserve (tool->control, TRUE); pika_drawable_filter_commit (filter_tool->filter, PIKA_PROGRESS (tool), TRUE); g_clear_object (&filter_tool->filter); pika_tool_control_pop_preserve (tool->control); pika_filter_tool_remove_guide (filter_tool); pika_image_flush (pika_display_get_image (tool->display)); if (filter_tool->config && filter_tool->has_settings) { PikaGuiConfig *config = PIKA_GUI_CONFIG (tool->tool_info->pika->config); pika_settings_box_add_current (PIKA_SETTINGS_BOX (filter_tool->settings_box), config->filter_tool_max_recent); } } } static void pika_filter_tool_dialog (PikaFilterTool *filter_tool) { PIKA_FILTER_TOOL_GET_CLASS (filter_tool)->dialog (filter_tool); } static void pika_filter_tool_update_dialog_operation_settings (PikaFilterTool *filter_tool) { PikaTool *tool = PIKA_TOOL (filter_tool); PikaFilterOptions *options = PIKA_FILTER_TOOL_GET_OPTIONS (filter_tool); PikaImage *image = pika_display_get_image (tool->display); if (filter_tool->operation_settings_box) { pika_gtk_container_clear ( GTK_CONTAINER (filter_tool->operation_settings_box)); if (filter_tool->config) { GtkWidget *vbox; GtkWidget *expander; GtkWidget *frame; GtkWidget *vbox2; GtkWidget *mode_box; GtkWidget *scale; GtkWidget *toggle; vbox = filter_tool->operation_settings_box; /* The clipping combo */ filter_tool->clip_combo = pika_prop_enum_combo_box_new (filter_tool->config, "pika-clip", PIKA_TRANSFORM_RESIZE_ADJUST, PIKA_TRANSFORM_RESIZE_CLIP); pika_int_combo_box_set_label ( PIKA_INT_COMBO_BOX (filter_tool->clip_combo), _("Clipping")); gtk_box_pack_start (GTK_BOX (vbox), filter_tool->clip_combo, FALSE, FALSE, 0); /* The region combo */ filter_tool->region_combo = pika_prop_enum_combo_box_new (filter_tool->config, "pika-region", 0, 0); gtk_box_pack_start (GTK_BOX (vbox), filter_tool->region_combo, FALSE, FALSE, 0); /* The blending-options expander */ expander = gtk_expander_new (_("Blending Options")); gtk_box_pack_start (GTK_BOX (vbox), expander, FALSE, FALSE, 0); gtk_widget_show (expander); g_object_bind_property (options, "blending-options-expanded", expander, "expanded", G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL); frame = pika_frame_new (NULL); gtk_container_add (GTK_CONTAINER (expander), frame); gtk_widget_show (frame); vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2); gtk_container_add (GTK_CONTAINER (frame), vbox2); gtk_widget_show (vbox2); /* The mode box */ mode_box = pika_prop_layer_mode_box_new ( filter_tool->config, "pika-mode", PIKA_LAYER_MODE_CONTEXT_FILTER); pika_layer_mode_box_set_label (PIKA_LAYER_MODE_BOX (mode_box), _("Mode")); gtk_box_pack_start (GTK_BOX (vbox2), mode_box, FALSE, FALSE, 0); gtk_widget_show (mode_box); /* The opacity scale */ scale = pika_prop_spin_scale_new (filter_tool->config, "pika-opacity", 1.0, 10.0, 1); pika_prop_widget_set_factor (scale, 100.0, 1.0, 10.0, 1); gtk_box_pack_start (GTK_BOX (vbox2), scale, FALSE, FALSE, 0); gtk_widget_show (scale); /* The Color Options expander */ expander = gtk_expander_new (_("Advanced Color Options")); gtk_box_pack_start (GTK_BOX (vbox), expander, FALSE, FALSE, 0); g_object_bind_property (image->pika->config, "filter-tool-show-color-options", expander, "visible", G_BINDING_SYNC_CREATE); g_object_bind_property (options, "color-options-expanded", expander, "expanded", G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL); frame = pika_frame_new (NULL); gtk_container_add (GTK_CONTAINER (expander), frame); gtk_widget_show (frame); vbox2 = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2); gtk_container_add (GTK_CONTAINER (frame), vbox2); gtk_widget_show (vbox2); /* The gamma hack toggle */ toggle = pika_prop_check_button_new (filter_tool->config, "pika-gamma-hack", NULL); gtk_box_pack_start (GTK_BOX (vbox2), toggle, FALSE, FALSE, 0); gtk_widget_show (toggle); } } } static void pika_filter_tool_reset (PikaFilterTool *filter_tool) { if (filter_tool->config) g_object_freeze_notify (filter_tool->config); PIKA_FILTER_TOOL_GET_CLASS (filter_tool)->reset (filter_tool); if (filter_tool->config) g_object_thaw_notify (filter_tool->config); if (filter_tool->widget) pika_filter_tool_reset_widget (filter_tool, filter_tool->widget); } static void pika_filter_tool_create_filter (PikaFilterTool *filter_tool) { PikaTool *tool = PIKA_TOOL (filter_tool); PikaFilterOptions *options = PIKA_FILTER_TOOL_GET_OPTIONS (filter_tool); if (filter_tool->filter) { pika_drawable_filter_abort (filter_tool->filter); g_object_unref (filter_tool->filter); } pika_assert (filter_tool->operation); g_return_if_fail (g_list_length (tool->drawables) == 1); filter_tool->filter = pika_drawable_filter_new (tool->drawables->data, pika_tool_get_undo_desc (tool), filter_tool->operation, pika_tool_get_icon_name (tool)); pika_filter_tool_update_filter (filter_tool); g_signal_connect (filter_tool->filter, "flush", G_CALLBACK (pika_filter_tool_flush), filter_tool); pika_gegl_progress_connect (filter_tool->operation, PIKA_PROGRESS (filter_tool), pika_tool_get_undo_desc (tool)); if (options->preview) pika_drawable_filter_apply (filter_tool->filter, NULL); } static void pika_filter_tool_update_dialog (PikaFilterTool *filter_tool) { PikaTool *tool = PIKA_TOOL (filter_tool); if (filter_tool->gui) { PikaImage *image = pika_display_get_image (tool->display); PikaChannel *mask = pika_image_get_mask (image); const Babl *format; g_return_if_fail (g_list_length (tool->drawables) == 1); if (filter_tool->filter) format = pika_drawable_filter_get_format (filter_tool->filter); else format = pika_drawable_get_format (tool->drawables->data); if (pika_channel_is_empty (mask)) { gtk_widget_set_visible ( filter_tool->clip_combo, pika_item_get_clip (PIKA_ITEM (tool->drawables->data), FALSE) == FALSE && ! pika_gegl_node_is_point_operation (filter_tool->operation) && babl_format_has_alpha (format)); gtk_widget_hide (filter_tool->region_combo); } else { gtk_widget_hide (filter_tool->clip_combo); gtk_widget_set_visible ( filter_tool->region_combo, ! pika_gegl_node_is_point_operation (filter_tool->operation) || pika_gegl_node_has_key (filter_tool->operation, "position-dependent")); } } } static void pika_filter_tool_region_changed (PikaFilterTool *filter_tool) { if (filter_tool->filter && PIKA_FILTER_TOOL_GET_CLASS (filter_tool)->region_changed) { PIKA_FILTER_TOOL_GET_CLASS (filter_tool)->region_changed (filter_tool); } } static void pika_filter_tool_flush (PikaDrawableFilter *filter, PikaFilterTool *filter_tool) { PikaTool *tool = PIKA_TOOL (filter_tool); PikaImage *image = pika_display_get_image (tool->display); pika_projection_flush (pika_image_get_projection (image)); } static void pika_filter_tool_config_notify (GObject *object, const GParamSpec *pspec, PikaFilterTool *filter_tool) { PIKA_FILTER_TOOL_GET_CLASS (filter_tool)->config_notify (filter_tool, PIKA_CONFIG (object), pspec); } static void pika_filter_tool_unset_setting (GObject *object, const GParamSpec *pspec, PikaFilterTool *filter_tool) { g_signal_handlers_disconnect_by_func (filter_tool->config, pika_filter_tool_unset_setting, filter_tool); pika_settings_box_unset (PIKA_SETTINGS_BOX (filter_tool->settings_box)); } static void pika_filter_tool_lock_position_changed (PikaDrawable *drawable, PikaFilterTool *filter_tool) { pika_filter_tool_update_dialog (filter_tool); } static void pika_filter_tool_mask_changed (PikaImage *image, PikaFilterTool *filter_tool) { PikaOperationSettings *settings; settings = PIKA_OPERATION_SETTINGS (filter_tool->config); pika_filter_tool_update_dialog (filter_tool); if (settings && settings->region == PIKA_FILTER_REGION_SELECTION) pika_filter_tool_region_changed (filter_tool); } static void pika_filter_tool_add_guide (PikaFilterTool *filter_tool) { PikaTool *tool = PIKA_TOOL (filter_tool); PikaFilterOptions *options = PIKA_FILTER_TOOL_GET_OPTIONS (filter_tool); PikaItem *item; PikaImage *image; PikaOrientationType orientation; gint position; g_return_if_fail (g_list_length (tool->drawables) == 1); if (filter_tool->preview_guide) return; item = PIKA_ITEM (tool->drawables->data); image = pika_item_get_image (item); if (options->preview_split_alignment == PIKA_ALIGN_LEFT || options->preview_split_alignment == PIKA_ALIGN_RIGHT) { orientation = PIKA_ORIENTATION_VERTICAL; position = pika_item_get_offset_x (item) + options->preview_split_position; } else { orientation = PIKA_ORIENTATION_HORIZONTAL; position = pika_item_get_offset_y (item) + options->preview_split_position; } filter_tool->preview_guide = pika_guide_custom_new (orientation, image->pika->next_guide_id++, PIKA_GUIDE_STYLE_SPLIT_VIEW); pika_image_add_guide (image, filter_tool->preview_guide, position); g_signal_connect (filter_tool->preview_guide, "removed", G_CALLBACK (pika_filter_tool_guide_removed), filter_tool); g_signal_connect (filter_tool->preview_guide, "notify::position", G_CALLBACK (pika_filter_tool_guide_moved), filter_tool); } static void pika_filter_tool_remove_guide (PikaFilterTool *filter_tool) { PikaTool *tool = PIKA_TOOL (filter_tool); PikaImage *image; if (! filter_tool->preview_guide) return; image = pika_item_get_image (PIKA_ITEM (tool->drawables->data)); pika_image_remove_guide (image, filter_tool->preview_guide, FALSE); } static void pika_filter_tool_move_guide (PikaFilterTool *filter_tool) { PikaTool *tool = PIKA_TOOL (filter_tool); PikaFilterOptions *options = PIKA_FILTER_TOOL_GET_OPTIONS (filter_tool); PikaItem *item; PikaOrientationType orientation; gint position; g_return_if_fail (g_list_length (tool->drawables) == 1); if (! filter_tool->preview_guide) return; item = PIKA_ITEM (tool->drawables->data); if (options->preview_split_alignment == PIKA_ALIGN_LEFT || options->preview_split_alignment == PIKA_ALIGN_RIGHT) { orientation = PIKA_ORIENTATION_VERTICAL; position = pika_item_get_offset_x (item) + options->preview_split_position; } else { orientation = PIKA_ORIENTATION_HORIZONTAL; position = pika_item_get_offset_x (item) + options->preview_split_position; } if (orientation != pika_guide_get_orientation (filter_tool->preview_guide) || position != pika_guide_get_position (filter_tool->preview_guide)) { pika_guide_set_orientation (filter_tool->preview_guide, orientation); pika_image_move_guide (pika_item_get_image (item), filter_tool->preview_guide, position, FALSE); } } static void pika_filter_tool_guide_removed (PikaGuide *guide, PikaFilterTool *filter_tool) { PikaFilterOptions *options = PIKA_FILTER_TOOL_GET_OPTIONS (filter_tool); g_signal_handlers_disconnect_by_func (G_OBJECT (filter_tool->preview_guide), pika_filter_tool_guide_removed, filter_tool); g_signal_handlers_disconnect_by_func (G_OBJECT (filter_tool->preview_guide), pika_filter_tool_guide_moved, filter_tool); g_clear_object (&filter_tool->preview_guide); g_object_set (options, "preview-split", FALSE, NULL); } static void pika_filter_tool_guide_moved (PikaGuide *guide, const GParamSpec *pspec, PikaFilterTool *filter_tool) { PikaTool *tool = PIKA_TOOL (filter_tool); PikaFilterOptions *options = PIKA_FILTER_TOOL_GET_OPTIONS (filter_tool); PikaItem *item; gint position; g_return_if_fail (g_list_length (tool->drawables) == 1); item = PIKA_ITEM (tool->drawables->data); if (options->preview_split_alignment == PIKA_ALIGN_LEFT || options->preview_split_alignment == PIKA_ALIGN_RIGHT) { position = CLAMP (pika_guide_get_position (guide) - pika_item_get_offset_x (item), 0, pika_item_get_width (item)); } else { position = CLAMP (pika_guide_get_position (guide) - pika_item_get_offset_y (item), 0, pika_item_get_height (item)); } g_object_set (options, "preview-split-position", position, NULL); } static void pika_filter_tool_response (PikaToolGui *gui, gint response_id, PikaFilterTool *filter_tool) { PikaTool *tool = PIKA_TOOL (filter_tool); switch (response_id) { case RESPONSE_RESET: pika_filter_tool_reset (filter_tool); break; case GTK_RESPONSE_OK: pika_tool_control (tool, PIKA_TOOL_ACTION_COMMIT, tool->display); break; default: pika_tool_control (tool, PIKA_TOOL_ACTION_HALT, tool->display); break; } } static void pika_filter_tool_update_filter (PikaFilterTool *filter_tool) { PikaFilterOptions *options = PIKA_FILTER_TOOL_GET_OPTIONS (filter_tool); PikaOperationSettings *settings; if (! filter_tool->filter) return; settings = PIKA_OPERATION_SETTINGS (filter_tool->config); pika_drawable_filter_set_preview (filter_tool->filter, options->preview); pika_drawable_filter_set_preview_split (filter_tool->filter, options->preview_split, options->preview_split_alignment, options->preview_split_position); pika_drawable_filter_set_add_alpha (filter_tool->filter, pika_gegl_node_has_key ( filter_tool->operation, "needs-alpha")); pika_operation_settings_sync_drawable_filter (settings, filter_tool->filter); } static void pika_filter_tool_set_has_settings (PikaFilterTool *filter_tool, gboolean has_settings) { g_return_if_fail (PIKA_IS_FILTER_TOOL (filter_tool)); filter_tool->has_settings = has_settings; if (! filter_tool->settings_box) return; if (filter_tool->has_settings) { PikaTool *tool = PIKA_TOOL (filter_tool); GQuark quark = g_quark_from_static_string ("settings-folder"); GType type = G_TYPE_FROM_INSTANCE (filter_tool->config); GFile *settings_folder; gchar *import_title; gchar *export_title; settings_folder = g_type_get_qdata (type, quark); import_title = g_strdup_printf (_("Import '%s' Settings"), pika_tool_get_label (tool)); export_title = g_strdup_printf (_("Export '%s' Settings"), pika_tool_get_label (tool)); g_object_set (filter_tool->settings_box, "visible", TRUE, "config", filter_tool->config, "container", filter_tool->settings, "help-id", pika_tool_get_help_id (tool), "import-title", import_title, "export-title", export_title, "default-folder", settings_folder, "last-file", NULL, NULL); g_free (import_title); g_free (export_title); } else { g_object_set (filter_tool->settings_box, "visible", FALSE, "config", NULL, "container", NULL, "help-id", NULL, "import-title", NULL, "export-title", NULL, "default-folder", NULL, "last-file", NULL, NULL); } } /* public functions */ void pika_filter_tool_get_operation (PikaFilterTool *filter_tool) { PikaTool *tool; PikaFilterToolClass *klass; gchar *operation_name; GParamSpec **pspecs; g_return_if_fail (PIKA_IS_FILTER_TOOL (filter_tool)); tool = PIKA_TOOL (filter_tool); klass = PIKA_FILTER_TOOL_GET_CLASS (filter_tool); if (filter_tool->filter) { pika_drawable_filter_abort (filter_tool->filter); g_clear_object (&filter_tool->filter); pika_filter_tool_remove_guide (filter_tool); } g_clear_object (&filter_tool->operation); if (filter_tool->config) { g_signal_handlers_disconnect_by_func (filter_tool->config, pika_filter_tool_config_notify, filter_tool); g_signal_handlers_disconnect_by_func (filter_tool->config, pika_filter_tool_unset_setting, filter_tool); g_clear_object (&filter_tool->config); } g_clear_object (&filter_tool->default_config); g_clear_object (&filter_tool->settings); g_clear_pointer (&filter_tool->description, g_free); operation_name = klass->get_operation (filter_tool, &filter_tool->description); if (! operation_name) operation_name = g_strdup ("gegl:nop"); if (! filter_tool->description) filter_tool->description = g_strdup (pika_tool_get_label (tool)); filter_tool->operation = gegl_node_new_child (NULL, "operation", operation_name, NULL); filter_tool->config = g_object_new (pika_operation_config_get_type (tool->tool_info->pika, operation_name, pika_tool_get_icon_name (tool), PIKA_TYPE_OPERATION_SETTINGS), NULL); pika_operation_config_sync_node (filter_tool->config, filter_tool->operation); pika_operation_config_connect_node (filter_tool->config, filter_tool->operation); filter_tool->settings = pika_operation_config_get_container (tool->tool_info->pika, G_TYPE_FROM_INSTANCE (filter_tool->config), (GCompareFunc) pika_settings_compare); g_object_ref (filter_tool->settings); pspecs = pika_operation_config_list_properties (filter_tool->config, G_TYPE_FROM_INSTANCE (filter_tool->config), 0, NULL); pika_filter_tool_set_has_settings (filter_tool, (pspecs != NULL)); g_free (pspecs); if (filter_tool->gui) { pika_tool_gui_set_title (filter_tool->gui, pika_tool_get_label (tool)); pika_tool_gui_set_description (filter_tool->gui, filter_tool->description); pika_tool_gui_set_icon_name (filter_tool->gui, pika_tool_get_icon_name (tool)); pika_tool_gui_set_help_id (filter_tool->gui, pika_tool_get_help_id (tool)); pika_filter_tool_update_dialog_operation_settings (filter_tool); } pika_filter_tool_update_dialog (filter_tool); g_free (operation_name); g_object_set (PIKA_FILTER_TOOL_GET_OPTIONS (filter_tool), "preview-split", FALSE, NULL); g_signal_connect_object (filter_tool->config, "notify", G_CALLBACK (pika_filter_tool_config_notify), G_OBJECT (filter_tool), 0); if (tool->drawables) pika_filter_tool_create_filter (filter_tool); } void pika_filter_tool_set_config (PikaFilterTool *filter_tool, PikaConfig *config) { g_return_if_fail (PIKA_IS_FILTER_TOOL (filter_tool)); g_return_if_fail (PIKA_IS_OPERATION_SETTINGS (config)); /* if the user didn't change a setting since the last set_config(), * this handler is still connected */ g_signal_handlers_disconnect_by_func (filter_tool->config, pika_filter_tool_unset_setting, filter_tool); PIKA_FILTER_TOOL_GET_CLASS (filter_tool)->set_config (filter_tool, config); if (filter_tool->widget) pika_filter_tool_reset_widget (filter_tool, filter_tool->widget); if (filter_tool->settings_box) g_signal_connect_object (filter_tool->config, "notify", G_CALLBACK (pika_filter_tool_unset_setting), G_OBJECT (filter_tool), 0); } void pika_filter_tool_edit_as (PikaFilterTool *filter_tool, const gchar *new_tool_id, PikaConfig *config) { PikaDisplay *display; PikaContext *user_context; PikaToolInfo *tool_info; PikaTool *new_tool; g_return_if_fail (PIKA_IS_FILTER_TOOL (filter_tool)); g_return_if_fail (new_tool_id != NULL); g_return_if_fail (PIKA_IS_CONFIG (config)); display = PIKA_TOOL (filter_tool)->display; user_context = pika_get_user_context (display->pika); tool_info = (PikaToolInfo *) pika_container_get_child_by_name (display->pika->tool_info_list, new_tool_id); pika_tool_control (PIKA_TOOL (filter_tool), PIKA_TOOL_ACTION_HALT, display); pika_context_set_tool (user_context, tool_info); tool_manager_initialize_active (display->pika, display); new_tool = tool_manager_get_active (display->pika); PIKA_FILTER_TOOL (new_tool)->default_config = g_object_ref (G_OBJECT (config)); pika_filter_tool_reset (PIKA_FILTER_TOOL (new_tool)); } gboolean pika_filter_tool_on_guide (PikaFilterTool *filter_tool, const PikaCoords *coords, PikaDisplay *display) { PikaDisplayShell *shell; g_return_val_if_fail (PIKA_IS_FILTER_TOOL (filter_tool), FALSE); g_return_val_if_fail (coords != NULL, FALSE); g_return_val_if_fail (PIKA_IS_DISPLAY (display), FALSE); shell = pika_display_get_shell (display); if (filter_tool->filter && filter_tool->preview_guide && pika_display_shell_get_show_guides (shell)) { const gint snap_distance = display->config->snap_distance; PikaOrientationType orientation; gint position; orientation = pika_guide_get_orientation (filter_tool->preview_guide); position = pika_guide_get_position (filter_tool->preview_guide); if (orientation == PIKA_ORIENTATION_HORIZONTAL) { if (fabs (coords->y - position) <= FUNSCALEY (shell, snap_distance)) return TRUE; } else { if (fabs (coords->x - position) <= FUNSCALEX (shell, snap_distance)) return TRUE; } } return FALSE; } GtkWidget * pika_filter_tool_dialog_get_vbox (PikaFilterTool *filter_tool) { g_return_val_if_fail (PIKA_IS_FILTER_TOOL (filter_tool), NULL); return pika_tool_gui_get_vbox (filter_tool->gui); } void pika_filter_tool_enable_color_picking (PikaFilterTool *filter_tool, gpointer identifier, gboolean pick_abyss) { g_return_if_fail (PIKA_IS_FILTER_TOOL (filter_tool)); pika_filter_tool_disable_color_picking (filter_tool); /* note that ownership over 'identifier' is not transferred, and its * lifetime should be managed by the caller. */ filter_tool->pick_identifier = identifier; filter_tool->pick_abyss = pick_abyss; pika_color_tool_enable (PIKA_COLOR_TOOL (filter_tool), PIKA_COLOR_TOOL_GET_OPTIONS (filter_tool)); } void pika_filter_tool_disable_color_picking (PikaFilterTool *filter_tool) { g_return_if_fail (PIKA_IS_FILTER_TOOL (filter_tool)); if (filter_tool->active_picker) { GtkToggleButton *toggle = GTK_TOGGLE_BUTTON (filter_tool->active_picker); filter_tool->active_picker = NULL; gtk_toggle_button_set_active (toggle, FALSE); } if (pika_color_tool_is_enabled (PIKA_COLOR_TOOL (filter_tool))) pika_color_tool_disable (PIKA_COLOR_TOOL (filter_tool)); } static void pika_filter_tool_color_picker_toggled (GtkWidget *widget, PikaFilterTool *filter_tool) { if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) { gpointer identifier; gboolean pick_abyss; if (filter_tool->active_picker == widget) return; identifier = g_object_get_data (G_OBJECT (widget), "picker-identifier"); pick_abyss = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (widget), "picker-pick-abyss")); pika_filter_tool_enable_color_picking (filter_tool, identifier, pick_abyss); filter_tool->active_picker = widget; } else if (filter_tool->active_picker == widget) { pika_filter_tool_disable_color_picking (filter_tool); } } GtkWidget * pika_filter_tool_add_color_picker (PikaFilterTool *filter_tool, gpointer identifier, const gchar *icon_name, const gchar *tooltip, gboolean pick_abyss, PikaPickerCallback callback, gpointer callback_data) { GtkWidget *button; GtkWidget *image; g_return_val_if_fail (PIKA_IS_FILTER_TOOL (filter_tool), NULL); g_return_val_if_fail (icon_name != NULL, NULL); button = g_object_new (GTK_TYPE_TOGGLE_BUTTON, "draw-indicator", FALSE, NULL); image = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_BUTTON); g_object_set (image, "margin-start", 2, "margin-end", 2, "margin-top", 2, "margin-bottom", 2, NULL); gtk_container_add (GTK_CONTAINER (button), image); gtk_widget_show (image); if (tooltip) pika_help_set_help_data (button, tooltip, NULL); g_object_set_data (G_OBJECT (button), "picker-identifier", identifier); g_object_set_data (G_OBJECT (button), "picker-pick-abyss", GINT_TO_POINTER (pick_abyss)); g_object_set_data (G_OBJECT (button), "picker-callback", callback); g_object_set_data (G_OBJECT (button), "picker-callback-data", callback_data); g_signal_connect (button, "toggled", G_CALLBACK (pika_filter_tool_color_picker_toggled), filter_tool); return button; } GCallback pika_filter_tool_add_controller (PikaFilterTool *filter_tool, PikaControllerType controller_type, const gchar *status_title, GCallback callback, gpointer callback_data, gpointer *set_func_data) { PikaToolWidget *widget; GCallback set_func; g_return_val_if_fail (PIKA_IS_FILTER_TOOL (filter_tool), NULL); g_return_val_if_fail (callback != NULL, NULL); g_return_val_if_fail (callback_data != NULL, NULL); g_return_val_if_fail (set_func_data != NULL, NULL); widget = pika_filter_tool_create_widget (filter_tool, controller_type, status_title, callback, callback_data, &set_func, set_func_data); pika_filter_tool_set_widget (filter_tool, widget); g_object_unref (widget); return set_func; } void pika_filter_tool_set_widget (PikaFilterTool *filter_tool, PikaToolWidget *widget) { g_return_if_fail (PIKA_IS_FILTER_TOOL (filter_tool)); g_return_if_fail (widget == NULL || PIKA_IS_TOOL_WIDGET (widget)); if (widget == filter_tool->widget) return; if (filter_tool->widget) { if (pika_draw_tool_is_active (PIKA_DRAW_TOOL (filter_tool))) pika_draw_tool_stop (PIKA_DRAW_TOOL (filter_tool)); g_object_unref (filter_tool->widget); } filter_tool->widget = widget; pika_draw_tool_set_widget (PIKA_DRAW_TOOL (filter_tool), widget); if (filter_tool->widget) { PikaFilterOptions *options = PIKA_FILTER_TOOL_GET_OPTIONS (filter_tool); g_object_ref (filter_tool->widget); pika_tool_widget_set_visible (filter_tool->widget, options->controller); if (PIKA_TOOL (filter_tool)->display) pika_draw_tool_start (PIKA_DRAW_TOOL (filter_tool), PIKA_TOOL (filter_tool)->display); } if (filter_tool->controller_toggle) { gtk_widget_set_visible (filter_tool->controller_toggle, filter_tool->widget != NULL); } } gboolean pika_filter_tool_get_drawable_area (PikaFilterTool *filter_tool, gint *drawable_offset_x, gint *drawable_offset_y, GeglRectangle *drawable_area) { PikaTool *tool; PikaOperationSettings *settings; PikaDrawable *drawable; g_return_val_if_fail (PIKA_IS_FILTER_TOOL (filter_tool), FALSE); g_return_val_if_fail (drawable_offset_x != NULL, FALSE); g_return_val_if_fail (drawable_offset_y != NULL, FALSE); g_return_val_if_fail (drawable_area != NULL, FALSE); tool = PIKA_TOOL (filter_tool); settings = PIKA_OPERATION_SETTINGS (filter_tool->config); g_return_val_if_fail (g_list_length (tool->drawables) == 1, FALSE); *drawable_offset_x = 0; *drawable_offset_y = 0; drawable_area->x = 0; drawable_area->y = 0; drawable_area->width = 1; drawable_area->height = 1; drawable = tool->drawables->data; if (drawable && settings) { pika_item_get_offset (PIKA_ITEM (drawable), drawable_offset_x, drawable_offset_y); switch (settings->region) { case PIKA_FILTER_REGION_SELECTION: if (! pika_item_mask_intersect (PIKA_ITEM (drawable), &drawable_area->x, &drawable_area->y, &drawable_area->width, &drawable_area->height)) { drawable_area->x = 0; drawable_area->y = 0; drawable_area->width = 1; drawable_area->height = 1; } break; case PIKA_FILTER_REGION_DRAWABLE: drawable_area->width = pika_item_get_width (PIKA_ITEM (drawable)); drawable_area->height = pika_item_get_height (PIKA_ITEM (drawable)); break; } return TRUE; } return FALSE; }