/* 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 * * color_dialog module (C) 1998 Austin Donnelly * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include "libpikabase/pikabase.h" #include "libpikacolor/pikacolor.h" #include "libpikawidgets/pikawidgets.h" #include "widgets-types.h" #include "config/pikacoreconfig.h" #include "core/pika.h" #include "core/pika-palettes.h" #include "core/pikacontext.h" #include "core/pikaimage.h" #include "core/pikaimage-color-profile.h" #include "core/pikaimage-colormap.h" #include "core/pikamarshal.h" #include "core/pikapalettemru.h" #include "core/pikaprojection.h" #include "pikaactiongroup.h" #include "pikacolordialog.h" #include "pikacolorhistory.h" #include "pikacolormapselection.h" #include "pikadialogfactory.h" #include "pikahelp-ids.h" #include "pikawidgets-constructors.h" #include "pikawidgets-utils.h" #include "pika-intl.h" #define RESPONSE_RESET 1 #define COLOR_AREA_SIZE 20 enum { UPDATE, LAST_SIGNAL }; enum { PROP_0, PROP_USER_CONTEXT_AWARE }; static void pika_color_dialog_constructed (GObject *object); static void pika_color_dialog_finalize (GObject *object); static void pika_color_dialog_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void pika_color_dialog_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static void pika_color_dialog_response (GtkDialog *dialog, gint response_id); static void pika_color_dialog_help_func (const gchar *help_id, gpointer help_data); static void pika_color_dialog_colormap_clicked (PikaColorDialog *dialog, PikaPaletteEntry *entry, GdkModifierType state); static void pika_color_dialog_colormap_edit_activate (PikaColorDialog *dialog); static void pika_color_dialog_colormap_add_activate (PikaColorDialog *dialog); static void pika_color_dialog_color_changed (PikaColorSelection *selection, PikaColorDialog *dialog); static void pika_color_history_add_clicked (GtkWidget *widget, PikaColorDialog *dialog); static void pika_color_dialog_history_selected (PikaColorHistory *history, const PikaRGB *rgb, PikaColorDialog *dialog); static void pika_color_dialog_image_changed (PikaContext *context, PikaImage *image, PikaColorDialog *dialog); static void pika_color_dialog_update_simulation(PikaImage *image, PikaColorDialog *dialog); static void pika_color_dialog_update (PikaColorDialog *dialog); static void pika_color_dialog_show (PikaColorDialog *dialog); static void pika_color_dialog_hide (PikaColorDialog *dialog); G_DEFINE_TYPE (PikaColorDialog, pika_color_dialog, PIKA_TYPE_VIEWABLE_DIALOG) #define parent_class pika_color_dialog_parent_class static guint color_dialog_signals[LAST_SIGNAL] = { 0, }; static void pika_color_dialog_class_init (PikaColorDialogClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkDialogClass *dialog_class = GTK_DIALOG_CLASS (klass); object_class->constructed = pika_color_dialog_constructed; object_class->finalize = pika_color_dialog_finalize; object_class->set_property = pika_color_dialog_set_property; object_class->get_property = pika_color_dialog_get_property; dialog_class->response = pika_color_dialog_response; color_dialog_signals[UPDATE] = g_signal_new ("update", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (PikaColorDialogClass, update), NULL, NULL, pika_marshal_VOID__BOXED_ENUM, G_TYPE_NONE, 2, PIKA_TYPE_RGB, PIKA_TYPE_COLOR_DIALOG_STATE); g_object_class_install_property (object_class, PROP_USER_CONTEXT_AWARE, g_param_spec_boolean ("user-context-aware", NULL, NULL, FALSE, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); } static void pika_color_dialog_init (PikaColorDialog *dialog) { dialog->stack = gtk_stack_new (); gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), dialog->stack, TRUE, TRUE, 0); gtk_widget_show (dialog->stack); } static void pika_color_dialog_constructed (GObject *object) { PikaColorDialog *dialog = PIKA_COLOR_DIALOG (object); PikaViewableDialog *viewable_dialog = PIKA_VIEWABLE_DIALOG (object); GtkWidget *hbox; GtkWidget *history; GtkWidget *button; GtkWidget *image; G_OBJECT_CLASS (parent_class)->constructed (object); /** Tab: colormap selection. **/ dialog->colormap_selection = pika_colormap_selection_new (viewable_dialog->context->pika->user_context); gtk_stack_add_named (GTK_STACK (dialog->stack), dialog->colormap_selection, "colormap"); g_signal_connect_swapped (dialog->colormap_selection, "color-clicked", G_CALLBACK (pika_color_dialog_colormap_clicked), dialog); g_signal_connect_swapped (dialog->colormap_selection, "color-activated", G_CALLBACK (pika_color_dialog_colormap_edit_activate), dialog); gtk_widget_show (dialog->colormap_selection); /* Edit color button */ button = pika_button_new (); gtk_button_set_relief (GTK_BUTTON (button), FALSE); image = gtk_image_new_from_icon_name (PIKA_ICON_EDIT, GTK_ICON_SIZE_LARGE_TOOLBAR); gtk_container_add (GTK_CONTAINER (button), image); gtk_widget_show (image); /* XXX: I use the same icon, tooltip and help id as in * colormap-actions.c. I wanted to just load these strings from * the action, but the group is only registered when the Colormap * dockable is opened. */ pika_help_set_help_data_with_markup (button, NC_("colormap-action", "Edit this color"), PIKA_HELP_INDEXED_PALETTE_EDIT); g_signal_connect_swapped (button, "clicked", G_CALLBACK (pika_color_dialog_colormap_edit_activate), dialog); gtk_widget_show (button); gtk_box_pack_start (GTK_BOX (PIKA_COLORMAP_SELECTION (dialog->colormap_selection)->right_vbox), button, FALSE, FALSE, 0); /* Add Color button */ button = pika_button_new (); gtk_button_set_relief (GTK_BUTTON (button), FALSE); image = gtk_image_new_from_icon_name (PIKA_ICON_LIST_ADD, GTK_ICON_SIZE_LARGE_TOOLBAR); gtk_container_add (GTK_CONTAINER (button), image); gtk_widget_show (image); pika_help_set_help_data_with_markup (button, NC_("colormap-action", "Add current foreground color"), PIKA_HELP_INDEXED_PALETTE_ADD); g_signal_connect_swapped (button, "clicked", G_CALLBACK (pika_color_dialog_colormap_add_activate), dialog); gtk_widget_show (button); gtk_box_pack_start (GTK_BOX (PIKA_COLORMAP_SELECTION (dialog->colormap_selection)->right_vbox), button, FALSE, FALSE, 0); /** Tab: color selection. **/ dialog->selection = pika_color_selection_new (); gtk_container_set_border_width (GTK_CONTAINER (dialog->selection), 12); gtk_stack_add_named (GTK_STACK (dialog->stack), dialog->selection, "color"); gtk_widget_show (dialog->selection); g_signal_connect (dialog->selection, "color-changed", G_CALLBACK (pika_color_dialog_color_changed), dialog); /* Color history box. */ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); gtk_box_pack_end (GTK_BOX (pika_color_selection_get_right_vbox (PIKA_COLOR_SELECTION (dialog->selection))), hbox, FALSE, FALSE, 0); gtk_widget_show (hbox); /* Button for adding to color history. */ button = pika_icon_button_new (PIKA_ICON_LIST_ADD, NULL); gtk_box_pack_start (GTK_BOX (hbox), GTK_WIDGET (button), FALSE, FALSE, 0); pika_help_set_help_data (button, _("Add the current color to the color history"), NULL); gtk_widget_show (button); g_signal_connect (button, "clicked", G_CALLBACK (pika_color_history_add_clicked), dialog); /* Color history table. */ history = pika_color_history_new (viewable_dialog->context, 12); gtk_box_pack_start (GTK_BOX (hbox), GTK_WIDGET (history), TRUE, TRUE, 0); gtk_widget_show (GTK_WIDGET (history)); g_signal_connect (history, "color-selected", G_CALLBACK (pika_color_dialog_history_selected), dialog); g_signal_connect (dialog, "show", G_CALLBACK (pika_color_dialog_show), NULL); g_signal_connect (dialog, "hide", G_CALLBACK (pika_color_dialog_hide), NULL); pika_color_dialog_show (dialog); } static void pika_color_dialog_finalize (GObject *object) { PikaColorDialog *dialog = PIKA_COLOR_DIALOG (object); PikaViewableDialog *viewable_dialog = PIKA_VIEWABLE_DIALOG (dialog); if (dialog->user_context_aware && viewable_dialog->context) { PikaContext *user_context = viewable_dialog->context->pika->user_context; g_signal_handlers_disconnect_by_func (user_context, G_CALLBACK (pika_color_dialog_image_changed), dialog); } G_OBJECT_CLASS (parent_class)->finalize (object); } static void pika_color_dialog_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { PikaColorDialog *dialog = PIKA_COLOR_DIALOG (object); switch (property_id) { case PROP_USER_CONTEXT_AWARE: dialog->user_context_aware = g_value_get_boolean (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void pika_color_dialog_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { PikaColorDialog *dialog = PIKA_COLOR_DIALOG (object); switch (property_id) { case PROP_USER_CONTEXT_AWARE: g_value_set_boolean (value, dialog->user_context_aware); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void pika_color_dialog_response (GtkDialog *gtk_dialog, gint response_id) { PikaColorDialog *dialog = PIKA_COLOR_DIALOG (gtk_dialog); PikaViewableDialog *viewable_dialog = PIKA_VIEWABLE_DIALOG (dialog); PikaImage *image = NULL; PikaColormapSelection *colormap_selection; gint col_index; PikaRGB color; colormap_selection = PIKA_COLORMAP_SELECTION (dialog->colormap_selection); col_index = pika_colormap_selection_get_index (colormap_selection, NULL); if (dialog->colormap_editing && viewable_dialog->context) { PikaContext *user_context = viewable_dialog->context->pika->user_context; image = pika_context_get_image (user_context); } switch (response_id) { case RESPONSE_RESET: pika_color_selection_reset (PIKA_COLOR_SELECTION (dialog->selection)); break; case GTK_RESPONSE_OK: pika_color_selection_get_color (PIKA_COLOR_SELECTION (dialog->selection), &color); if (dialog->colormap_editing && image) { PikaRGB old_color; dialog->colormap_editing = FALSE; /* Restore old color for undo */ pika_color_selection_get_old_color (PIKA_COLOR_SELECTION (dialog->selection), &old_color); pika_image_set_colormap_entry (image, col_index, &old_color, FALSE); pika_image_set_colormap_entry (image, col_index, &color, TRUE); pika_image_flush (image); gtk_stack_set_visible_child_name (GTK_STACK (dialog->stack), "colormap"); g_signal_emit (dialog, color_dialog_signals[UPDATE], 0, &color, PIKA_COLOR_DIALOG_UPDATE); } else { g_signal_emit (dialog, color_dialog_signals[UPDATE], 0, &color, PIKA_COLOR_DIALOG_OK); } break; default: pika_color_selection_get_old_color (PIKA_COLOR_SELECTION (dialog->selection), &color); if (dialog->colormap_editing && image) { dialog->colormap_editing = FALSE; pika_image_set_colormap_entry (image, col_index, &color, FALSE); pika_projection_flush (pika_image_get_projection (image)); gtk_stack_set_visible_child_name (GTK_STACK (dialog->stack), "colormap"); g_signal_emit (dialog, color_dialog_signals[UPDATE], 0, &color, PIKA_COLOR_DIALOG_UPDATE); } else { g_signal_emit (dialog, color_dialog_signals[UPDATE], 0, &color, PIKA_COLOR_DIALOG_CANCEL); } break; } } /* public functions */ GtkWidget * pika_color_dialog_new (PikaViewable *viewable, PikaContext *context, gboolean user_context_aware, const gchar *title, const gchar *icon_name, const gchar *desc, GtkWidget *parent, PikaDialogFactory *dialog_factory, const gchar *dialog_identifier, const PikaRGB *color, gboolean wants_updates, gboolean show_alpha) { PikaColorDialog *dialog; const gchar *role; gboolean use_header_bar; g_return_val_if_fail (viewable == NULL || PIKA_IS_VIEWABLE (viewable), NULL); g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL); g_return_val_if_fail (GTK_IS_WIDGET (parent), NULL); g_return_val_if_fail (dialog_factory == NULL || PIKA_IS_DIALOG_FACTORY (dialog_factory), NULL); g_return_val_if_fail (dialog_factory == NULL || dialog_identifier != NULL, NULL); g_return_val_if_fail (color != NULL, NULL); role = dialog_identifier ? dialog_identifier : "pika-color-selector"; g_object_get (gtk_settings_get_default (), "gtk-dialogs-use-header", &use_header_bar, NULL); dialog = g_object_new (PIKA_TYPE_COLOR_DIALOG, "title", title, "role", role, "help-func", pika_color_dialog_help_func, "help-id", PIKA_HELP_COLOR_DIALOG, "icon-name", icon_name, "description", desc, "context", context, "user-context-aware", user_context_aware, "parent", gtk_widget_get_toplevel (parent), "use-header-bar", use_header_bar, NULL); pika_dialog_add_buttons (PIKA_DIALOG (dialog), _("_Reset"), RESPONSE_RESET, _("_Cancel"), GTK_RESPONSE_CANCEL, _("_OK"), GTK_RESPONSE_OK, NULL); pika_dialog_set_alternative_button_order (GTK_DIALOG (dialog), RESPONSE_RESET, GTK_RESPONSE_OK, GTK_RESPONSE_CANCEL, -1); if (viewable) { pika_viewable_dialog_set_viewables (PIKA_VIEWABLE_DIALOG (dialog), g_list_prepend (NULL, viewable), context); } else { GtkWidget *parent; parent = gtk_widget_get_parent (PIKA_VIEWABLE_DIALOG (dialog)->icon); parent = gtk_widget_get_parent (parent); gtk_widget_hide (parent); } dialog->wants_updates = wants_updates; if (dialog_factory) { pika_dialog_factory_add_foreign (dialog_factory, dialog_identifier, GTK_WIDGET (dialog), pika_widget_get_monitor (parent)); } pika_color_selection_set_show_alpha (PIKA_COLOR_SELECTION (dialog->selection), show_alpha); g_object_set_data (G_OBJECT (context->pika->config->color_management), "pika-context", context); pika_color_selection_set_config (PIKA_COLOR_SELECTION (dialog->selection), context->pika->config->color_management); g_object_set_data (G_OBJECT (context->pika->config->color_management), "pika-context", NULL); pika_color_selection_set_color (PIKA_COLOR_SELECTION (dialog->selection), color); pika_color_selection_set_old_color (PIKA_COLOR_SELECTION (dialog->selection), color); return GTK_WIDGET (dialog); } void pika_color_dialog_set_color (PikaColorDialog *dialog, const PikaRGB *color) { g_return_if_fail (PIKA_IS_COLOR_DIALOG (dialog)); g_return_if_fail (color != NULL); g_signal_handlers_block_by_func (dialog->selection, pika_color_dialog_color_changed, dialog); pika_color_selection_set_color (PIKA_COLOR_SELECTION (dialog->selection), color); pika_color_selection_set_old_color (PIKA_COLOR_SELECTION (dialog->selection), color); g_signal_handlers_unblock_by_func (dialog->selection, pika_color_dialog_color_changed, dialog); } void pika_color_dialog_get_color (PikaColorDialog *dialog, PikaRGB *color) { g_return_if_fail (PIKA_IS_COLOR_DIALOG (dialog)); g_return_if_fail (color != NULL); pika_color_selection_get_color (PIKA_COLOR_SELECTION (dialog->selection), color); } /* private functions */ static void pika_color_dialog_help_func (const gchar *help_id, gpointer help_data) { PikaColorDialog *dialog = PIKA_COLOR_DIALOG (help_data); PikaColorNotebook *notebook; PikaColorSelector *current; notebook = PIKA_COLOR_NOTEBOOK (pika_color_selection_get_notebook (PIKA_COLOR_SELECTION (dialog->selection))); current = pika_color_notebook_get_current_selector (notebook); help_id = PIKA_COLOR_SELECTOR_GET_CLASS (current)->help_id; pika_standard_help_func (help_id, NULL); } static void pika_color_dialog_colormap_clicked (PikaColorDialog *dialog, PikaPaletteEntry *entry, GdkModifierType state) { pika_color_dialog_set_color (dialog, &entry->color); if (dialog->wants_updates) { g_signal_emit (dialog, color_dialog_signals[UPDATE], 0, &entry->color, PIKA_COLOR_DIALOG_UPDATE); } } static void pika_color_dialog_colormap_edit_activate (PikaColorDialog *dialog) { PikaColormapSelection *colormap_selection; PikaRGB color; gint col_index; dialog->colormap_editing = TRUE; colormap_selection = PIKA_COLORMAP_SELECTION (dialog->colormap_selection); col_index = pika_colormap_selection_get_index (colormap_selection, NULL); pika_image_get_colormap_entry (dialog->active_image, col_index, &color); pika_color_dialog_set_color (dialog, &color); gtk_stack_set_visible_child_name (GTK_STACK (dialog->stack), "color"); } static void pika_color_dialog_colormap_add_activate (PikaColorDialog *dialog) { PikaViewableDialog *viewable_dialog = PIKA_VIEWABLE_DIALOG (dialog); if (dialog->active_image && pika_image_get_colormap_size (dialog->active_image) < 256 && viewable_dialog->context) { PikaContext *user_context = viewable_dialog->context->pika->user_context; PikaRGB color; pika_context_get_foreground (user_context, &color); pika_image_add_colormap_entry (dialog->active_image, &color); pika_image_flush (dialog->active_image); } } static void pika_color_dialog_color_changed (PikaColorSelection *selection, PikaColorDialog *dialog) { PikaViewableDialog *viewable_dialog = PIKA_VIEWABLE_DIALOG (dialog); PikaRGB color; pika_color_selection_get_color (selection, &color); if (dialog->colormap_editing && viewable_dialog->context) { PikaContext *user_context = viewable_dialog->context->pika->user_context; PikaImage *image; image = pika_context_get_image (user_context); if (image) { PikaColormapSelection *colormap_selection; gboolean push_undo = FALSE; gint col_index; colormap_selection = PIKA_COLORMAP_SELECTION (dialog->colormap_selection); col_index = pika_colormap_selection_get_index (colormap_selection, NULL); if (push_undo) { PikaRGB old_color; pika_color_selection_get_old_color (PIKA_COLOR_SELECTION (dialog->selection), &old_color); /* Restore old color for undo */ pika_image_set_colormap_entry (image, col_index, &old_color, FALSE); } pika_image_set_colormap_entry (image, col_index, &color, push_undo); if (push_undo) pika_image_flush (image); else pika_projection_flush (pika_image_get_projection (image)); } } if (dialog->wants_updates) { g_signal_emit (dialog, color_dialog_signals[UPDATE], 0, &color, PIKA_COLOR_DIALOG_UPDATE); } } /* History-adding button callback */ static void pika_color_history_add_clicked (GtkWidget *widget, PikaColorDialog *dialog) { PikaViewableDialog *viewable_dialog = PIKA_VIEWABLE_DIALOG (dialog); PikaPalette *history; PikaRGB color; history = pika_palettes_get_color_history (viewable_dialog->context->pika); pika_color_selection_get_color (PIKA_COLOR_SELECTION (dialog->selection), &color); pika_palette_mru_add (PIKA_PALETTE_MRU (history), &color); } /* Color history callback */ static void pika_color_dialog_history_selected (PikaColorHistory *history, const PikaRGB *rgb, PikaColorDialog *dialog) { pika_color_selection_set_color (PIKA_COLOR_SELECTION (dialog->selection), rgb); } /* Context-related callbacks */ static void pika_color_dialog_image_changed (PikaContext *context, PikaImage *image, PikaColorDialog *dialog) { if (dialog->active_image != image) { if (dialog->active_image) { g_signal_handlers_disconnect_by_func (dialog->active_image, G_CALLBACK (pika_color_dialog_update), dialog); g_signal_handlers_disconnect_by_func (dialog->active_image, pika_color_dialog_update_simulation, dialog); } g_set_weak_pointer (&dialog->active_image, image); if (image) { g_signal_connect_swapped (image, "notify::base-type", G_CALLBACK (pika_color_dialog_update), dialog); g_signal_connect (image, "simulation-profile-changed", G_CALLBACK (pika_color_dialog_update_simulation), dialog); g_signal_connect (image, "simulation-intent-changed", G_CALLBACK (pika_color_dialog_update_simulation), dialog); g_signal_connect (image, "simulation-bpc-changed", G_CALLBACK (pika_color_dialog_update_simulation), dialog); pika_color_dialog_update_simulation (image, dialog); } pika_color_dialog_update (dialog); } } static void pika_color_dialog_update_simulation (PikaImage *image, PikaColorDialog *dialog) { g_return_if_fail (PIKA_IS_COLOR_DIALOG (dialog)); if (image && PIKA_IS_COLOR_DIALOG (dialog)) { pika_color_selection_set_simulation (PIKA_COLOR_SELECTION (dialog->selection), pika_image_get_simulation_profile (image), pika_image_get_simulation_intent (image), pika_image_get_simulation_bpc (image)); } } static void pika_color_dialog_update (PikaColorDialog *dialog) { if (dialog->active_image && pika_image_get_base_type (dialog->active_image) == PIKA_INDEXED) gtk_stack_set_visible_child_name (GTK_STACK (dialog->stack), "colormap"); else gtk_stack_set_visible_child_name (GTK_STACK (dialog->stack), "color"); } static void pika_color_dialog_show (PikaColorDialog *dialog) { PikaViewableDialog *viewable_dialog = PIKA_VIEWABLE_DIALOG (dialog); dialog->colormap_editing = FALSE; if (dialog->user_context_aware && viewable_dialog->context) { PikaContext *user_context = viewable_dialog->context->pika->user_context; PikaImage *image = pika_context_get_image (user_context); g_signal_connect (user_context, "image-changed", G_CALLBACK (pika_color_dialog_image_changed), dialog); pika_color_dialog_image_changed (viewable_dialog->context, image, dialog); pika_color_dialog_update (dialog); } else { gtk_stack_set_visible_child_name (GTK_STACK (dialog->stack), "color"); } } static void pika_color_dialog_hide (PikaColorDialog *dialog) { PikaViewableDialog *viewable_dialog = PIKA_VIEWABLE_DIALOG (dialog); if (dialog->user_context_aware && viewable_dialog->context) { PikaContext *user_context = viewable_dialog->context->pika->user_context; g_signal_handlers_disconnect_by_func (user_context, G_CALLBACK (pika_color_dialog_image_changed), dialog); } }