/* PIKA - Photo and Image Kooker Application * a rebranding of The GNU Image Manipulation Program (created with heckimp) * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio * * Original copyright, applying to most contents (license remains unchanged): * Copyright (C) 1995 Spencer Kimball and Peter Mattis * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "config.h" #include #include #include "libpikabase/pikabase.h" #include "libpikacolor/pikacolor.h" #include "libpikamath/pikamath.h" #include "libpikawidgets/pikawidgets.h" #include "widgets-types.h" #include "core/pika.h" #include "core/pikacontext.h" #include "core/pikacontainer.h" #include "core/pikaimage.h" #include "core/pikaimage-colormap.h" #include "core/pikamarshal.h" #include "core/pikapalette.h" #include "core/pikaprojection.h" #include "pikacolordialog.h" #include "pikacolormapselection.h" #include "pikadialogfactory.h" #include "pikadnd.h" #include "pikadocked.h" #include "pikamenufactory.h" #include "pikapaletteview.h" #include "pikauimanager.h" #include "pikaviewrendererpalette.h" #include "pikawidgets-utils.h" #include "pika-intl.h" enum { PROP_0, PROP_CONTEXT, PROP_INDEX }; enum { COLOR_CLICKED, COLOR_ACTIVATED, LAST_SIGNAL }; #define BORDER 6 #define RGB_EPSILON 1e-6 #define HAVE_COLORMAP(image) \ (image != NULL && \ pika_image_get_base_type (image) == PIKA_INDEXED && \ pika_image_get_colormap_palette (image) != NULL) static void pika_colormap_selection_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void pika_colormap_selection_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static void pika_colormap_selection_dispose (GObject *object); static void pika_colormap_selection_finalize (GObject *object); static void pika_colormap_selection_unmap (GtkWidget *widget); static PangoLayout * pika_colormap_selection_create_layout (GtkWidget *widget); static void pika_colormap_selection_update_entries (PikaColormapSelection *selection); static gboolean pika_colormap_selection_preview_draw (GtkWidget *widget, cairo_t *cr, PikaColormapSelection *selection); static void pika_colormap_selection_entry_clicked (PikaPaletteView *view, PikaPaletteEntry *entry, GdkModifierType state, PikaColormapSelection *selection); static void pika_colormap_selection_entry_selected (PikaPaletteView *view, PikaPaletteEntry *entry, PikaColormapSelection *selection); static void pika_colormap_selection_entry_activated (PikaPaletteView *view, PikaPaletteEntry *entry, PikaColormapSelection *selection); static void pika_colormap_selection_color_dropped (PikaPaletteView *view, PikaPaletteEntry *entry, const PikaRGB *color, PikaColormapSelection *selection); static void pika_colormap_adjustment_changed (GtkAdjustment *adjustment, PikaColormapSelection *selection); static void pika_colormap_hex_entry_changed (PikaColorHexEntry *entry, PikaColormapSelection *selection); static void pika_colormap_selection_set_context (PikaColormapSelection *selection, PikaContext *context); static void pika_colormap_selection_image_changed (PikaColormapSelection *selection, PikaImage *image); static void pika_colormap_selection_set_palette (PikaColormapSelection *selection); G_DEFINE_TYPE (PikaColormapSelection, pika_colormap_selection, GTK_TYPE_BOX) #define parent_class pika_colormap_selection_parent_class static guint signals[LAST_SIGNAL] = { 0 }; static void pika_colormap_selection_class_init (PikaColormapSelectionClass* klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); signals[COLOR_CLICKED] = g_signal_new ("color-clicked", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaColormapSelectionClass, color_clicked), NULL, NULL, pika_marshal_VOID__POINTER_ENUM, G_TYPE_NONE, 2, G_TYPE_POINTER, GDK_TYPE_MODIFIER_TYPE); signals[COLOR_ACTIVATED] = g_signal_new ("color-activated", G_TYPE_FROM_CLASS (klass), G_SIGNAL_RUN_FIRST, G_STRUCT_OFFSET (PikaColormapSelectionClass, color_activated), NULL, NULL, NULL, G_TYPE_NONE, 1, G_TYPE_POINTER); object_class->set_property = pika_colormap_selection_set_property; object_class->get_property = pika_colormap_selection_get_property; object_class->dispose = pika_colormap_selection_dispose; object_class->finalize = pika_colormap_selection_finalize; widget_class->unmap = pika_colormap_selection_unmap; g_object_class_install_property (object_class, PROP_CONTEXT, g_param_spec_object ("context", NULL, NULL, PIKA_TYPE_CONTEXT, PIKA_PARAM_READWRITE | G_PARAM_CONSTRUCT)); g_object_class_install_property (object_class, PROP_INDEX, g_param_spec_int ("index", NULL, NULL, 0, G_MAXINT, 0, PIKA_PARAM_READABLE)); } static void pika_colormap_selection_init (PikaColormapSelection *selection) { GtkWidget *frame; GtkWidget *grid; gtk_box_set_homogeneous (GTK_BOX (selection), FALSE); /* Main colormap frame. */ frame = gtk_frame_new (NULL); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); gtk_box_pack_start (GTK_BOX (selection), frame, TRUE, TRUE, 0); gtk_widget_show (frame); selection->view = pika_view_new_full_by_types (NULL, PIKA_TYPE_PALETTE_VIEW, PIKA_TYPE_PALETTE, 1, 1, 0, FALSE, TRUE, FALSE); pika_view_set_expand (PIKA_VIEW (selection->view), TRUE); gtk_container_add (GTK_CONTAINER (frame), selection->view); gtk_widget_show (selection->view); g_signal_connect (selection->view, "draw", G_CALLBACK (pika_colormap_selection_preview_draw), selection); g_signal_connect (selection->view, "entry-clicked", G_CALLBACK (pika_colormap_selection_entry_clicked), selection); g_signal_connect (selection->view, "entry-selected", G_CALLBACK (pika_colormap_selection_entry_selected), selection); g_signal_connect (selection->view, "entry-activated", G_CALLBACK (pika_colormap_selection_entry_activated), selection); g_signal_connect (selection->view, "color-dropped", G_CALLBACK (pika_colormap_selection_color_dropped), selection); /* Bottom horizontal box for additional widgets. */ selection->right_vbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); gtk_box_set_homogeneous (GTK_BOX (selection->right_vbox), TRUE); gtk_box_pack_end (GTK_BOX (selection), selection->right_vbox, FALSE, FALSE, 0); gtk_widget_show (selection->right_vbox); /* Some helpful hints */ grid = gtk_grid_new (); gtk_grid_set_column_spacing (GTK_GRID (grid), 4); gtk_grid_set_row_spacing (GTK_GRID (grid), 2); gtk_box_pack_end (GTK_BOX (selection), grid, FALSE, FALSE, 0); gtk_widget_show (grid); selection->index_adjustment = (GtkAdjustment *) gtk_adjustment_new (0, 0, 0, 1, 10, 0); selection->index_spinbutton = pika_spin_button_new (selection->index_adjustment, 1.0, 0); gtk_widget_set_halign (selection->index_spinbutton, GTK_ALIGN_START); gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (selection->index_spinbutton), TRUE); pika_grid_attach_aligned (GTK_GRID (grid), 0, 0, _("Color index:"), 0.0, 0.5, selection->index_spinbutton, 1); g_signal_connect (selection->index_adjustment, "value-changed", G_CALLBACK (pika_colormap_adjustment_changed), selection); selection->color_entry = pika_color_hex_entry_new (); gtk_widget_set_halign (selection->color_entry, GTK_ALIGN_START); pika_grid_attach_aligned (GTK_GRID (grid), 0, 1, _("HTML notation:"), 0.0, 0.5, selection->color_entry, 1); g_signal_connect (selection->color_entry, "color-changed", G_CALLBACK (pika_colormap_hex_entry_changed), selection); } static void pika_colormap_selection_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { PikaColormapSelection *selection = PIKA_COLORMAP_SELECTION (object); switch (property_id) { case PROP_CONTEXT: pika_colormap_selection_set_context (selection, g_value_get_object (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void pika_colormap_selection_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { PikaColormapSelection *selection = PIKA_COLORMAP_SELECTION (object); switch (property_id) { case PROP_CONTEXT: g_value_set_object (value, selection->context); break; case PROP_INDEX: g_value_set_int (value, selection->col_index); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void pika_colormap_selection_dispose (GObject *object) { PikaColormapSelection *selection = PIKA_COLORMAP_SELECTION (object); g_clear_pointer (&selection->color_dialog, gtk_widget_destroy); G_OBJECT_CLASS (parent_class)->dispose (object); } static void pika_colormap_selection_finalize (GObject *object) { PikaColormapSelection *selection = PIKA_COLORMAP_SELECTION (object); if (selection->context) { g_signal_handlers_disconnect_by_func (selection->context, gtk_widget_queue_draw, selection); g_signal_handlers_disconnect_by_func (selection->context, G_CALLBACK (pika_colormap_selection_image_changed), selection); } if (selection->active_image) { g_signal_handlers_disconnect_by_func (selection->active_image, G_CALLBACK (gtk_widget_queue_draw), selection); g_signal_handlers_disconnect_by_func (selection->active_image, G_CALLBACK (pika_colormap_selection_set_palette), selection); } if (selection->active_palette) { g_signal_handlers_disconnect_by_func (selection->active_palette, G_CALLBACK (gtk_widget_queue_draw), selection); } g_clear_object (&selection->layout); g_clear_object (&selection->context); G_OBJECT_CLASS (parent_class)->finalize (object); } static void pika_colormap_selection_unmap (GtkWidget *widget) { PikaColormapSelection *selection = PIKA_COLORMAP_SELECTION (widget); if (selection->color_dialog) gtk_widget_hide (selection->color_dialog); GTK_WIDGET_CLASS (parent_class)->unmap (widget); } /* public functions */ GtkWidget * pika_colormap_selection_new (PikaContext *context) { g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL); return g_object_new (PIKA_TYPE_COLORMAP_SELECTION, "context", context, "orientation", GTK_ORIENTATION_VERTICAL, NULL); } gint pika_colormap_selection_get_index (PikaColormapSelection *selection, const PikaRGB *search) { PikaImage *image; gint index; g_return_val_if_fail (PIKA_IS_COLORMAP_SELECTION (selection), 0); image = pika_context_get_image (selection->context); if (! HAVE_COLORMAP (image)) return -1; index = selection->col_index; if (search) { PikaRGB temp; pika_image_get_colormap_entry (image, index, &temp); if (pika_rgb_distance (&temp, search) > RGB_EPSILON) { gint n_colors = pika_image_get_colormap_size (image); gint i; for (i = 0; i < n_colors; i++) { pika_image_get_colormap_entry (image, i, &temp); if (pika_rgb_distance (&temp, search) < RGB_EPSILON) { index = i; break; } } } } return index; } gboolean pika_colormap_selection_set_index (PikaColormapSelection *selection, gint index, PikaRGB *color) { PikaImage *image; gint size; g_return_val_if_fail (PIKA_IS_COLORMAP_SELECTION (selection), FALSE); image = pika_context_get_image (selection->context); if (! HAVE_COLORMAP (image)) return FALSE; size = pika_image_get_colormap_size (image); if (size < 1) return FALSE; index = CLAMP (index, 0, size - 1); if (index != selection->col_index) { PikaPalette *palette = pika_image_get_colormap_palette (image); selection->col_index = index; g_object_notify (G_OBJECT (selection), "index"); pika_palette_view_select_entry (PIKA_PALETTE_VIEW (selection->view), pika_palette_get_entry (palette, index)); pika_colormap_selection_update_entries (selection); } if (color) pika_image_get_colormap_entry (image, index, color); return TRUE; } gint pika_colormap_selection_max_index (PikaColormapSelection *selection) { PikaImage *image; g_return_val_if_fail (PIKA_IS_COLORMAP_SELECTION (selection), -1); image = pika_context_get_image (selection->context); if (! HAVE_COLORMAP (image)) return -1; return MAX (0, pika_image_get_colormap_size (image) - 1); } PikaPaletteEntry * pika_colormap_selection_get_selected_entry (PikaColormapSelection *selection) { g_return_val_if_fail (PIKA_IS_COLORMAP_SELECTION (selection), NULL); return pika_palette_view_get_selected_entry (PIKA_PALETTE_VIEW (selection->view)); } void pika_colormap_selection_get_entry_rect (PikaColormapSelection *selection, PikaPaletteEntry *entry, GdkRectangle *rect) { GtkAllocation allocation; g_return_if_fail (PIKA_IS_COLORMAP_SELECTION (selection)); g_return_if_fail (entry); g_return_if_fail (rect); pika_palette_view_get_entry_rect (PIKA_PALETTE_VIEW (selection->view), entry, rect); gtk_widget_get_allocation (GTK_WIDGET (selection), &allocation); /* rect->x += allocation.x; */ /* rect->y += allocation.y; */ } /* private functions */ static PangoLayout * pika_colormap_selection_create_layout (GtkWidget *widget) { PangoLayout *layout; PangoAttrList *attrs; PangoAttribute *attr; layout = gtk_widget_create_pango_layout (widget, _("Only indexed images have " "a colormap.")); pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER); attrs = pango_attr_list_new (); attr = pango_attr_style_new (PANGO_STYLE_ITALIC); attr->start_index = 0; attr->end_index = -1; pango_attr_list_insert (attrs, attr); pango_layout_set_attributes (layout, attrs); pango_attr_list_unref (attrs); return layout; } static gboolean pika_colormap_selection_preview_draw (GtkWidget *widget, cairo_t *cr, PikaColormapSelection *selection) { GtkStyleContext *style = gtk_widget_get_style_context (widget); PikaImage *image; GtkAllocation allocation; GdkRGBA color; gint width, height; gint y; image = pika_context_get_image (selection->context); if (image == NULL || pika_image_get_base_type (image) == PIKA_INDEXED) return FALSE; gtk_style_context_get_color (style, gtk_widget_get_state_flags (widget), &color); gdk_cairo_set_source_rgba (cr, &color); gtk_widget_get_allocation (widget, &allocation); if (! selection->layout) selection->layout = pika_colormap_selection_create_layout (selection->view); pango_layout_set_width (selection->layout, PANGO_SCALE * (allocation.width - 2 * BORDER)); pango_layout_get_pixel_size (selection->layout, &width, &height); y = (allocation.height - height) / 2; cairo_move_to (cr, BORDER, MAX (y, 0)); pango_cairo_show_layout (cr, selection->layout); return TRUE; } static void pika_colormap_selection_update_entries (PikaColormapSelection *selection) { PikaImage *image = pika_context_get_image (selection->context); if (! HAVE_COLORMAP (image) || ! pika_image_get_colormap_size (image)) { gtk_widget_set_sensitive (selection->index_spinbutton, FALSE); gtk_widget_set_sensitive (selection->color_entry, FALSE); gtk_adjustment_set_value (selection->index_adjustment, 0); gtk_entry_set_text (GTK_ENTRY (selection->color_entry), ""); } else { PikaRGB color; guchar r, g, b; gchar *string; gtk_adjustment_set_value (selection->index_adjustment, selection->col_index); pika_image_get_colormap_entry (image, selection->col_index, &color); pika_rgb_get_uchar (&color, &r, &g, &b); string = g_strdup_printf ("%02x%02x%02x", r, g, b); gtk_entry_set_text (GTK_ENTRY (selection->color_entry), string); g_free (string); gtk_widget_set_sensitive (selection->index_spinbutton, TRUE); gtk_widget_set_sensitive (selection->color_entry, TRUE); } } static void pika_colormap_selection_entry_clicked (PikaPaletteView *view, PikaPaletteEntry *entry, GdkModifierType state, PikaColormapSelection *selection) { PikaPalette *palette; gint index; palette = pika_image_get_colormap_palette (selection->active_image); index = pika_palette_get_entry_position (palette, entry); pika_colormap_selection_set_index (selection, index, NULL); g_signal_emit (selection, signals[COLOR_CLICKED], 0, entry, state); } static void pika_colormap_selection_entry_selected (PikaPaletteView *view, PikaPaletteEntry *entry, PikaColormapSelection *selection) { PikaPalette *palette; gint index = 0; palette = pika_image_get_colormap_palette (selection->active_image); if (entry) index = pika_palette_get_entry_position (palette, entry); pika_colormap_selection_set_index (selection, index, NULL); } static void pika_colormap_selection_entry_activated (PikaPaletteView *view, PikaPaletteEntry *entry, PikaColormapSelection *selection) { PikaPalette *palette; gint index; palette = pika_image_get_colormap_palette (selection->active_image); index = pika_palette_get_entry_position (palette, entry); pika_colormap_selection_set_index (selection, index, NULL); g_signal_emit (selection, signals[COLOR_ACTIVATED], 0, entry); } static void pika_colormap_selection_color_dropped (PikaPaletteView *view, PikaPaletteEntry *entry, const PikaRGB *color, PikaColormapSelection *selection) { } static void pika_colormap_adjustment_changed (GtkAdjustment *adjustment, PikaColormapSelection *selection) { PikaImage *image = pika_context_get_image (selection->context); if (HAVE_COLORMAP (image)) { gint index = ROUND (gtk_adjustment_get_value (adjustment)); pika_colormap_selection_set_index (selection, index, NULL); pika_colormap_selection_update_entries (selection); } } static void pika_colormap_hex_entry_changed (PikaColorHexEntry *entry, PikaColormapSelection *selection) { PikaImage *image = pika_context_get_image (selection->context); if (image) { PikaRGB color; pika_color_hex_entry_get_color (entry, &color); pika_image_set_colormap_entry (image, selection->col_index, &color, TRUE); pika_image_flush (image); } } static void pika_colormap_selection_set_context (PikaColormapSelection *selection, PikaContext *context) { g_return_if_fail (PIKA_IS_COLORMAP_SELECTION (selection)); g_return_if_fail (context == NULL || PIKA_IS_CONTEXT (context)); if (context != selection->context) { if (selection->context) { g_signal_handlers_disconnect_by_func (selection->context, gtk_widget_queue_draw, selection); g_signal_handlers_disconnect_by_func (selection->context, G_CALLBACK (pika_colormap_selection_image_changed), selection); g_object_unref (selection->context); } selection->context = context; if (context) { g_object_ref (context); g_signal_connect_swapped (context, "foreground-changed", G_CALLBACK (gtk_widget_queue_draw), selection); g_signal_connect_swapped (context, "background-changed", G_CALLBACK (gtk_widget_queue_draw), selection); g_signal_connect_swapped (context, "image-changed", G_CALLBACK (pika_colormap_selection_image_changed), selection); pika_colormap_selection_image_changed (selection, pika_context_get_image (context)); } pika_view_renderer_set_context (PIKA_VIEW (selection->view)->renderer, context); g_object_notify (G_OBJECT (selection), "context"); } } static void pika_colormap_selection_image_changed (PikaColormapSelection *selection, PikaImage *image) { if (selection->active_image) { g_signal_handlers_disconnect_by_func (selection->active_image, G_CALLBACK (gtk_widget_queue_draw), selection); g_signal_handlers_disconnect_by_func (selection->active_image, G_CALLBACK (pika_colormap_selection_set_palette), selection); if (pika_image_get_base_type (selection->active_image) == PIKA_INDEXED) { PikaPalette *palette; palette = pika_image_get_colormap_palette (selection->active_image); g_signal_handlers_disconnect_by_func (palette, G_CALLBACK (gtk_widget_queue_draw), selection); } if (selection->color_dialog) gtk_widget_hide (selection->color_dialog); if (! HAVE_COLORMAP (image)) { gtk_adjustment_set_upper (selection->index_adjustment, 0); if (gtk_widget_get_mapped (GTK_WIDGET (selection))) { if (selection->active_palette) g_signal_handlers_disconnect_by_func (selection->active_palette, G_CALLBACK (gtk_widget_queue_draw), selection); pika_view_set_viewable (PIKA_VIEW (selection->view), NULL); gtk_adjustment_set_upper (selection->index_adjustment, 0); selection->active_palette = NULL; } } } g_set_weak_pointer (&selection->active_image, image); if (image) { g_signal_connect_swapped (image, "colormap-changed", G_CALLBACK (gtk_widget_queue_draw), selection); g_signal_connect_swapped (image, "mode-changed", G_CALLBACK (pika_colormap_selection_set_palette), selection); } pika_colormap_selection_set_palette (selection); gtk_widget_queue_draw (GTK_WIDGET (selection)); } static void pika_colormap_selection_set_palette (PikaColormapSelection *selection) { PikaPalette *palette = NULL; if (selection->active_image) palette = pika_image_get_colormap_palette (selection->active_image); if (palette != selection->active_palette) { if (selection->active_palette) { pika_colormap_selection_set_index (selection, 0, NULL); g_signal_handlers_disconnect_by_func (selection->active_palette, G_CALLBACK (gtk_widget_queue_draw), selection); pika_view_set_viewable (PIKA_VIEW (selection->view), NULL); gtk_adjustment_set_upper (selection->index_adjustment, 0); } g_set_weak_pointer (&selection->active_palette, palette); if (palette) { g_signal_connect_swapped (palette, "dirty", G_CALLBACK (gtk_widget_queue_draw), selection); pika_view_set_viewable (PIKA_VIEW (selection->view), PIKA_VIEWABLE (palette)); gtk_adjustment_set_upper (selection->index_adjustment, pika_image_get_colormap_size (selection->active_image) - 1); } } }