/* LIBGIMP - The GIMP Library * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball * * gimpdrawablechooser.h * Copyright (C) 2023 Jehan * * This library is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 3 of the License, or (at your option) any later version. * * This library 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 * Library General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see * . */ #include "config.h" #include #include #include "libgimpwidgets/gimpwidgets.h" #include "gimp.h" #include "gimpuitypes.h" #include "gimpdrawablechooser.h" #include "gimpuimarshal.h" #include "libgimp-intl.h" /** * SECTION: gimpdrawablechooser * @title: GimpDrawableChooser * @short_description: A widget allowing to select a drawable. * * The chooser contains an optional label and a button which queries the core * process to pop up a drawable selection dialog. * * Since: 3.0 **/ #define CELL_SIZE 40 enum { PROP_0, PROP_TITLE, PROP_LABEL, PROP_DRAWABLE, PROP_DRAWABLE_TYPE, N_PROPS }; struct _GimpDrawableChooser { GtkBox parent_instance; GType drawable_type; GimpDrawable *drawable; gchar *title; gchar *label; gchar *callback; GBytes *thumbnail; GimpDrawable *thumbnail_drawable; gint width; gint height; gint bpp; GtkWidget *label_widget; GtkWidget *preview_frame; GtkWidget *preview; GtkWidget *preview_title; }; /* local function prototypes */ static void gimp_drawable_chooser_constructed (GObject *object); static void gimp_drawable_chooser_dispose (GObject *object); static void gimp_drawable_chooser_finalize (GObject *object); static void gimp_drawable_chooser_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec); static void gimp_drawable_chooser_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec); static void gimp_drawable_chooser_clicked (GimpDrawableChooser *chooser); static GimpValueArray * gimp_temp_callback_run (GimpProcedure *procedure, GimpProcedureConfig *config, GimpDrawableChooser *chooser); static gboolean gimp_drawable_select_remove_after_run (const gchar *procedure_name); static void gimp_drawable_chooser_draw (GimpDrawableChooser *chooser); static void gimp_drawable_chooser_get_thumbnail (GimpDrawableChooser *chooser, gint width, gint height); static GParamSpec *drawable_button_props[N_PROPS] = { NULL, }; G_DEFINE_FINAL_TYPE (GimpDrawableChooser, gimp_drawable_chooser, GTK_TYPE_BOX) static void gimp_drawable_chooser_class_init (GimpDrawableChooserClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); object_class->constructed = gimp_drawable_chooser_constructed; object_class->dispose = gimp_drawable_chooser_dispose; object_class->finalize = gimp_drawable_chooser_finalize; object_class->set_property = gimp_drawable_chooser_set_property; object_class->get_property = gimp_drawable_chooser_get_property; /** * GimpDrawableChooser:title: * * The title to be used for the drawable selection popup dialog. * * Since: 3.0 */ drawable_button_props[PROP_TITLE] = g_param_spec_string ("title", "Title", "The title to be used for the drawable selection popup dialog", "Drawable Selection", GIMP_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); /** * GimpDrawableChooser:label: * * Label text with mnemonic. * * Since: 3.0 */ drawable_button_props[PROP_LABEL] = g_param_spec_string ("label", "Label", "The label to be used next to the button", NULL, GIMP_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY); /** * GimpDrawableChooser:drawable: * * The currently selected drawable. * * Since: 3.0 */ drawable_button_props[PROP_DRAWABLE] = gimp_param_spec_drawable ("drawable", "Drawable", "The currently selected drawable", TRUE, GIMP_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY); /** * GimpDrawableChooser:drawable-type: * * Allowed drawable types, which must be either GIMP_TYPE_DRAWABLE or a * subtype. * * Since: 3.0 */ drawable_button_props[PROP_DRAWABLE_TYPE] = g_param_spec_gtype ("drawable-type", "Allowed drawable Type", "The GType of the drawable property", GIMP_TYPE_DRAWABLE, G_PARAM_CONSTRUCT_ONLY | GIMP_PARAM_READWRITE); g_object_class_install_properties (object_class, N_PROPS, drawable_button_props); } static void gimp_drawable_chooser_init (GimpDrawableChooser *chooser) { gtk_orientable_set_orientation (GTK_ORIENTABLE (chooser), GTK_ORIENTATION_HORIZONTAL); gtk_box_set_spacing (GTK_BOX (chooser), 6); chooser->thumbnail_drawable = NULL; chooser->thumbnail = NULL; } static void gimp_drawable_chooser_constructed (GObject *object) { GimpDrawableChooser *chooser = GIMP_DRAWABLE_CHOOSER (object); GtkWidget *button; GtkWidget *box; gint scale_factor; scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (chooser)); chooser->label_widget = gtk_label_new (NULL); gtk_box_pack_start (GTK_BOX (chooser), chooser->label_widget, FALSE, FALSE, 0); gtk_label_set_text_with_mnemonic (GTK_LABEL (chooser->label_widget), chooser->label); gtk_widget_show (GTK_WIDGET (chooser->label_widget)); button = gtk_button_new (); gtk_box_pack_start (GTK_BOX (chooser), button, FALSE, FALSE, 0); gtk_label_set_mnemonic_widget (GTK_LABEL (chooser->label_widget), button); gtk_widget_show (button); box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2); gtk_container_add (GTK_CONTAINER (button), box); gtk_widget_show (box); chooser->preview_frame = gtk_aspect_frame_new (NULL, 0.5, 0.5, 1.0, FALSE); gtk_frame_set_shadow_type (GTK_FRAME (chooser->preview_frame), GTK_SHADOW_IN); gtk_box_pack_start (GTK_BOX (box), chooser->preview_frame, FALSE, FALSE, 0); gtk_widget_show (chooser->preview_frame); chooser->preview = gimp_preview_area_new (); gtk_widget_set_size_request (chooser->preview, scale_factor * CELL_SIZE, scale_factor * CELL_SIZE); gtk_container_add (GTK_CONTAINER (chooser->preview_frame), chooser->preview); gtk_widget_show (chooser->preview); chooser->preview_title = gtk_label_new (_("Browse...")); gtk_box_pack_start (GTK_BOX (box), chooser->preview_title, FALSE, FALSE, 0); gtk_widget_show (chooser->preview_title); g_signal_connect_swapped (button, "clicked", G_CALLBACK (gimp_drawable_chooser_clicked), chooser); G_OBJECT_CLASS (gimp_drawable_chooser_parent_class)->constructed (object); } static void gimp_drawable_chooser_dispose (GObject *object) { GimpDrawableChooser *chooser = GIMP_DRAWABLE_CHOOSER (object); if (chooser->callback) { gimp_drawables_close_popup (chooser->callback); gimp_plug_in_remove_temp_procedure (gimp_get_plug_in (), chooser->callback); g_clear_pointer (&chooser->callback, g_free); } G_OBJECT_CLASS (gimp_drawable_chooser_parent_class)->dispose (object); } static void gimp_drawable_chooser_finalize (GObject *object) { GimpDrawableChooser *chooser = GIMP_DRAWABLE_CHOOSER (object); g_clear_pointer (&chooser->title, g_free); g_clear_pointer (&chooser->label, g_free); g_clear_pointer (&chooser->thumbnail, g_bytes_unref); G_OBJECT_CLASS (gimp_drawable_chooser_parent_class)->finalize (object); } static void gimp_drawable_chooser_set_property (GObject *object, guint property_id, const GValue *gvalue, GParamSpec *pspec) { GimpDrawableChooser *chooser = GIMP_DRAWABLE_CHOOSER (object); switch (property_id) { case PROP_TITLE: chooser->title = g_value_dup_string (gvalue); break; case PROP_LABEL: chooser->label = g_value_dup_string (gvalue); break; case PROP_DRAWABLE: g_return_if_fail (g_value_get_object (gvalue) == NULL || g_type_is_a (G_TYPE_FROM_INSTANCE (g_value_get_object (gvalue)), chooser->drawable_type)); gimp_drawable_chooser_set_drawable (chooser, g_value_get_object (gvalue)); break; case PROP_DRAWABLE_TYPE: chooser->drawable_type = g_value_get_gtype (gvalue); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void gimp_drawable_chooser_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { GimpDrawableChooser *chooser = GIMP_DRAWABLE_CHOOSER (object); switch (property_id) { case PROP_TITLE: g_value_set_string (value, chooser->title); break; case PROP_LABEL: g_value_set_string (value, chooser->label); break; case PROP_DRAWABLE: g_value_set_object (value, chooser->drawable); break; case PROP_DRAWABLE_TYPE: g_value_set_gtype (value, chooser->drawable_type); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } /** * gimp_drawable_chooser_new: * @title: (nullable): Title of the dialog to use or %NULL to use the default title. * @label: (nullable): Button label or %NULL for no label. * @drawable_type: the acceptable subtype of choosable drawables. * @drawable: (nullable): Initial drawable. * * Creates a new #GtkWidget that lets a user choose a drawable which must be of * type @drawable_type. @drawable_type of values %G_TYPE_NONE and * %GIMP_TYPE_DRAWABLE are equivalent. Otherwise it must be a subtype of * %GIMP_TYPE_DRAWABLE. * * When @drawable is %NULL, initial choice is from context. * * Returns: A [class@GimpUi.DrawableChooser. * * Since: 3.0 */ GtkWidget * gimp_drawable_chooser_new (const gchar *title, const gchar *label, GType drawable_type, GimpDrawable *drawable) { GtkWidget *chooser; if (drawable_type == G_TYPE_NONE) drawable_type = GIMP_TYPE_DRAWABLE; g_return_val_if_fail (g_type_is_a (drawable_type, GIMP_TYPE_DRAWABLE), NULL); g_return_val_if_fail (drawable == NULL || g_type_is_a (G_TYPE_FROM_INSTANCE (drawable), drawable_type), NULL); chooser = g_object_new (GIMP_TYPE_DRAWABLE_CHOOSER, "title", title, "label", label, "drawable", drawable, "drawable-type", drawable_type, NULL); return chooser; } /** * gimp_drawable_chooser_get_drawable: * @chooser: A #GimpDrawableChooser * * Gets the currently selected drawable. * * Returns: (transfer none): an internal copy of the drawable which must not be freed. * * Since: 3.0 */ GimpDrawable * gimp_drawable_chooser_get_drawable (GimpDrawableChooser *chooser) { g_return_val_if_fail (GIMP_IS_DRAWABLE_CHOOSER (chooser), NULL); return chooser->drawable; } /** * gimp_drawable_chooser_set_drawable: * @chooser: A #GimpDrawableChooser * @drawable: Drawable to set. * * Sets the currently selected drawable. * This will select the drawable in both the button and any chooser popup. * * Since: 3.0 */ void gimp_drawable_chooser_set_drawable (GimpDrawableChooser *chooser, GimpDrawable *drawable) { g_return_if_fail (GIMP_IS_DRAWABLE_CHOOSER (chooser)); g_return_if_fail (drawable == NULL || GIMP_IS_DRAWABLE (drawable)); chooser->drawable = drawable; if (chooser->callback) gimp_drawables_set_popup (chooser->callback, chooser->drawable); g_object_notify_by_pspec (G_OBJECT (chooser), drawable_button_props[PROP_DRAWABLE]); gimp_drawable_chooser_draw (chooser); } /** * gimp_drawable_chooser_get_label: * @widget: A [class@DrawableChooser]. * * Returns the label widget. * * Returns: (transfer none): the [class@Gtk.Widget] showing the label text. * Since: 3.0 */ GtkWidget * gimp_drawable_chooser_get_label (GimpDrawableChooser *chooser) { g_return_val_if_fail (GIMP_IS_DRAWABLE_CHOOSER (chooser), NULL); return chooser->label_widget; } /* private functions */ static GimpValueArray * gimp_temp_callback_run (GimpProcedure *procedure, GimpProcedureConfig *config, GimpDrawableChooser *chooser) { GimpDrawable *drawable; gboolean closing; g_object_get (config, "drawable", &drawable, "closing", &closing, NULL); g_object_set (chooser, "drawable", drawable, NULL); if (closing) { g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, (GSourceFunc) gimp_drawable_select_remove_after_run, g_strdup (gimp_procedure_get_name (procedure)), g_free); g_clear_pointer (&chooser->callback, g_free); } g_clear_object (&drawable); return gimp_procedure_new_return_values (procedure, GIMP_PDB_SUCCESS, NULL); } static gboolean gimp_drawable_select_remove_after_run (const gchar *procedure_name) { gimp_plug_in_remove_temp_procedure (gimp_get_plug_in (), procedure_name); return G_SOURCE_REMOVE; } static void gimp_drawable_chooser_clicked (GimpDrawableChooser *chooser) { if (chooser->callback) { /* Popup already created. Calling setter raises the popup. */ gimp_drawables_set_popup (chooser->callback, chooser->drawable); } else { GimpPlugIn *plug_in = gimp_get_plug_in (); GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (chooser)); GBytes *handle = NULL; gchar *callback_name; GimpProcedure *callback_procedure; if (GIMP_IS_DIALOG (toplevel)) handle = gimp_dialog_get_native_handle (GIMP_DIALOG (toplevel)); callback_name = gimp_pdb_temp_procedure_name (gimp_get_pdb ()); callback_procedure = gimp_procedure_new (plug_in, callback_name, GIMP_PDB_PROC_TYPE_TEMPORARY, (GimpRunFunc) gimp_temp_callback_run, g_object_ref (chooser), (GDestroyNotify) g_object_unref); gimp_procedure_add_drawable_argument (callback_procedure, "drawable", "Drawable", "The selected drawable", TRUE, G_PARAM_READWRITE); gimp_procedure_add_boolean_argument (callback_procedure, "closing", "Closing", "If the dialog was closing", FALSE, G_PARAM_READWRITE); gimp_plug_in_add_temp_procedure (plug_in, callback_procedure); g_object_unref (callback_procedure); g_free (callback_name); if (gimp_drawables_popup (gimp_procedure_get_name (callback_procedure), chooser->title, g_type_name (chooser->drawable_type), chooser->drawable, handle)) { /* Allow callbacks to be watched */ gimp_plug_in_extension_enable (plug_in); chooser->callback = g_strdup (gimp_procedure_get_name (callback_procedure)); } else { g_warning ("%s: failed to open remote drawable select dialog.", G_STRFUNC); gimp_plug_in_remove_temp_procedure (plug_in, gimp_procedure_get_name (callback_procedure)); return; } gimp_drawables_set_popup (chooser->callback, chooser->drawable); } } static void gimp_drawable_chooser_draw (GimpDrawableChooser *chooser) { GimpPreviewArea *area = GIMP_PREVIEW_AREA (chooser->preview); GtkAllocation allocation; gint x = 0; gint y = 0; gtk_widget_get_allocation (chooser->preview, &allocation); gimp_drawable_chooser_get_thumbnail (chooser, allocation.width, allocation.height); if (chooser->width < allocation.width || chooser->height < allocation.height) { x = ((allocation.width - chooser->width) / 2); y = ((allocation.height - chooser->height) / 2); } gimp_preview_area_reset (area); if (chooser->thumbnail) { GimpImageType type; gint rowstride; rowstride = chooser->width * chooser->bpp; switch (chooser->bpp) { case 1: type = GIMP_GRAY_IMAGE; break; case 2: type = GIMP_GRAYA_IMAGE; break; case 3: type = GIMP_RGB_IMAGE; break; case 4: type = GIMP_RGBA_IMAGE; break; default: g_return_if_reached (); } gimp_preview_area_draw (area, x, y, chooser->width, chooser->height, type, g_bytes_get_data (chooser->thumbnail, NULL), rowstride); } } static void gimp_drawable_chooser_get_thumbnail (GimpDrawableChooser *chooser, gint width, gint height) { if (chooser->drawable == chooser->thumbnail_drawable && chooser->width == width && chooser->height == height) /* Let's assume drawable contents is not changing in a single run. */ return; g_clear_pointer (&chooser->thumbnail, g_bytes_unref); chooser->width = chooser->height = 0; chooser->thumbnail_drawable = chooser->drawable; if (chooser->drawable) chooser->thumbnail = gimp_drawable_get_thumbnail_data (chooser->drawable, width, height, &chooser->width, &chooser->height, &chooser->bpp); }