/* 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 "libpikabase/pikabase.h" #include "libpikacolor/pikacolor.h" #include "libpikawidgets/pikawidgets.h" #include "actions-types.h" #include "config/pikadialogconfig.h" #include "core/pika.h" #include "core/pikachannel.h" #include "core/pikachannel-select.h" #include "core/pikacontainer.h" #include "core/pikacontext.h" #include "core/pikadrawable-fill.h" #include "core/pikaimage.h" #include "core/pikaimage-undo.h" #include "widgets/pikaaction.h" #include "widgets/pikacolorpanel.h" #include "widgets/pikacomponenteditor.h" #include "widgets/pikadock.h" #include "widgets/pikahelp-ids.h" #include "dialogs/dialogs.h" #include "dialogs/channel-options-dialog.h" #include "actions.h" #include "channels-commands.h" #include "items-commands.h" #include "pika-intl.h" #define RGBA_EPSILON 1e-6 /* local function prototypes */ static void channels_new_callback (GtkWidget *dialog, PikaImage *image, PikaChannel *channel, PikaContext *context, const gchar *channel_name, const PikaRGB *channel_color, gboolean save_selection, gboolean channel_visible, PikaColorTag channel_color_tag, gboolean channel_lock_content, gboolean channel_lock_position, gboolean channel_lock_visibility, gpointer user_data); static void channels_edit_attributes_callback (GtkWidget *dialog, PikaImage *image, PikaChannel *channel, PikaContext *context, const gchar *channel_name, const PikaRGB *channel_color, gboolean save_selection, gboolean channel_visible, PikaColorTag channel_color_tag, gboolean channel_lock_content, gboolean channel_lock_position, gboolean channel_lock_visibility, gpointer user_data); /* public functions */ void channels_edit_attributes_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; PikaChannel *channel; GList *channels; GtkWidget *widget; GtkWidget *dialog; return_if_no_channels (image, channels, data); return_if_no_widget (widget, data); #define EDIT_DIALOG_KEY "pika-channel-edit-attributes-dialog" if (g_list_length (channels) != 1) return; channel = channels->data; dialog = dialogs_get_dialog (G_OBJECT (channel), EDIT_DIALOG_KEY); if (! dialog) { PikaItem *item = PIKA_ITEM (channel); dialog = channel_options_dialog_new (image, channel, action_data_get_context (data), widget, _("Channel Attributes"), "pika-channel-edit", PIKA_ICON_EDIT, _("Edit Channel Attributes"), PIKA_HELP_CHANNEL_EDIT, _("Edit Channel Color"), _("_Fill opacity:"), FALSE, pika_object_get_name (channel), &channel->color, 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), channels_edit_attributes_callback, NULL); dialogs_attach_dialog (G_OBJECT (channel), EDIT_DIALOG_KEY, dialog); } gtk_window_present (GTK_WINDOW (dialog)); } void channels_new_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GtkWidget *widget; GtkWidget *dialog; return_if_no_image (image, data); return_if_no_widget (widget, data); #define NEW_DIALOG_KEY "pika-channel-new-dialog" dialog = dialogs_get_dialog (G_OBJECT (image), NEW_DIALOG_KEY); if (! dialog) { PikaDialogConfig *config = PIKA_DIALOG_CONFIG (image->pika->config); dialog = channel_options_dialog_new (image, NULL, action_data_get_context (data), widget, _("New Channel"), "pika-channel-new", PIKA_ICON_CHANNEL, _("Create a New Channel"), PIKA_HELP_CHANNEL_NEW, _("New Channel Color"), _("_Fill opacity:"), TRUE, config->channel_new_name, &config->channel_new_color, TRUE, PIKA_COLOR_TAG_NONE, FALSE, FALSE, FALSE, channels_new_callback, NULL); dialogs_attach_dialog (G_OBJECT (image), NEW_DIALOG_KEY, dialog); } gtk_window_present (GTK_WINDOW (dialog)); } void channels_new_last_vals_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; PikaChannel *channel; PikaDialogConfig *config; return_if_no_image (image, data); config = PIKA_DIALOG_CONFIG (image->pika->config); channel = pika_channel_new (image, pika_image_get_width (image), pika_image_get_height (image), config->channel_new_name, &config->channel_new_color); pika_drawable_fill (PIKA_DRAWABLE (channel), action_data_get_context (data), PIKA_FILL_TRANSPARENT); pika_image_add_channel (image, channel, PIKA_IMAGE_ACTIVE_PARENT, -1, TRUE); pika_image_flush (image); } void channels_raise_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *channels; GList *iter; GList *raised_channels = NULL; return_if_no_channels (image, channels, data); for (iter = channels; iter; iter = iter->next) { gint index; index = pika_item_get_index (iter->data); if (index > 0) raised_channels = g_list_prepend (raised_channels, iter->data); } pika_image_undo_group_start (image, PIKA_UNDO_GROUP_ITEM_DISPLACE, ngettext ("Raise Channel", "Raise Channels", g_list_length (raised_channels))); for (iter = raised_channels; 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_channels); } void channels_raise_to_top_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *channels; GList *iter; GList *raised_channels = NULL; return_if_no_channels (image, channels, data); for (iter = channels; iter; iter = iter->next) { gint index; index = pika_item_get_index (iter->data); if (index > 0) raised_channels = g_list_prepend (raised_channels, iter->data); } pika_image_undo_group_start (image, PIKA_UNDO_GROUP_ITEM_DISPLACE, ngettext ("Raise Channel to Top", "Raise Channels to Top", g_list_length (raised_channels))); for (iter = raised_channels; 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_channels); } void channels_lower_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *channels; GList *iter; GList *lowered_channels = NULL; return_if_no_channels (image, channels, data); for (iter = channels; 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_channels = g_list_prepend (lowered_channels, iter->data); } pika_image_undo_group_start (image, PIKA_UNDO_GROUP_ITEM_DISPLACE, ngettext ("Lower Channel", "Lower Channels", g_list_length (lowered_channels))); for (iter = lowered_channels; 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_channels); } void channels_lower_to_bottom_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *channels; GList *iter; GList *lowered_channels = NULL; return_if_no_channels (image, channels, data); for (iter = channels; 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_channels = g_list_prepend (lowered_channels, iter->data); } pika_image_undo_group_start (image, PIKA_UNDO_GROUP_ITEM_DISPLACE, ngettext ("Lower Channel to Bottom", "Lower Channels to Bottom", g_list_length (lowered_channels))); for (iter = lowered_channels; 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_channels); } void channels_duplicate_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image = NULL; GList *channels; PikaChannel *parent = PIKA_IMAGE_ACTIVE_PARENT; return_if_no_channels (image, channels, data); if (PIKA_IS_COMPONENT_EDITOR (data)) { PikaChannelType component; PikaChannel *new_channel; const gchar *desc; gchar *name; component = PIKA_COMPONENT_EDITOR (data)->clicked_component; pika_enum_get_value (PIKA_TYPE_CHANNEL_TYPE, component, NULL, NULL, &desc, NULL); name = g_strdup_printf (_("%s Channel Copy"), desc); new_channel = pika_channel_new_from_component (image, component, name, NULL); /* copied components are invisible by default so subsequent copies * of components don't affect each other */ pika_item_set_visible (PIKA_ITEM (new_channel), FALSE, FALSE); pika_image_add_channel (image, new_channel, parent, -1, TRUE); g_free (name); } else { GList *new_channels = NULL; GList *iter; channels = g_list_copy (channels); pika_image_undo_group_start (image, PIKA_UNDO_GROUP_CHANNEL_ADD, _("Duplicate channels")); for (iter = channels; iter; iter = iter->next) { PikaChannel *new_channel; new_channel = PIKA_CHANNEL (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_channel (image, new_channel, pika_channel_get_parent (iter->data), pika_item_get_index (iter->data), TRUE); new_channels = g_list_prepend (new_channels, new_channel); } pika_image_set_selected_channels (image, new_channels); g_list_free (channels); g_list_free (new_channels); pika_image_undo_group_end (image); } pika_image_flush (image); } void channels_delete_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *channels; GList *iter; return_if_no_channels (image, channels, data); channels = g_list_copy (channels); if (g_list_length (channels) > 1) { gchar *undo_name; undo_name = g_strdup_printf (C_("undo-type", "Remove %d Channels"), g_list_length (channels)); pika_image_undo_group_start (image, PIKA_UNDO_GROUP_IMAGE_ITEM_REMOVE, undo_name); } for (iter = channels; iter; iter = iter->next) pika_image_remove_channel (image, iter->data, TRUE, NULL); if (g_list_length (channels) > 1) pika_image_undo_group_end (image); g_list_free (channels); pika_image_flush (image); } void channels_to_selection_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaChannelOps op; PikaImage *image; op = (PikaChannelOps) g_variant_get_int32 (value); if (PIKA_IS_COMPONENT_EDITOR (data)) { PikaChannelType component; return_if_no_image (image, data); component = PIKA_COMPONENT_EDITOR (data)->clicked_component; pika_channel_select_component (pika_image_get_mask (image), component, op, FALSE, 0.0, 0.0); } else { GList *channels; GList *iter; return_if_no_channels (image, channels, data); pika_image_undo_group_start (image, PIKA_UNDO_GROUP_DRAWABLE_MOD, _("Channels to selection")); for (iter = channels; iter; iter = iter->next) { pika_item_to_selection (iter->data, op, TRUE, FALSE, 0.0, 0.0); if (op == PIKA_CHANNEL_OP_REPLACE && iter == channels) op = PIKA_CHANNEL_OP_ADD; } pika_image_undo_group_end (image); } pika_image_flush (image); } void channels_visible_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *channels; return_if_no_channels (image, channels, data); items_visible_cmd_callback (action, value, image, channels); } void channels_lock_content_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *channels; return_if_no_channels (image, channels, data); items_lock_content_cmd_callback (action, value, image, channels); } void channels_lock_position_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *channels; return_if_no_channels (image, channels, data); items_lock_position_cmd_callback (action, value, image, channels); } void channels_color_tag_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *channels; PikaColorTag color_tag; return_if_no_channels (image, channels, data); color_tag = (PikaColorTag) g_variant_get_int32 (value); items_color_tag_cmd_callback (action, image, channels, color_tag); } void channels_select_cmd_callback (PikaAction *action, GVariant *value, gpointer data) { PikaImage *image; GList *channels; GList *new_channels = NULL; GList *iter; PikaActionSelectType select_type; gboolean run_once; return_if_no_image (image, data); select_type = (PikaActionSelectType) g_variant_get_int32 (value); channels = pika_image_get_selected_channels (image); run_once = (g_list_length (channels) == 0); for (iter = channels; iter || run_once; iter = iter ? iter->next : NULL) { PikaChannel *new_channel = NULL; PikaContainer *container; if (iter) { container = pika_item_get_container (PIKA_ITEM (iter->data)); } else /* run_once */ { container = pika_image_get_channels (image); run_once = FALSE; } new_channel = (PikaChannel *) action_select_object (select_type, container, iter ? iter->data : NULL); if (new_channel) new_channels = g_list_prepend (new_channels, new_channel); } if (new_channels) { pika_image_set_selected_channels (image, new_channels); pika_image_flush (image); } g_list_free (new_channels); } /* private functions */ static void channels_new_callback (GtkWidget *dialog, PikaImage *image, PikaChannel *channel, PikaContext *context, const gchar *channel_name, const PikaRGB *channel_color, gboolean save_selection, gboolean channel_visible, PikaColorTag channel_color_tag, gboolean channel_lock_content, gboolean channel_lock_position, gboolean channel_lock_visibility, gpointer user_data) { PikaDialogConfig *config = PIKA_DIALOG_CONFIG (image->pika->config); g_object_set (config, "channel-new-name", channel_name, "channel-new-color", channel_color, NULL); if (save_selection) { PikaChannel *selection = pika_image_get_mask (image); channel = PIKA_CHANNEL (pika_item_duplicate (PIKA_ITEM (selection), PIKA_TYPE_CHANNEL)); pika_object_set_name (PIKA_OBJECT (channel), config->channel_new_name); pika_channel_set_color (channel, &config->channel_new_color, FALSE); } else { channel = pika_channel_new (image, pika_image_get_width (image), pika_image_get_height (image), config->channel_new_name, &config->channel_new_color); pika_drawable_fill (PIKA_DRAWABLE (channel), context, PIKA_FILL_TRANSPARENT); } pika_item_set_visible (PIKA_ITEM (channel), channel_visible, FALSE); pika_item_set_color_tag (PIKA_ITEM (channel), channel_color_tag, FALSE); pika_item_set_lock_content (PIKA_ITEM (channel), channel_lock_content, FALSE); pika_item_set_lock_position (PIKA_ITEM (channel), channel_lock_position, FALSE); pika_item_set_lock_visibility (PIKA_ITEM (channel), channel_lock_visibility, FALSE); pika_image_add_channel (image, channel, PIKA_IMAGE_ACTIVE_PARENT, -1, TRUE); pika_image_flush (image); gtk_widget_destroy (dialog); } static void channels_edit_attributes_callback (GtkWidget *dialog, PikaImage *image, PikaChannel *channel, PikaContext *context, const gchar *channel_name, const PikaRGB *channel_color, gboolean save_selection, gboolean channel_visible, PikaColorTag channel_color_tag, gboolean channel_lock_content, gboolean channel_lock_position, gboolean channel_lock_visibility, gpointer user_data) { PikaItem *item = PIKA_ITEM (channel); if (strcmp (channel_name, pika_object_get_name (channel)) || pika_rgba_distance (channel_color, &channel->color) > RGBA_EPSILON || channel_visible != pika_item_get_visible (item) || channel_color_tag != pika_item_get_color_tag (item) || channel_lock_content != pika_item_get_lock_content (item) || channel_lock_position != pika_item_get_lock_position (item) || channel_lock_visibility != pika_item_get_lock_visibility (item)) { pika_image_undo_group_start (image, PIKA_UNDO_GROUP_ITEM_PROPERTIES, _("Channel Attributes")); if (strcmp (channel_name, pika_object_get_name (channel))) pika_item_rename (PIKA_ITEM (channel), channel_name, NULL); if (pika_rgba_distance (channel_color, &channel->color) > RGBA_EPSILON) pika_channel_set_color (channel, channel_color, TRUE); if (channel_visible != pika_item_get_visible (item)) pika_item_set_visible (item, channel_visible, TRUE); if (channel_color_tag != pika_item_get_color_tag (item)) pika_item_set_color_tag (item, channel_color_tag, TRUE); if (channel_lock_content != pika_item_get_lock_content (item)) pika_item_set_lock_content (item, channel_lock_content, TRUE); if (channel_lock_position != pika_item_get_lock_position (item)) pika_item_set_lock_position (item, channel_lock_position, TRUE); if (channel_lock_visibility != pika_item_get_lock_visibility (item)) pika_item_set_lock_visibility (item, channel_lock_visibility, TRUE); pika_image_undo_group_end (image); pika_image_flush (image); } gtk_widget_destroy (dialog); }