/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include #include "libpikamath/pikamath.h" #include "libpikabase/pikabase.h" #include "libpikacolor/pikacolor.h" #include "libpikawidgets/pikawidgets.h" #include "actions-types.h" #include "config/pikadialogconfig.h" #include "operations/layer-modes/pika-layer-modes.h" #include "core/pika.h" #include "core/pikachannel.h" #include "core/pikachannel-combine.h" #include "core/pikacontainer.h" #include "core/pikacontext.h" #include "core/pikadrawable-fill.h" #include "core/pikagrouplayer.h" #include "core/pikaimage.h" #include "core/pikaimage-merge.h" #include "core/pikaimage-undo.h" #include "core/pikaimage-undo-push.h" #include "core/pikaitemundo.h" #include "core/pikalayerpropundo.h" #include "core/pikalayer-floating-selection.h" #include "core/pikalayer-new.h" #include "core/pikapickable.h" #include "core/pikapickable-auto-shrink.h" #include "core/pikatoolinfo.h" #include "core/pikaundostack.h" #include "core/pikaprogress.h" #include "text/pikatext.h" #include "text/pikatext-vectors.h" #include "text/pikatextlayer.h" #include "vectors/pikastroke.h" #include "vectors/pikavectors.h" #include "vectors/pikavectors-warp.h" #include "widgets/pikaaction.h" #include "widgets/pikadock.h" #include "widgets/pikahelp-ids.h" #include "widgets/pikaprogressdialog.h" #include "display/pikadisplay.h" #include "display/pikadisplayshell.h" #include "display/pikaimagewindow.h" #include "tools/pikatexttool.h" #include "tools/tool_manager.h" #include "dialogs/dialogs.h" #include "dialogs/layer-add-mask-dialog.h" #include "dialogs/layer-options-dialog.h" #include "dialogs/resize-dialog.h" #include "dialogs/scale-dialog.h" #include "actions.h" #include "items-commands.h" #include "layers-commands.h" #include "pika-intl.h" /* local function prototypes */ static void layers_new_callback (GtkWidget *dialog, PikaImage *image, PikaLayer *layer, PikaContext *context, const gchar *layer_name, PikaLayerMode layer_mode, PikaLayerColorSpace layer_blend_space, PikaLayerColorSpace layer_composite_space, PikaLayerCompositeMode layer_composite_mode, gdouble layer_opacity, PikaFillType layer_fill_type, gint layer_width, gint layer_height, gint layer_offset_x, gint layer_offset_y, gboolean layer_visible, PikaColorTag layer_color_tag, gboolean layer_lock_pixels, gboolean layer_lock_position, gboolean layer_lock_visibility, gboolean layer_lock_alpha, gboolean rename_text_layer, gpointer user_data); static void layers_edit_attributes_callback (GtkWidget *dialog, PikaImage *image, PikaLayer *layer, PikaContext *context, const gchar *layer_name, PikaLayerMode layer_mode, PikaLayerColorSpace layer_blend_space, PikaLayerColorSpace layer_composite_space, PikaLayerCompositeMode layer_composite_mode, gdouble layer_opacity, PikaFillType layer_fill_type, gint layer_width, gint layer_height, gint layer_offset_x, gint layer_offset_y, gboolean layer_visible, PikaColorTag layer_color_tag, gboolean layer_lock_pixels, gboolean layer_lock_position, gboolean layer_lock_visibility, gboolean layer_lock_alpha, gboolean rename_text_layer, gpointer user_data); static void layers_add_mask_callback (GtkWidget *dialog, GList *layers, PikaAddMaskType add_mask_type, PikaChannel *channel, gboolean invert, gpointer user_data); static void layers_scale_callback (GtkWidget *dialog, PikaViewable *viewable, gint width, gint height, PikaUnit unit, PikaInterpolationType interpolation, gdouble xresolution, gdouble yresolution, PikaUnit resolution_unit, gpointer user_data); static void layers_resize_callback (GtkWidget *dialog, PikaViewable *viewable, PikaContext *context, gint width, gint height, PikaUnit unit, gint offset_x, gint offset_y, gdouble unused0, gdouble unused1, PikaUnit unused2, PikaFillType fill_type, PikaItemSet unused3, gboolean unused4, gpointer data); static gint layers_mode_index (PikaLayerMode layer_mode, const PikaLayerMode *modes, gint n_modes); /* private variables */ static PikaUnit layer_resize_unit = PIKA_UNIT_PIXEL; static PikaUnit layer_scale_unit = PIKA_UNIT_PIXEL; static PikaInterpolationType layer_scale_interp = -1; /* public functions */ void layers_edit_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; GtkWidget *widget; return_if_no_layers (image, layers, data); return_if_no_widget (widget, data); if (g_list_length (layers) != 1) return; if (pika_item_is_text_layer (PIKA_ITEM (layers->data))) { layers_edit_text_cmd_callback (action, value, data); } else { layers_edit_attributes_cmd_callback (action, value, data); } } void layers_edit_text_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; PikaLayer *layer; GList *layers; GtkWidget *widget; PikaTool *active_tool; return_if_no_layers (image, layers, data); return_if_no_widget (widget, data); if (g_list_length (layers) != 1) return; layer = layers->data; g_return_if_fail (pika_item_is_text_layer (PIKA_ITEM (layer))); active_tool = tool_manager_get_active (image->pika); if (! PIKA_IS_TEXT_TOOL (active_tool)) { PikaToolInfo *tool_info = pika_get_tool_info (image->pika, "pika-text-tool"); if (PIKA_IS_TOOL_INFO (tool_info)) { pika_context_set_tool (action_data_get_context (data), tool_info); active_tool = tool_manager_get_active (image->pika); } } if (PIKA_IS_TEXT_TOOL (active_tool)) { if (pika_text_tool_set_layer (PIKA_TEXT_TOOL (active_tool), layer)) { PikaDisplayShell *shell; shell = pika_display_get_shell (active_tool->display); gtk_widget_grab_focus (shell->canvas); } } } void layers_edit_attributes_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; PikaLayer *layer; GList *layers; GtkWidget *widget; GtkWidget *dialog; return_if_no_layers (image, layers, data); return_if_no_widget (widget, data); if (g_list_length (layers) != 1) return; layer = layers->data; #define EDIT_DIALOG_KEY "pika-layer-edit-attributes-dialog" dialog = dialogs_get_dialog (G_OBJECT (layer), EDIT_DIALOG_KEY); if (! dialog) { PikaItem *item = PIKA_ITEM (layer); dialog = layer_options_dialog_new (pika_item_get_image (PIKA_ITEM (layer)), layer, action_data_get_context (data), widget, _("Layer Attributes"), "pika-layer-edit", PIKA_ICON_EDIT, _("Edit Layer Attributes"), PIKA_HELP_LAYER_EDIT, pika_object_get_name (layer), pika_layer_get_mode (layer), pika_layer_get_blend_space (layer), pika_layer_get_composite_space (layer), pika_layer_get_composite_mode (layer), pika_layer_get_opacity (layer), 0 /* unused */, pika_item_get_visible (item), pika_item_get_color_tag (item), pika_item_get_lock_content (item), pika_item_get_lock_position (item), pika_item_get_lock_visibility (item), pika_layer_get_lock_alpha (layer), layers_edit_attributes_callback, NULL); dialogs_attach_dialog (G_OBJECT (layer), EDIT_DIALOG_KEY, dialog); } gtk_window_present (GTK_WINDOW (dialog)); } void layers_new_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GtkWidget *widget; PikaLayer *floating_sel; GtkWidget *dialog; return_if_no_image (image, data); return_if_no_widget (widget, data); /* If there is a floating selection, the new command transforms * the current fs into a new layer */ if ((floating_sel = pika_image_get_floating_selection (image))) { GError *error = NULL; if (! floating_sel_to_layer (floating_sel, &error)) { pika_message_literal (image->pika, G_OBJECT (widget), PIKA_MESSAGE_WARNING, error->message); g_clear_error (&error); return; } pika_image_flush (image); return; } #define NEW_DIALOG_KEY "pika-layer-new-dialog" dialog = dialogs_get_dialog (G_OBJECT (image), NEW_DIALOG_KEY); if (! dialog) { PikaDialogConfig *config = PIKA_DIALOG_CONFIG (image->pika->config); const gchar *title; gchar *desc; gint n_layers; PikaLayerMode layer_mode = config->layer_new_mode; n_layers = g_list_length (pika_image_get_selected_layers (image)); title = ngettext ("New Layer", "New Layers", n_layers > 0 ? n_layers : 1); desc = ngettext ("Create a New Layer", "Create %d New Layers", n_layers > 0 ? n_layers : 1); desc = g_strdup_printf (desc, n_layers > 0 ? n_layers : 1); if (layer_mode == PIKA_LAYER_MODE_NORMAL || layer_mode == PIKA_LAYER_MODE_NORMAL_LEGACY) { layer_mode = pika_image_get_default_new_layer_mode (image); } dialog = layer_options_dialog_new (image, NULL, action_data_get_context (data), widget, title, "pika-layer-new", PIKA_ICON_LAYER, desc, PIKA_HELP_LAYER_NEW, config->layer_new_name, layer_mode, config->layer_new_blend_space, config->layer_new_composite_space, config->layer_new_composite_mode, config->layer_new_opacity, config->layer_new_fill_type, TRUE, PIKA_COLOR_TAG_NONE, FALSE, FALSE, FALSE, FALSE, layers_new_callback, NULL); g_free (desc); dialogs_attach_dialog (G_OBJECT (image), NEW_DIALOG_KEY, dialog); } gtk_window_present (GTK_WINDOW (dialog)); } void layers_new_last_vals_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GtkWidget *widget; PikaLayer *layer; PikaDialogConfig *config; GList *layers; GList *new_layers = NULL; GList *iter; PikaLayerMode layer_mode; gint n_layers; gboolean run_once; return_if_no_image (image, data); return_if_no_widget (widget, data); config = PIKA_DIALOG_CONFIG (image->pika->config); /* If there is a floating selection, the new command transforms * the current fs into a new layer */ if (pika_image_get_floating_selection (image)) { layers_new_cmd_callback (action, value, data); return; } layer_mode = config->layer_new_mode; if (layer_mode == PIKA_LAYER_MODE_NORMAL || layer_mode == PIKA_LAYER_MODE_NORMAL_LEGACY) { layer_mode = pika_image_get_default_new_layer_mode (image); } layers = pika_image_get_selected_layers (image); layers = g_list_copy (layers); n_layers = g_list_length (layers); run_once = (n_layers == 0); pika_image_undo_group_start (image, PIKA_UNDO_GROUP_LAYER_ADD, ngettext ("New layer", "New layers", n_layers > 0 ? n_layers : 1)); for (iter = layers; iter || run_once ; iter = iter ? iter->next : NULL) { PikaLayer *parent; gint position; run_once = FALSE; if (iter) { if (pika_viewable_get_children (PIKA_VIEWABLE (iter->data))) { parent = iter->data; position = 0; } else { parent = PIKA_LAYER (pika_item_get_parent (iter->data)); position = pika_item_get_index (iter->data); } } else /* run_once */ { parent = NULL; position = -1; } layer = pika_layer_new (image, pika_image_get_width (image), pika_image_get_height (image), pika_image_get_layer_format (image, TRUE), config->layer_new_name, config->layer_new_opacity, layer_mode); pika_drawable_fill (PIKA_DRAWABLE (layer), action_data_get_context (data), config->layer_new_fill_type); pika_layer_set_blend_space (layer, config->layer_new_blend_space, FALSE); pika_layer_set_composite_space (layer, config->layer_new_composite_space, FALSE); pika_layer_set_composite_mode (layer, config->layer_new_composite_mode, FALSE); pika_image_add_layer (image, layer, parent, position, TRUE); new_layers = g_list_prepend (new_layers, layer); } pika_image_set_selected_layers (image, new_layers); pika_image_undo_group_end (image); g_list_free (layers); g_list_free (new_layers); pika_image_flush (image); } void layers_new_from_visible_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; PikaDisplayShell *shell; PikaLayer *layer; PikaPickable *pickable; PikaColorProfile *profile; return_if_no_image (image, data); return_if_no_shell (shell, data); pickable = pika_display_shell_get_canvas_pickable (shell); pika_pickable_flush (pickable); profile = pika_color_managed_get_color_profile (PIKA_COLOR_MANAGED (image)); layer = pika_layer_new_from_gegl_buffer (pika_pickable_get_buffer (pickable), image, pika_image_get_layer_format (image, TRUE), _("Visible"), PIKA_OPACITY_OPAQUE, pika_image_get_default_new_layer_mode (image), profile); pika_image_add_layer (image, layer, PIKA_IMAGE_ACTIVE_PARENT, -1, TRUE); pika_image_flush (image); } void layers_new_group_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *new_layers = NULL; GList *layers; GList *iter; gint n_layers; gboolean run_once; return_if_no_image (image, data); layers = pika_image_get_selected_layers (image); layers = g_list_copy (layers); n_layers = g_list_length (layers); run_once = (n_layers == 0); pika_image_undo_group_start (image, PIKA_UNDO_GROUP_LAYER_ADD, ngettext ("New layer group", "New layer groups", n_layers > 0 ? n_layers : 1)); for (iter = layers; iter || run_once ; iter = iter ? iter->next : NULL) { PikaLayer *layer; PikaLayer *parent; gint position; run_once = FALSE; if (iter) { if (pika_viewable_get_children (PIKA_VIEWABLE (iter->data))) { parent = iter->data; position = 0; } else { parent = PIKA_LAYER (pika_item_get_parent (iter->data)); position = pika_item_get_index (iter->data); } } else /* run_once */ { parent = NULL; position = -1; } layer = pika_group_layer_new (image); pika_image_add_layer (image, layer, parent, position, TRUE); new_layers = g_list_prepend (new_layers, layer); } pika_image_set_selected_layers (image, new_layers); pika_image_undo_group_end (image); pika_image_flush (image); g_list_free (layers); g_list_free (new_layers); } void layers_select_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *new_layers = NULL; GList *layers; GList *iter; PikaActionSelectType select_type; gboolean run_once; return_if_no_image (image, data); select_type = (PikaActionSelectType) g_variant_get_int32 (value); layers = pika_image_get_selected_layers (image); run_once = (g_list_length (layers) == 0); for (iter = layers; iter || run_once; iter = iter ? iter->next : NULL) { PikaLayer *new_layer; PikaContainer *container; if (iter) { container = pika_item_get_container (PIKA_ITEM (iter->data)); } else /* run_once */ { container = pika_image_get_layers (image); run_once = FALSE; } new_layer = (PikaLayer *) action_select_object (select_type, container, iter ? iter->data : NULL); if (new_layer) new_layers = g_list_prepend (new_layers, new_layer); } if (new_layers) { pika_image_set_selected_layers (image, new_layers); pika_image_flush (image); } g_list_free (new_layers); } void layers_raise_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; GList *iter; GList *raised_layers = NULL; return_if_no_layers (image, layers, data); for (iter = layers; iter; iter = iter->next) { gint index; index = pika_item_get_index (iter->data); if (index > 0) raised_layers = g_list_prepend (raised_layers, iter->data); } pika_image_undo_group_start (image, PIKA_UNDO_GROUP_ITEM_DISPLACE, ngettext ("Raise Layer", "Raise Layers", g_list_length (raised_layers))); for (iter = raised_layers; iter; iter = iter->next) pika_image_raise_item (image, iter->data, NULL); pika_image_flush (image); pika_image_undo_group_end (image); g_list_free (raised_layers); } void layers_raise_to_top_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; GList *iter; GList *raised_layers = NULL; return_if_no_layers (image, layers, data); for (iter = layers; iter; iter = iter->next) { gint index; index = pika_item_get_index (iter->data); if (index > 0) raised_layers = g_list_prepend (raised_layers, iter->data); } pika_image_undo_group_start (image, PIKA_UNDO_GROUP_ITEM_DISPLACE, ngettext ("Raise Layer to Top", "Raise Layers to Top", g_list_length (raised_layers))); for (iter = raised_layers; iter; iter = iter->next) pika_image_raise_item_to_top (image, iter->data); pika_image_flush (image); pika_image_undo_group_end (image); g_list_free (raised_layers); } void layers_lower_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; GList *iter; GList *lowered_layers = NULL; return_if_no_layers (image, layers, data); for (iter = layers; iter; iter = iter->next) { GList *layer_list; gint index; layer_list = pika_item_get_container_iter (PIKA_ITEM (iter->data)); index = pika_item_get_index (iter->data); if (index < g_list_length (layer_list) - 1) lowered_layers = g_list_prepend (lowered_layers, iter->data); } pika_image_undo_group_start (image, PIKA_UNDO_GROUP_ITEM_DISPLACE, ngettext ("Lower Layer", "Lower Layers", g_list_length (lowered_layers))); for (iter = lowered_layers; iter; iter = iter->next) pika_image_lower_item (image, iter->data, NULL); pika_image_flush (image); pika_image_undo_group_end (image); g_list_free (lowered_layers); } void layers_lower_to_bottom_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; GList *iter; GList *lowered_layers = NULL; return_if_no_layers (image, layers, data); for (iter = layers; iter; iter = iter->next) { GList *layer_list; gint index; layer_list = pika_item_get_container_iter (PIKA_ITEM (iter->data)); index = pika_item_get_index (iter->data); if (index < g_list_length (layer_list) - 1) lowered_layers = g_list_prepend (lowered_layers, iter->data); } pika_image_undo_group_start (image, PIKA_UNDO_GROUP_ITEM_DISPLACE, ngettext ("Lower Layer to Bottom", "Lower Layers to Bottom", g_list_length (lowered_layers))); for (iter = lowered_layers; iter; iter = iter->next) pika_image_lower_item_to_bottom (image, iter->data); pika_image_flush (image); pika_image_undo_group_end (image); g_list_free (lowered_layers); } void layers_duplicate_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; GList *new_layers = NULL; GList *iter; return_if_no_layers (image, layers, data); layers = g_list_copy (layers); pika_image_undo_group_start (image, PIKA_UNDO_GROUP_LAYER_ADD, _("Duplicate layers")); for (iter = layers; iter; iter = iter->next) { PikaLayer *new_layer; new_layer = PIKA_LAYER (pika_item_duplicate (PIKA_ITEM (iter->data), G_TYPE_FROM_INSTANCE (iter->data))); /* use the actual parent here, not PIKA_IMAGE_ACTIVE_PARENT because * the latter would add a duplicated group inside itself instead of * above it */ pika_image_add_layer (image, new_layer, pika_layer_get_parent (iter->data), pika_item_get_index (iter->data), TRUE); new_layers = g_list_prepend (new_layers, new_layer); } pika_image_set_selected_layers (image, new_layers); g_list_free (layers); g_list_free (new_layers); pika_image_undo_group_end (image); pika_image_flush (image); } void layers_anchor_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; return_if_no_layers (image, layers, data); if (g_list_length (layers) == 1 && pika_layer_is_floating_sel (layers->data)) { floating_sel_anchor (layers->data); pika_image_flush (image); } } void layers_merge_down_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; PikaDisplay *display; GError *error = NULL; return_if_no_layers (image, layers, data); return_if_no_display (display, data); layers = pika_image_merge_down (image, layers, action_data_get_context (data), PIKA_EXPAND_AS_NECESSARY, PIKA_PROGRESS (display), &error); if (error) { pika_message_literal (image->pika, G_OBJECT (display), PIKA_MESSAGE_WARNING, error->message); g_clear_error (&error); return; } pika_image_set_selected_layers (image, layers); g_list_free (layers); pika_image_flush (image); } void layers_merge_group_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; GList *merge_layers = NULL; GList *iter; return_if_no_layers (image, layers, data); for (iter = layers; iter; iter = iter->next) { if (pika_viewable_get_children (PIKA_VIEWABLE (iter->data))) { GList *iter2; for (iter2 = layers; iter2; iter2 = iter2->next) { /* Do not merge a layer when we already merge one of its * ancestors. */ if (pika_viewable_is_ancestor (iter2->data, iter->data)) break; } if (iter2 == NULL) merge_layers = g_list_prepend (merge_layers, iter->data); } } if (g_list_length (merge_layers) > 1) { gchar *undo_name; undo_name = g_strdup_printf (C_("undo-type", "Merge %d Layer Groups"), g_list_length (merge_layers)); pika_image_undo_group_start (image, PIKA_UNDO_GROUP_IMAGE_LAYERS_MERGE, undo_name); g_free (undo_name); } for (iter = merge_layers; iter; iter = iter->next) pika_image_merge_group_layer (image, PIKA_GROUP_LAYER (iter->data)); if (g_list_length (merge_layers) > 1) pika_image_undo_group_end (image); g_list_free (merge_layers); pika_image_flush (image); } void layers_delete_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; GList *removed_layers; GList *iter; GList *iter2; return_if_no_image (image, data); layers = pika_image_get_selected_layers (image); /*TODO: we should have a failsafe to determine when we are going to * delete all layers (i.e. all layers of first level at least) and * forbid it. */ /* Copy of the original selection. */ removed_layers = g_list_copy (layers); /* Removing children layers (they will be removed anyway by removing * the parent. */ for (iter = removed_layers; iter; iter = iter->next) { for (iter2 = removed_layers; iter2; iter2 = iter2->next) { if (iter->data != iter2->data && pika_viewable_is_ancestor (iter2->data, iter->data)) { removed_layers = g_list_delete_link (removed_layers, iter); iter = removed_layers; break; } } } if (g_list_length (removed_layers) > 1) { gchar *undo_name; undo_name = g_strdup_printf (C_("undo-type", "Remove %d Layers"), g_list_length (removed_layers)); pika_image_undo_group_start (image, PIKA_UNDO_GROUP_IMAGE_ITEM_REMOVE, undo_name); } for (iter = removed_layers; iter; iter = iter->next) pika_image_remove_layer (image, iter->data, TRUE, NULL); if (g_list_length (removed_layers) > 1) pika_image_undo_group_end (image); g_list_free (removed_layers); pika_image_flush (image); } void layers_text_discard_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; GList *iter; return_if_no_layers (image, layers, data); pika_image_undo_group_start (image, PIKA_UNDO_GROUP_TEXT, _("Discard Text Information")); for (iter = layers; iter; iter = iter->next) if (PIKA_IS_TEXT_LAYER (iter->data)) pika_text_layer_discard (PIKA_TEXT_LAYER (iter->data)); pika_image_undo_group_end (image); } void layers_text_to_vectors_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; GList *iter; return_if_no_layers (image, layers, data); /* TODO: have the proper undo group. */ pika_image_undo_group_start (image, PIKA_UNDO_GROUP_VECTORS_IMPORT, _("Add Paths")); for (iter = layers; iter; iter = iter->next) { PikaLayer *layer = iter->data; if (PIKA_IS_TEXT_LAYER (layer)) { PikaVectors *vectors; gint x, y; vectors = pika_text_vectors_new (image, PIKA_TEXT_LAYER (layer)->text); pika_item_get_offset (PIKA_ITEM (layer), &x, &y); pika_item_translate (PIKA_ITEM (vectors), x, y, FALSE); pika_image_add_vectors (image, vectors, PIKA_IMAGE_ACTIVE_PARENT, -1, TRUE); pika_image_flush (image); } } pika_image_undo_group_end (image); } void layers_text_along_vectors_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; GList *paths; PikaLayer *layer; PikaVectors *vectors; return_if_no_layers (image, layers, data); return_if_no_vectors_list (image, paths, data); if (g_list_length (layers) != 1 || g_list_length (paths) != 1) return; layer = layers->data; vectors = paths->data; if (PIKA_IS_TEXT_LAYER (layer)) { gdouble box_width; gdouble box_height; PikaVectors *new_vectors; gdouble offset; box_width = pika_item_get_width (PIKA_ITEM (layer)); box_height = pika_item_get_height (PIKA_ITEM (layer)); new_vectors = pika_text_vectors_new (image, PIKA_TEXT_LAYER (layer)->text); offset = 0; switch (PIKA_TEXT_LAYER (layer)->text->base_dir) { case PIKA_TEXT_DIRECTION_LTR: case PIKA_TEXT_DIRECTION_RTL: offset = 0.5 * box_height; break; case PIKA_TEXT_DIRECTION_TTB_RTL: case PIKA_TEXT_DIRECTION_TTB_RTL_UPRIGHT: case PIKA_TEXT_DIRECTION_TTB_LTR: case PIKA_TEXT_DIRECTION_TTB_LTR_UPRIGHT: { PikaStroke *stroke = NULL; while ((stroke = pika_vectors_stroke_get_next (new_vectors, stroke))) { pika_stroke_rotate (stroke, 0, 0, 270); pika_stroke_translate (stroke, 0, box_width); } } offset = 0.5 * box_width; break; } pika_vectors_warp_vectors (vectors, new_vectors, offset); pika_item_set_visible (PIKA_ITEM (new_vectors), TRUE, FALSE); pika_image_add_vectors (image, new_vectors, PIKA_IMAGE_ACTIVE_PARENT, -1, TRUE); pika_image_flush (image); } } void layers_resize_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; PikaLayer *layer; GList *layers; GtkWidget *widget; GtkWidget *dialog; return_if_no_layers (image, layers, data); return_if_no_widget (widget, data); #define RESIZE_DIALOG_KEY "pika-resize-dialog" g_return_if_fail (g_list_length (layers) == 1); layer = layers->data; dialog = dialogs_get_dialog (G_OBJECT (layer), RESIZE_DIALOG_KEY); if (! dialog) { PikaDialogConfig *config = PIKA_DIALOG_CONFIG (image->pika->config); PikaDisplay *display = NULL; if (PIKA_IS_IMAGE_WINDOW (data)) display = action_data_get_display (data); if (layer_resize_unit != PIKA_UNIT_PERCENT && display) layer_resize_unit = pika_display_get_shell (display)->unit; dialog = resize_dialog_new (PIKA_VIEWABLE (layer), action_data_get_context (data), _("Set Layer Boundary Size"), "pika-layer-resize", widget, pika_standard_help_func, PIKA_HELP_LAYER_RESIZE, layer_resize_unit, config->layer_resize_fill_type, PIKA_ITEM_SET_NONE, FALSE, layers_resize_callback, NULL); dialogs_attach_dialog (G_OBJECT (layer), RESIZE_DIALOG_KEY, dialog); } gtk_window_present (GTK_WINDOW (dialog)); } void layers_resize_to_image_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; GList *iter; return_if_no_layers (image, layers, data); if (g_list_length (layers) > 1) pika_image_undo_group_start (image, PIKA_UNDO_GROUP_ITEM_RESIZE, _("Layers to Image Size")); for (iter = layers; iter; iter = iter->next) pika_layer_resize_to_image (iter->data, action_data_get_context (data), PIKA_FILL_TRANSPARENT); if (g_list_length (layers) > 1) pika_image_undo_group_end (image); pika_image_flush (image); } void layers_scale_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; PikaLayer *layer; GtkWidget *widget; GtkWidget *dialog; return_if_no_layers (image, layers, data); return_if_no_widget (widget, data); #define SCALE_DIALOG_KEY "pika-scale-dialog" g_return_if_fail (g_list_length (layers) == 1); layer = layers->data; dialog = dialogs_get_dialog (G_OBJECT (layer), SCALE_DIALOG_KEY); if (! dialog) { PikaDisplay *display = NULL; if (PIKA_IS_IMAGE_WINDOW (data)) display = action_data_get_display (data); if (layer_scale_unit != PIKA_UNIT_PERCENT && display) layer_scale_unit = pika_display_get_shell (display)->unit; if (layer_scale_interp == -1) layer_scale_interp = image->pika->config->interpolation_type; dialog = scale_dialog_new (PIKA_VIEWABLE (layer), action_data_get_context (data), _("Scale Layer"), "pika-layer-scale", widget, pika_standard_help_func, PIKA_HELP_LAYER_SCALE, layer_scale_unit, layer_scale_interp, layers_scale_callback, display); dialogs_attach_dialog (G_OBJECT (layer), SCALE_DIALOG_KEY, dialog); } gtk_window_present (GTK_WINDOW (dialog)); } void layers_crop_to_selection_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; GList *iter; GtkWidget *widget; gchar *desc; gint x, y; gint width, height; return_if_no_layers (image, layers, data); return_if_no_widget (widget, data); if (! pika_item_bounds (PIKA_ITEM (pika_image_get_mask (image)), &x, &y, &width, &height)) { pika_message_literal (image->pika, G_OBJECT (widget), PIKA_MESSAGE_WARNING, _("Cannot crop because the current selection " "is empty.")); return; } desc = g_strdup_printf (ngettext ("Crop Layer to Selection", "Crop %d Layers to Selection", g_list_length (layers)), g_list_length (layers)); pika_image_undo_group_start (image, PIKA_UNDO_GROUP_ITEM_RESIZE, desc); g_free (desc); for (iter = layers; iter; iter = iter->next) { gint off_x, off_y; pika_item_get_offset (PIKA_ITEM (iter->data), &off_x, &off_y); off_x -= x; off_y -= y; pika_item_resize (PIKA_ITEM (iter->data), action_data_get_context (data), PIKA_FILL_TRANSPARENT, width, height, off_x, off_y); } pika_image_undo_group_end (image); pika_image_flush (image); } void layers_crop_to_content_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; GList *iter; GtkWidget *widget; gchar *desc; gint x, y; gint width, height; gint n_croppable = 0; return_if_no_layers (image, layers, data); return_if_no_widget (widget, data); for (iter = layers; iter; iter = iter->next) { switch (pika_pickable_auto_shrink (PIKA_PICKABLE (iter->data), 0, 0, pika_item_get_width (iter->data), pika_item_get_height (iter->data), &x, &y, &width, &height)) { case PIKA_AUTO_SHRINK_SHRINK: n_croppable++; break; case PIKA_AUTO_SHRINK_EMPTY: /* Cannot crop because the layer has no content. */ case PIKA_AUTO_SHRINK_UNSHRINKABLE: /* Cannot crop because the active layer is already cropped to * its content. */ break; } } if (n_croppable == 0) { pika_message_literal (image->pika, G_OBJECT (widget), PIKA_MESSAGE_INFO, _("Cannot crop because none of the selected" " layers have content or they are already" " cropped to their content.")); return; } desc = g_strdup_printf (ngettext ("Crop Layer to Content", "Crop %d Layers to Content", n_croppable), n_croppable); pika_image_undo_group_start (image, PIKA_UNDO_GROUP_ITEM_RESIZE, desc); g_free (desc); for (iter = layers; iter; iter = iter->next) { switch (pika_pickable_auto_shrink (PIKA_PICKABLE (iter->data), 0, 0, pika_item_get_width (iter->data), pika_item_get_height (iter->data), &x, &y, &width, &height)) { case PIKA_AUTO_SHRINK_SHRINK: pika_item_resize (iter->data, action_data_get_context (data), PIKA_FILL_TRANSPARENT, width, height, -x, -y); break; default: break; } } pika_image_flush (image); pika_image_undo_group_end (image); } void layers_mask_add_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; GList *iter; GtkWidget *widget; GtkWidget *dialog; GList *update_layers = NULL; gint n_channels = 0; return_if_no_layers (image, layers, data); return_if_no_widget (widget, data); for (iter = layers; iter; iter = iter->next) { g_return_if_fail (PIKA_IS_LAYER (iter->data)); if (! pika_layer_get_mask (iter->data)) { update_layers = g_list_prepend (update_layers, iter->data); n_channels++; } } if (n_channels == 0) /* No layers or they all have masks already. */ return; #define ADD_MASK_DIALOG_KEY "pika-add-mask-dialog" for (iter = update_layers; iter; iter = iter->next) { dialog = dialogs_get_dialog (G_OBJECT (iter->data), ADD_MASK_DIALOG_KEY); if (dialog) break; } if (! dialog) { PikaDialogConfig *config = PIKA_DIALOG_CONFIG (image->pika->config); dialog = layer_add_mask_dialog_new (update_layers, action_data_get_context (data), widget, config->layer_add_mask_type, config->layer_add_mask_invert, layers_add_mask_callback, NULL); for (iter = update_layers; iter; iter = iter->next) dialogs_attach_dialog (G_OBJECT (iter->data), ADD_MASK_DIALOG_KEY, dialog); } gtk_window_present (GTK_WINDOW (dialog)); } void layers_mask_add_last_vals_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; GList *iter; GtkWidget *widget; PikaDialogConfig *config; PikaChannel *channel = NULL; PikaLayerMask *mask; return_if_no_layers (image, layers, data); return_if_no_widget (widget, data); config = PIKA_DIALOG_CONFIG (image->pika->config); if (config->layer_add_mask_type == PIKA_ADD_MASK_CHANNEL) { GList *selected_channels; selected_channels = pika_image_get_selected_channels (image); if (selected_channels) { channel = selected_channels->data; } else { PikaContainer *channels = pika_image_get_channels (image); channel = PIKA_CHANNEL (pika_container_get_first_child (channels)); } if (! channel) { layers_mask_add_cmd_callback (action, value, data); return; } } for (iter = layers; iter; iter = iter->next) { if (! pika_layer_get_mask (iter->data)) break; } if (iter == NULL) /* No layers or they all have masks already. */ return; pika_image_undo_group_start (image, PIKA_UNDO_GROUP_LAYER_ADD, _("Add Layer Masks")); for (iter = layers; iter; iter = iter->next) { if (pika_layer_get_mask (iter->data)) continue; mask = pika_layer_create_mask (iter->data, config->layer_add_mask_type, channel); if (config->layer_add_mask_invert) pika_channel_invert (PIKA_CHANNEL (mask), FALSE); pika_layer_add_mask (iter->data, mask, TRUE, NULL); } pika_image_undo_group_end (image); pika_image_flush (image); } void layers_mask_apply_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaMaskApplyMode mode; PikaImage *image; GList *layers; GList *iter; gchar *undo_text = NULL; PikaUndoType undo_type = PIKA_UNDO_GROUP_NONE; return_if_no_layers (image, layers, data); mode = (PikaMaskApplyMode) g_variant_get_int32 (value); for (iter = layers; iter; iter = iter->next) { if (pika_layer_get_mask (iter->data) && (mode != PIKA_MASK_APPLY || (! pika_viewable_get_children (PIKA_VIEWABLE (iter->data)) && ! pika_item_is_content_locked (PIKA_ITEM (iter->data), NULL)))) break; } if (iter == NULL) /* No layers or none have applicable masks. */ return; switch (mode) { case PIKA_MASK_APPLY: undo_type = PIKA_UNDO_GROUP_MASK; undo_text = _("Apply Layer Masks"); break; case PIKA_MASK_DISCARD: undo_type = PIKA_UNDO_GROUP_MASK; undo_text = _("Delete Layer Masks"); break; default: g_warning ("%s: unhandled PikaMaskApplyMode %d\n", G_STRFUNC, mode); break; } if (undo_type != PIKA_UNDO_GROUP_NONE) pika_image_undo_group_start (image, undo_type, undo_text); for (iter = layers; iter; iter = iter->next) { if (pika_layer_get_mask (iter->data)) { if (mode == PIKA_MASK_APPLY && (pika_viewable_get_children (PIKA_VIEWABLE (iter->data)) || pika_item_is_content_locked (PIKA_ITEM (iter->data), NULL))) /* Layer groups cannot apply masks. Neither can * content-locked layers. */ continue; pika_layer_apply_mask (iter->data, mode, TRUE); } } if (undo_type != PIKA_UNDO_GROUP_NONE) pika_image_undo_group_end (image); pika_image_flush (image); } void layers_mask_edit_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; GList *iter; gboolean active = g_variant_get_boolean (value); return_if_no_layers (image, layers, data); /* Multiple-layer selection cannot edit masks. */ active = active && (g_list_length (layers) == 1); for (iter = layers; iter; iter = iter->next) if (pika_layer_get_mask (iter->data)) pika_layer_set_edit_mask (iter->data, active); pika_image_flush (image); } void layers_mask_show_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; GList *iter; gboolean active = g_variant_get_boolean (value); gboolean have_masks = FALSE; return_if_no_layers (image, layers, data); for (iter = layers; iter; iter = iter->next) { if (pika_layer_get_mask (iter->data)) { have_masks = TRUE; /* A bit of tricky to handle multiple and diverse layers with * a toggle action (with only binary state). * In non-active state, we will consider sets of both shown * and hidden masks as ok and exits. This allows us to switch * the action "active" state without actually changing * individual masks state without explicit user request. */ if (! active && ! pika_layer_get_show_mask (iter->data)) return; } } if (! have_masks) return; pika_image_undo_group_start (image, PIKA_UNDO_GROUP_LAYER_ADD, _("Show Layer Masks")); for (iter = layers; iter; iter = iter->next) { if (pika_layer_get_mask (iter->data)) { pika_layer_set_show_mask (iter->data, active, TRUE); } } pika_image_flush (image); pika_image_undo_group_end (image); } void layers_mask_disable_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; GList *iter; gboolean active = g_variant_get_boolean (value); gboolean have_masks = FALSE; return_if_no_layers (image, layers, data); for (iter = layers; iter; iter = iter->next) { if (pika_layer_get_mask (iter->data)) { have_masks = TRUE; /* A bit of tricky to handle multiple and diverse layers with * a toggle action (with only binary state). * In non-active state, we will consider sets of both enabled * and disabled masks as ok and exits. This allows us to * switch the action "active" state without actually changing * individual masks state without explicit user request. */ if (! active && pika_layer_get_apply_mask (iter->data)) return; } } if (! have_masks) return; pika_image_undo_group_start (image, PIKA_UNDO_GROUP_LAYER_ADD, _("Disable Layer Masks")); for (iter = layers; iter; iter = iter->next) { if (pika_layer_get_mask (iter->data)) { pika_layer_set_apply_mask (iter->data, ! active, TRUE); } } pika_image_flush (image); pika_image_undo_group_end (image); } void layers_mask_to_selection_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; GList *iter; GList *masks = NULL; return_if_no_layers (image, layers, data); for (iter = layers; iter; iter = iter->next) { if (pika_layer_get_mask (iter->data)) masks = g_list_prepend (masks, pika_layer_get_mask (iter->data)); } if (masks) { PikaChannelOps operation = (PikaChannelOps) g_variant_get_int32 (value); switch (operation) { case PIKA_CHANNEL_OP_REPLACE: pika_channel_push_undo (pika_image_get_mask (image), C_("undo-type", "Masks to Selection")); break; case PIKA_CHANNEL_OP_ADD: pika_channel_push_undo (pika_image_get_mask (image), C_("undo-type", "Add Masks to Selection")); break; case PIKA_CHANNEL_OP_SUBTRACT: pika_channel_push_undo (pika_image_get_mask (image), C_("undo-type", "Subtract Masks from Selection")); break; case PIKA_CHANNEL_OP_INTERSECT: pika_channel_push_undo (pika_image_get_mask (image), C_("undo-type", "Intersect Masks with Selection")); break; } pika_channel_combine_items (pika_image_get_mask (image), masks, operation); pika_image_flush (image); g_list_free (masks); } } void layers_alpha_add_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; GList *iter; return_if_no_layers (image, layers, data); pika_image_undo_group_start (image, PIKA_UNDO_GROUP_LAYER_ADD_ALPHA, _("Add Alpha Channel")); for (iter = layers; iter; iter = iter->next) if (! pika_drawable_has_alpha (iter->data)) pika_layer_add_alpha (iter->data); pika_image_undo_group_end (image); pika_image_flush (image); } void layers_alpha_remove_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; GList *iter; return_if_no_layers (image, layers, data); pika_image_undo_group_start (image, PIKA_UNDO_GROUP_LAYER_ADD_ALPHA, _("Remove Alpha Channel")); for (iter = layers; iter; iter = iter->next) if (pika_drawable_has_alpha (iter->data)) pika_layer_remove_alpha (iter->data, action_data_get_context (data)); pika_image_undo_group_end (image); pika_image_flush (image); } void layers_alpha_to_selection_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; PikaDisplay *display; GList *layers; PikaChannelOps operation; return_if_no_layers (image, layers, data); return_if_no_display (display, data); operation = (PikaChannelOps) g_variant_get_int32 (value); switch (operation) { case PIKA_CHANNEL_OP_REPLACE: pika_channel_push_undo (pika_image_get_mask (image), C_("undo-type", "Alpha to Selection")); break; case PIKA_CHANNEL_OP_ADD: pika_channel_push_undo (pika_image_get_mask (image), C_("undo-type", "Add Alpha to Selection")); break; case PIKA_CHANNEL_OP_SUBTRACT: pika_channel_push_undo (pika_image_get_mask (image), C_("undo-type", "Subtract Alpha from Selection")); break; case PIKA_CHANNEL_OP_INTERSECT: pika_channel_push_undo (pika_image_get_mask (image), C_("undo-type", "Intersect Alpha with Selection")); break; } pika_channel_combine_items (pika_image_get_mask (image), layers, operation); pika_image_flush (image); if (pika_channel_is_empty (pika_image_get_mask (image))) { pika_message_literal (image->pika, G_OBJECT (display), PIKA_MESSAGE_WARNING, _("Empty Selection")); } } void layers_opacity_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; GList *iter; gdouble opacity; PikaUndo *undo; PikaActionSelectType select_type; gboolean push_undo = TRUE; return_if_no_layers (image, layers, data); select_type = (PikaActionSelectType) g_variant_get_int32 (value); if (g_list_length (layers) == 1) { undo = pika_image_undo_can_compress (image, PIKA_TYPE_ITEM_UNDO, PIKA_UNDO_LAYER_OPACITY); if (undo && PIKA_ITEM_UNDO (undo)->item == PIKA_ITEM (layers->data)) push_undo = FALSE; } if (g_list_length (layers) > 1) pika_image_undo_group_start (image, PIKA_UNDO_GROUP_LAYER_OPACITY, _("Set layers opacity")); for (iter = layers; iter; iter = iter->next) { opacity = action_select_value (select_type, pika_layer_get_opacity (iter->data), 0.0, 1.0, 1.0, 1.0 / 255.0, 0.01, 0.1, 0.0, FALSE); pika_layer_set_opacity (iter->data, opacity, push_undo); } if (g_list_length (layers) > 1) pika_image_undo_group_end (image); pika_image_flush (image); } void layers_mode_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; GList *iter; PikaActionSelectType select_type; gboolean push_undo = TRUE; return_if_no_layers (image, layers, data); select_type = (PikaActionSelectType) g_variant_get_int32 (value); if (g_list_length (layers) == 1) { PikaUndo *undo; undo = pika_image_undo_can_compress (image, PIKA_TYPE_ITEM_UNDO, PIKA_UNDO_LAYER_MODE); if (undo && PIKA_ITEM_UNDO (undo)->item == PIKA_ITEM (layers->data)) push_undo = FALSE; } if (g_list_length (layers) > 1) pika_image_undo_group_start (image, PIKA_UNDO_GROUP_LAYER_OPACITY, _("Set layers opacity")); for (iter = layers; iter; iter = iter->next) { PikaLayerMode *modes; gint n_modes; PikaLayerMode layer_mode; gint index; layer_mode = pika_layer_get_mode (iter->data); modes = pika_layer_mode_get_context_array (layer_mode, PIKA_LAYER_MODE_CONTEXT_LAYER, &n_modes); index = layers_mode_index (layer_mode, modes, n_modes); index = action_select_value (select_type, index, 0, n_modes - 1, 0, 0.0, 1.0, 1.0, 0.0, FALSE); layer_mode = modes[index]; g_free (modes); pika_layer_set_mode (iter->data, layer_mode, push_undo); } if (g_list_length (layers) > 1) pika_image_undo_group_end (image); pika_image_flush (image); } void layers_blend_space_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; GList *update_layers = NULL; GList *iter; PikaLayerColorSpace blend_space; gboolean push_undo = TRUE; return_if_no_layers (image, layers, data); blend_space = (PikaLayerColorSpace) g_variant_get_int32 (value); for (iter = layers; iter; iter = iter->next) { PikaLayerMode mode; mode = pika_layer_get_mode (iter->data); if (pika_layer_mode_is_blend_space_mutable (mode) && blend_space != pika_layer_get_blend_space (iter->data)) update_layers = g_list_prepend (update_layers, iter->data); } if (g_list_length (update_layers) == 1) { PikaUndo *undo; undo = pika_image_undo_can_compress (image, PIKA_TYPE_LAYER_PROP_UNDO, PIKA_UNDO_LAYER_MODE); if (undo && PIKA_ITEM_UNDO (undo)->item == PIKA_ITEM (update_layers->data)) push_undo = FALSE; } if (update_layers) { if (g_list_length (update_layers) > 1) pika_image_undo_group_start (image, PIKA_UNDO_GROUP_LAYER_MODE, _("Set layers' blend space")); for (iter = update_layers; iter; iter = iter->next) pika_layer_set_blend_space (iter->data, blend_space, push_undo); if (g_list_length (update_layers) > 1) pika_image_undo_group_end (image); g_list_free (update_layers); pika_image_flush (image); } } void layers_composite_space_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; GList *update_layers = NULL; GList *iter; PikaLayerColorSpace composite_space; gboolean push_undo = TRUE; return_if_no_layers (image, layers, data); composite_space = (PikaLayerColorSpace) g_variant_get_int32 (value); for (iter = layers; iter; iter = iter->next) { PikaLayerMode mode; mode = pika_layer_get_mode (iter->data); if (pika_layer_mode_is_composite_space_mutable (mode) && composite_space != pika_layer_get_composite_space (iter->data)) update_layers = g_list_prepend (update_layers, iter->data); } if (g_list_length (update_layers) == 1) { PikaUndo *undo; undo = pika_image_undo_can_compress (image, PIKA_TYPE_LAYER_PROP_UNDO, PIKA_UNDO_LAYER_MODE); if (undo && PIKA_ITEM_UNDO (undo)->item == PIKA_ITEM (update_layers->data)) push_undo = FALSE; } if (update_layers) { if (g_list_length (update_layers) > 1) pika_image_undo_group_start (image, PIKA_UNDO_GROUP_LAYER_MODE, _("Set layers' composite space")); for (iter = update_layers; iter; iter = iter->next) pika_layer_set_composite_space (iter->data, composite_space, push_undo); if (g_list_length (update_layers) > 1) pika_image_undo_group_end (image); g_list_free (update_layers); pika_image_flush (image); } } void layers_composite_mode_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; GList *update_layers = NULL; GList *iter; PikaLayerCompositeMode composite_mode; gboolean push_undo = TRUE; return_if_no_layers (image, layers, data); composite_mode = (PikaLayerCompositeMode) g_variant_get_int32 (value); for (iter = layers; iter; iter = iter->next) { PikaLayerMode mode; mode = pika_layer_get_mode (iter->data); if (pika_layer_mode_is_composite_mode_mutable (mode) && composite_mode != pika_layer_get_composite_mode (iter->data)) update_layers = g_list_prepend (update_layers, iter->data); } if (g_list_length (update_layers) == 1) { PikaUndo *undo; undo = pika_image_undo_can_compress (image, PIKA_TYPE_LAYER_PROP_UNDO, PIKA_UNDO_LAYER_MODE); if (undo && PIKA_ITEM_UNDO (undo)->item == PIKA_ITEM (update_layers->data)) push_undo = FALSE; } if (update_layers) { if (g_list_length (update_layers) > 1) pika_image_undo_group_start (image, PIKA_UNDO_GROUP_LAYER_MODE, _("Set layers' composite mode")); for (iter = update_layers; iter; iter = iter->next) pika_layer_set_composite_mode (iter->data, composite_mode, push_undo); if (g_list_length (update_layers) > 1) pika_image_undo_group_end (image); g_list_free (update_layers); pika_image_flush (image); } } void layers_visible_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; return_if_no_layers (image, layers, data); items_visible_cmd_callback (action, value, image, layers); } void layers_lock_content_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; return_if_no_layers (image, layers, data); items_lock_content_cmd_callback (action, value, image, layers); } void layers_lock_position_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; return_if_no_layers (image, layers, data); items_lock_position_cmd_callback (action, value, image, layers); } void layers_lock_alpha_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; GList *iter; gboolean lock_alpha; gboolean lock_change = FALSE; return_if_no_layers (image, layers, data); lock_alpha = g_variant_get_boolean (value); for (iter = layers; iter; iter = iter->next) { if (pika_layer_can_lock_alpha (iter->data)) { /* Similar trick as in layers_mask_show_cmd_callback(). * When unlocking, we expect all selected layers to be locked, * otherwise SET_ACTIVE() calls in layers-actions.c will * trigger lock updates. */ if (! lock_alpha && ! pika_layer_get_lock_alpha (iter->data)) return; if (lock_alpha != pika_layer_get_lock_alpha (iter->data)) lock_change = TRUE; } } if (! lock_change) /* No layer locks would be changed. */ return; pika_image_undo_group_start (image, PIKA_UNDO_GROUP_LAYER_LOCK_ALPHA, lock_alpha ? _("Lock alpha channels") : _("Unlock alpha channels")); for (iter = layers; iter; iter = iter->next) { if (pika_layer_can_lock_alpha (iter->data)) { if (lock_alpha != pika_layer_get_lock_alpha (iter->data)) pika_layer_set_lock_alpha (iter->data, lock_alpha, TRUE); } } pika_image_undo_group_end (image); pika_image_flush (image); } void layers_color_tag_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *layers; PikaColorTag color_tag; return_if_no_layers (image, layers, data); color_tag = (PikaColorTag) g_variant_get_int32 (value); items_color_tag_cmd_callback (action, image, layers, color_tag); } /* private functions */ static void layers_new_callback (GtkWidget *dialog, PikaImage *image, PikaLayer *layer, PikaContext *context, const gchar *layer_name, PikaLayerMode layer_mode, PikaLayerColorSpace layer_blend_space, PikaLayerColorSpace layer_composite_space, PikaLayerCompositeMode layer_composite_mode, gdouble layer_opacity, PikaFillType layer_fill_type, gint layer_width, gint layer_height, gint layer_offset_x, gint layer_offset_y, gboolean layer_visible, PikaColorTag layer_color_tag, gboolean layer_lock_pixels, gboolean layer_lock_position, gboolean layer_lock_visibility, gboolean layer_lock_alpha, gboolean rename_text_layer, /* unused */ gpointer user_data) { PikaDialogConfig *config = PIKA_DIALOG_CONFIG (image->pika->config); GList *layers = pika_image_get_selected_layers (image); GList *new_layers = NULL; GList *iter; gint n_layers = g_list_length (layers); gboolean run_once = (n_layers == 0); g_object_set (config, "layer-new-name", layer_name, "layer-new-mode", layer_mode, "layer-new-blend-space", layer_blend_space, "layer-new-composite-space", layer_composite_space, "layer-new-composite-mode", layer_composite_mode, "layer-new-opacity", layer_opacity, "layer-new-fill-type", layer_fill_type, NULL); layers = g_list_copy (layers); pika_image_undo_group_start (image, PIKA_UNDO_GROUP_LAYER_ADD, ngettext ("New layer", "New layers", n_layers > 0 ? n_layers : 1)); for (iter = layers; iter || run_once; iter = iter ? iter->next : NULL) { PikaLayer *parent; gint position; run_once = FALSE; if (iter) { if (pika_viewable_get_children (PIKA_VIEWABLE (iter->data))) { parent = iter->data; position = 0; } else { parent = PIKA_LAYER (pika_item_get_parent (iter->data)); position = pika_item_get_index (iter->data); } } else /* run_once */ { parent = NULL; position = 0; } layer = pika_layer_new (image, layer_width, layer_height, pika_image_get_layer_format (image, TRUE), config->layer_new_name, config->layer_new_opacity, config->layer_new_mode); if (layer) { pika_item_set_offset (PIKA_ITEM (layer), layer_offset_x, layer_offset_y); pika_drawable_fill (PIKA_DRAWABLE (layer), context, config->layer_new_fill_type); pika_item_set_visible (PIKA_ITEM (layer), layer_visible, FALSE); pika_item_set_color_tag (PIKA_ITEM (layer), layer_color_tag, FALSE); pika_item_set_lock_content (PIKA_ITEM (layer), layer_lock_pixels, FALSE); pika_item_set_lock_position (PIKA_ITEM (layer), layer_lock_position, FALSE); pika_item_set_lock_visibility (PIKA_ITEM (layer), layer_lock_visibility, FALSE); pika_layer_set_lock_alpha (layer, layer_lock_alpha, FALSE); pika_layer_set_blend_space (layer, layer_blend_space, FALSE); pika_layer_set_composite_space (layer, layer_composite_space, FALSE); pika_layer_set_composite_mode (layer, layer_composite_mode, FALSE); pika_image_add_layer (image, layer, parent, position, TRUE); pika_image_flush (image); new_layers = g_list_prepend (new_layers, layer); } else { g_warning ("%s: could not allocate new layer", G_STRFUNC); } } pika_image_undo_group_end (image); pika_image_set_selected_layers (image, new_layers); g_list_free (layers); g_list_free (new_layers); gtk_widget_destroy (dialog); } static void layers_edit_attributes_callback (GtkWidget *dialog, PikaImage *image, PikaLayer *layer, PikaContext *context, const gchar *layer_name, PikaLayerMode layer_mode, PikaLayerColorSpace layer_blend_space, PikaLayerColorSpace layer_composite_space, PikaLayerCompositeMode layer_composite_mode, gdouble layer_opacity, PikaFillType unused1, gint unused2, gint unused3, gint layer_offset_x, gint layer_offset_y, gboolean layer_visible, PikaColorTag layer_color_tag, gboolean layer_lock_pixels, gboolean layer_lock_position, gboolean layer_lock_visibility, gboolean layer_lock_alpha, gboolean rename_text_layer, gpointer user_data) { PikaItem *item = PIKA_ITEM (layer); if (strcmp (layer_name, pika_object_get_name (layer)) || layer_mode != pika_layer_get_mode (layer) || layer_blend_space != pika_layer_get_blend_space (layer) || layer_composite_space != pika_layer_get_composite_space (layer) || layer_composite_mode != pika_layer_get_composite_mode (layer) || layer_opacity != pika_layer_get_opacity (layer) || layer_offset_x != pika_item_get_offset_x (item) || layer_offset_y != pika_item_get_offset_y (item) || layer_visible != pika_item_get_visible (item) || layer_color_tag != pika_item_get_color_tag (item) || layer_lock_pixels != pika_item_get_lock_content (item) || layer_lock_position != pika_item_get_lock_position (item) || layer_lock_visibility != pika_item_get_lock_visibility (item) || layer_lock_alpha != pika_layer_get_lock_alpha (layer)) { pika_image_undo_group_start (image, PIKA_UNDO_GROUP_ITEM_PROPERTIES, _("Layer Attributes")); if (strcmp (layer_name, pika_object_get_name (layer))) { GError *error = NULL; if (! pika_item_rename (PIKA_ITEM (layer), layer_name, &error)) { pika_message_literal (image->pika, G_OBJECT (dialog), PIKA_MESSAGE_WARNING, error->message); g_clear_error (&error); } } if (layer_mode != pika_layer_get_mode (layer)) pika_layer_set_mode (layer, layer_mode, TRUE); if (layer_blend_space != pika_layer_get_blend_space (layer)) pika_layer_set_blend_space (layer, layer_blend_space, TRUE); if (layer_composite_space != pika_layer_get_composite_space (layer)) pika_layer_set_composite_space (layer, layer_composite_space, TRUE); if (layer_composite_mode != pika_layer_get_composite_mode (layer)) pika_layer_set_composite_mode (layer, layer_composite_mode, TRUE); if (layer_opacity != pika_layer_get_opacity (layer)) pika_layer_set_opacity (layer, layer_opacity, TRUE); if (layer_offset_x != pika_item_get_offset_x (item) || layer_offset_y != pika_item_get_offset_y (item)) { pika_item_translate (item, layer_offset_x - pika_item_get_offset_x (item), layer_offset_y - pika_item_get_offset_y (item), TRUE); } if (layer_visible != pika_item_get_visible (item)) pika_item_set_visible (item, layer_visible, TRUE); if (layer_color_tag != pika_item_get_color_tag (item)) pika_item_set_color_tag (item, layer_color_tag, TRUE); if (layer_lock_pixels != pika_item_get_lock_content (item)) pika_item_set_lock_content (item, layer_lock_pixels, TRUE); if (layer_lock_position != pika_item_get_lock_position (item)) pika_item_set_lock_position (item, layer_lock_position, TRUE); if (layer_lock_visibility != pika_item_get_lock_visibility (item)) pika_item_set_lock_visibility (item, layer_lock_visibility, TRUE); if (layer_lock_alpha != pika_layer_get_lock_alpha (layer)) pika_layer_set_lock_alpha (layer, layer_lock_alpha, TRUE); pika_image_undo_group_end (image); pika_image_flush (image); } if (pika_item_is_text_layer (PIKA_ITEM (layer))) { g_object_set (layer, "auto-rename", rename_text_layer, NULL); } gtk_widget_destroy (dialog); } static void layers_add_mask_callback (GtkWidget *dialog, GList *layers, PikaAddMaskType add_mask_type, PikaChannel *channel, gboolean invert, gpointer user_data) { PikaImage *image = pika_item_get_image (PIKA_ITEM (layers->data)); PikaDialogConfig *config = PIKA_DIALOG_CONFIG (image->pika->config); PikaLayerMask *mask; GList *iter; GError *error = NULL; g_object_set (config, "layer-add-mask-type", add_mask_type, "layer-add-mask-invert", invert, NULL); pika_image_undo_group_start (image, PIKA_UNDO_GROUP_LAYER_ADD, _("Add Layer Masks")); for (iter = layers; iter; iter = iter->next) { mask = pika_layer_create_mask (iter->data, config->layer_add_mask_type, channel); if (config->layer_add_mask_invert) pika_channel_invert (PIKA_CHANNEL (mask), FALSE); if (! pika_layer_add_mask (iter->data, mask, TRUE, &error)) { pika_message_literal (image->pika, G_OBJECT (dialog), PIKA_MESSAGE_WARNING, error->message); g_object_unref (mask); g_clear_error (&error); return; } } pika_image_undo_group_end (image); pika_image_flush (image); gtk_widget_destroy (dialog); } static void layers_scale_callback (GtkWidget *dialog, PikaViewable *viewable, gint width, gint height, PikaUnit unit, PikaInterpolationType interpolation, gdouble xresolution, /* unused */ gdouble yresolution, /* unused */ PikaUnit resolution_unit,/* unused */ gpointer user_data) { PikaDisplay *display = PIKA_DISPLAY (user_data); layer_scale_unit = unit; layer_scale_interp = interpolation; if (width > 0 && height > 0) { PikaItem *item = PIKA_ITEM (viewable); PikaProgress *progress; GtkWidget *progress_dialog = NULL; gtk_widget_destroy (dialog); if (width == pika_item_get_width (item) && height == pika_item_get_height (item)) return; if (display) { progress = PIKA_PROGRESS (display); } else { progress_dialog = pika_progress_dialog_new (); progress = PIKA_PROGRESS (progress_dialog); } progress = pika_progress_start (progress, FALSE, _("Scaling")); pika_item_scale_by_origin (item, width, height, interpolation, progress, TRUE); if (progress) pika_progress_end (progress); if (progress_dialog) gtk_widget_destroy (progress_dialog); pika_image_flush (pika_item_get_image (item)); } else { g_warning ("Scale Error: " "Both width and height must be greater than zero."); } } static void layers_resize_callback (GtkWidget *dialog, PikaViewable *viewable, PikaContext *context, gint width, gint height, PikaUnit unit, gint offset_x, gint offset_y, gdouble unused0, gdouble unused1, PikaUnit unused2, PikaFillType fill_type, PikaItemSet unused3, gboolean unused4, gpointer user_data) { layer_resize_unit = unit; if (width > 0 && height > 0) { PikaItem *item = PIKA_ITEM (viewable); PikaImage *image = pika_item_get_image (item); PikaDialogConfig *config = PIKA_DIALOG_CONFIG (image->pika->config); g_object_set (config, "layer-resize-fill-type", fill_type, NULL); gtk_widget_destroy (dialog); if (width == pika_item_get_width (item) && height == pika_item_get_height (item)) return; pika_item_resize (item, context, fill_type, width, height, offset_x, offset_y); pika_image_flush (pika_item_get_image (item)); } else { g_warning ("Resize Error: " "Both width and height must be greater than zero."); } } static gint layers_mode_index (PikaLayerMode layer_mode, const PikaLayerMode *modes, gint n_modes) { gint i = 0; while (i < (n_modes - 1) && modes[i] != layer_mode) i++; return i; }