From a9dc4ddb2ce123172fe163fe29e52f50e7551a5b Mon Sep 17 00:00:00 2001 From: Alx Sa Date: Wed, 12 Nov 2025 03:12:24 +0000 Subject: [PATCH] widgets, pdb, libgimp: GimpProcedureDialog Image Widget This patch adds a GimpImageChooser widget for use in GimpProcedureDialog Image parameter GUI creation. --- app/core/gimp-gui.c | 9 +- app/gui/gui-vtable.c | 14 + app/pdb/image-select-cmds.c | 186 +++++++++++ app/pdb/internal-procs.c | 2 +- app/widgets/gimpimagechooser.c | 370 +++++++++++++++++++++ app/widgets/gimpimagechooser.h | 60 ++++ app/widgets/gimpimageselect.c | 163 ++++++++++ app/widgets/gimpimageselect.h | 48 +++ app/widgets/meson.build | 2 + app/widgets/widgets-types.h | 2 + libgimp/gimp.def | 3 + libgimp/gimpimagechooser.c | 573 +++++++++++++++++++++++++++++++++ libgimp/gimpimagechooser.h | 46 +++ libgimp/gimpimageselect_pdb.c | 111 +++++++ libgimp/gimpimageselect_pdb.h | 7 + libgimp/gimpproceduredialog.c | 10 + libgimp/gimppropwidgets.c | 60 ++++ libgimp/gimppropwidgets.h | 3 + libgimp/gimpui.def | 6 + libgimp/gimpui.h | 1 + libgimp/gimpuitypes.h | 1 + libgimp/meson.build | 2 + pdb/groups/image_select.pdb | 87 ++++- 23 files changed, 1761 insertions(+), 5 deletions(-) create mode 100644 app/widgets/gimpimagechooser.c create mode 100644 app/widgets/gimpimagechooser.h create mode 100644 app/widgets/gimpimageselect.c create mode 100644 app/widgets/gimpimageselect.h create mode 100644 libgimp/gimpimagechooser.c create mode 100644 libgimp/gimpimagechooser.h diff --git a/app/core/gimp-gui.c b/app/core/gimp-gui.c index 140813b982..3e6c4e5248 100644 --- a/app/core/gimp-gui.c +++ b/app/core/gimp-gui.c @@ -418,7 +418,8 @@ gimp_pdb_dialog_new (Gimp *gimp, g_return_val_if_fail (GIMP_IS_CONTEXT (context), FALSE); g_return_val_if_fail (progress == NULL || GIMP_IS_PROGRESS (progress), FALSE); g_return_val_if_fail (g_type_is_a (contents_type, GIMP_TYPE_RESOURCE) || - g_type_is_a (contents_type, GIMP_TYPE_DRAWABLE), FALSE); + g_type_is_a (contents_type, GIMP_TYPE_DRAWABLE) || + g_type_is_a (contents_type, GIMP_TYPE_IMAGE), FALSE); g_return_val_if_fail (object == NULL || g_type_is_a (G_TYPE_FROM_INSTANCE (object), contents_type), FALSE); g_return_val_if_fail (title != NULL, FALSE); @@ -451,7 +452,8 @@ gimp_pdb_dialog_set (Gimp *gimp, g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE); g_return_val_if_fail (g_type_is_a (contents_type, GIMP_TYPE_RESOURCE) || - contents_type == GIMP_TYPE_DRAWABLE, FALSE); + contents_type == GIMP_TYPE_DRAWABLE || + contents_type == GIMP_TYPE_IMAGE, FALSE); g_return_val_if_fail (callback_name != NULL, FALSE); g_return_val_if_fail (object == NULL || g_type_is_a (G_TYPE_FROM_INSTANCE (object), contents_type), FALSE); @@ -477,7 +479,8 @@ gimp_pdb_dialog_close (Gimp *gimp, { g_return_val_if_fail (GIMP_IS_GIMP (gimp), FALSE); g_return_val_if_fail (g_type_is_a (contents_type, GIMP_TYPE_RESOURCE) || - contents_type == GIMP_TYPE_DRAWABLE, FALSE); + contents_type == GIMP_TYPE_DRAWABLE || + contents_type == GIMP_TYPE_IMAGE, FALSE); g_return_val_if_fail (callback_name != NULL, FALSE); if (gimp->gui.pdb_dialog_close) diff --git a/app/gui/gui-vtable.c b/app/gui/gui-vtable.c index 6d5b6ebae5..090d1d48d4 100644 --- a/app/gui/gui-vtable.c +++ b/app/gui/gui-vtable.c @@ -84,6 +84,8 @@ #include "widgets/gimpgradientselect.h" #include "widgets/gimphelp.h" #include "widgets/gimphelp-ids.h" +#include "widgets/gimpimageselect.h" +#include "widgets/gimpimageview.h" #include "widgets/gimpmenufactory.h" #include "widgets/gimppaletteselect.h" #include "widgets/gimppatternselect.h" @@ -643,6 +645,12 @@ gui_pdb_dialog_new (Gimp *gimp, dialog_role = "gimp-gradient-selection"; help_id = GIMP_HELP_GRADIENT_DIALOG; } + else if (contents_type == GIMP_TYPE_IMAGE) + { + dialog_type = GIMP_TYPE_IMAGE_SELECT; + dialog_role = "gimp-image-selection"; + help_id = GIMP_HELP_IMAGE_DIALOG; + } else if (contents_type == GIMP_TYPE_PALETTE) { dialog_type = GIMP_TYPE_PALETTE_SELECT; @@ -768,6 +776,10 @@ gui_pdb_dialog_set (Gimp *gimp, klass = g_type_class_peek (GIMP_TYPE_GRADIENT_SELECT); container = gimp_data_factory_get_container (gimp->gradient_factory); } + else if (contents_type == GIMP_TYPE_IMAGE) + { + klass = g_type_class_peek (GIMP_TYPE_IMAGE_SELECT); + } else if (contents_type == GIMP_TYPE_PALETTE) { klass = g_type_class_peek (GIMP_TYPE_PALETTE_SELECT); @@ -828,6 +840,8 @@ gui_pdb_dialog_close (Gimp *gimp, klass = g_type_class_peek (GIMP_TYPE_FONT_SELECT); else if (contents_type == GIMP_TYPE_GRADIENT) klass = g_type_class_peek (GIMP_TYPE_GRADIENT_SELECT); + else if (contents_type == GIMP_TYPE_IMAGE) + klass = g_type_class_peek (GIMP_TYPE_IMAGE_SELECT); else if (contents_type == GIMP_TYPE_PALETTE) klass = g_type_class_peek (GIMP_TYPE_PALETTE_SELECT); else if (contents_type == GIMP_TYPE_PATTERN) diff --git a/app/pdb/image-select-cmds.c b/app/pdb/image-select-cmds.c index b03cea35da..0c7e9942d3 100644 --- a/app/pdb/image-select-cmds.c +++ b/app/pdb/image-select-cmds.c @@ -34,7 +34,9 @@ #include "pdb-types.h" +#include "core/gimp.h" #include "core/gimpchannel-select.h" +#include "core/gimpdatafactory.h" #include "core/gimpdrawable.h" #include "core/gimpimage.h" #include "core/gimpitem.h" @@ -354,6 +356,93 @@ image_select_item_invoker (GimpProcedure *procedure, error ? *error : NULL); } +static GimpValueArray * +images_popup_invoker (GimpProcedure *procedure, + Gimp *gimp, + GimpContext *context, + GimpProgress *progress, + const GimpValueArray *args, + GError **error) +{ + gboolean success = TRUE; + const gchar *callback; + const gchar *popup_title; + GimpImage *initial_image; + GBytes *parent_window; + + callback = g_value_get_string (gimp_value_array_index (args, 0)); + popup_title = g_value_get_string (gimp_value_array_index (args, 1)); + initial_image = g_value_get_object (gimp_value_array_index (args, 2)); + parent_window = g_value_get_boxed (gimp_value_array_index (args, 3)); + + if (success) + { + if (gimp->no_interface || + ! gimp_pdb_lookup_procedure (gimp->pdb, callback) || + ! gimp_pdb_dialog_new (gimp, context, progress, + GIMP_TYPE_IMAGE, + parent_window, popup_title, callback, + GIMP_OBJECT (initial_image), + NULL)) + success = FALSE; + } + + return gimp_procedure_get_return_values (procedure, success, + error ? *error : NULL); +} + +static GimpValueArray * +images_close_popup_invoker (GimpProcedure *procedure, + Gimp *gimp, + GimpContext *context, + GimpProgress *progress, + const GimpValueArray *args, + GError **error) +{ + gboolean success = TRUE; + const gchar *callback; + + callback = g_value_get_string (gimp_value_array_index (args, 0)); + + if (success) + { + if (gimp->no_interface || + ! gimp_pdb_lookup_procedure (gimp->pdb, callback) || + ! gimp_pdb_dialog_close (gimp, GIMP_TYPE_IMAGE, callback)) + success = FALSE; + } + + return gimp_procedure_get_return_values (procedure, success, + error ? *error : NULL); +} + +static GimpValueArray * +images_set_popup_invoker (GimpProcedure *procedure, + Gimp *gimp, + GimpContext *context, + GimpProgress *progress, + const GimpValueArray *args, + GError **error) +{ + gboolean success = TRUE; + const gchar *callback; + GimpImage *image; + + callback = g_value_get_string (gimp_value_array_index (args, 0)); + image = g_value_get_object (gimp_value_array_index (args, 1)); + + if (success) + { + if (gimp->no_interface || + ! gimp_pdb_lookup_procedure (gimp->pdb, callback) || + ! gimp_pdb_dialog_set (gimp, GIMP_TYPE_IMAGE, callback, GIMP_OBJECT (image), NULL)) + success = FALSE; + } + + return gimp_procedure_get_return_values (procedure, success, + error ? *error : NULL); +} + void register_image_select_procs (GimpPDB *pdb) { @@ -712,4 +801,101 @@ register_image_select_procs (GimpPDB *pdb) GIMP_PARAM_READWRITE)); gimp_pdb_register_procedure (pdb, procedure); g_object_unref (procedure); + + /* + * gimp-images-popup + */ + procedure = gimp_procedure_new (images_popup_invoker, FALSE); + gimp_object_set_static_name (GIMP_OBJECT (procedure), + "gimp-images-popup"); + gimp_procedure_set_static_help (procedure, + "Invokes the image selection dialog.", + "Opens a dialog letting a user choose an image.", + NULL); + gimp_procedure_set_static_attribution (procedure, + "Alex S.", + "Alex S.", + "2025"); + gimp_procedure_add_argument (procedure, + gimp_param_spec_string ("callback", + "callback", + "The callback PDB proc to call when user chooses an image", + FALSE, FALSE, TRUE, + NULL, + GIMP_PARAM_READWRITE)); + gimp_procedure_add_argument (procedure, + gimp_param_spec_string ("popup-title", + "popup title", + "Title of the image selection dialog", + FALSE, FALSE, FALSE, + NULL, + GIMP_PARAM_READWRITE)); + gimp_procedure_add_argument (procedure, + gimp_param_spec_image ("initial-image", + "initial image", + "The image to set as the initial choice", + TRUE, + GIMP_PARAM_READWRITE | GIMP_PARAM_NO_VALIDATE)); + gimp_procedure_add_argument (procedure, + g_param_spec_boxed ("parent-window", + "parent window", + "An optional parent window handle for the popup to be set transient to", + G_TYPE_BYTES, + GIMP_PARAM_READWRITE)); + gimp_pdb_register_procedure (pdb, procedure); + g_object_unref (procedure); + + /* + * gimp-images-close-popup + */ + procedure = gimp_procedure_new (images_close_popup_invoker, FALSE); + gimp_object_set_static_name (GIMP_OBJECT (procedure), + "gimp-images-close-popup"); + gimp_procedure_set_static_help (procedure, + "Close the image selection dialog.", + "Closes an open image selection dialog.", + NULL); + gimp_procedure_set_static_attribution (procedure, + "Alex S.", + "Alex S.", + "2025"); + gimp_procedure_add_argument (procedure, + gimp_param_spec_string ("callback", + "callback", + "The name of the callback registered for this pop-up", + FALSE, FALSE, TRUE, + NULL, + GIMP_PARAM_READWRITE)); + gimp_pdb_register_procedure (pdb, procedure); + g_object_unref (procedure); + + /* + * gimp-images-set-popup + */ + procedure = gimp_procedure_new (images_set_popup_invoker, FALSE); + gimp_object_set_static_name (GIMP_OBJECT (procedure), + "gimp-images-set-popup"); + gimp_procedure_set_static_help (procedure, + "Sets the selected image in a image selection dialog.", + "Sets the selected image in a image selection dialog.", + NULL); + gimp_procedure_set_static_attribution (procedure, + "Alex S.", + "Alex S.", + "2025"); + gimp_procedure_add_argument (procedure, + gimp_param_spec_string ("callback", + "callback", + "The name of the callback registered for this pop-up", + FALSE, FALSE, TRUE, + NULL, + GIMP_PARAM_READWRITE)); + gimp_procedure_add_argument (procedure, + gimp_param_spec_image ("image", + "image", + "The image to set as selected", + FALSE, + GIMP_PARAM_READWRITE | GIMP_PARAM_NO_VALIDATE)); + gimp_pdb_register_procedure (pdb, procedure); + g_object_unref (procedure); } diff --git a/app/pdb/internal-procs.c b/app/pdb/internal-procs.c index 7745233b04..a6aa965fc4 100644 --- a/app/pdb/internal-procs.c +++ b/app/pdb/internal-procs.c @@ -30,7 +30,7 @@ #include "internal-procs.h" -/* 779 procedures registered total */ +/* 782 procedures registered total */ void internal_procs_init (GimpPDB *pdb) diff --git a/app/widgets/gimpimagechooser.c b/app/widgets/gimpimagechooser.c new file mode 100644 index 0000000000..f10b28097e --- /dev/null +++ b/app/widgets/gimpimagechooser.c @@ -0,0 +1,370 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpimagechooser.c + * + * 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 "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpimage.h" +#include "core/gimpviewable.h" + +#include "gimpcontainertreeview.h" +#include "gimpcontainerview.h" +#include "gimpimagechooser.h" +#include "gimpviewrenderer.h" + +#include "gimp-intl.h" + + +enum +{ + ACTIVATE, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_CONTEXT, + PROP_IMAGE, + PROP_VIEW_SIZE, + PROP_VIEW_BORDER_WIDTH +}; + +struct _GimpImageChooserPrivate +{ + GimpImage *image; + GimpContext *context; + + gint view_size; + gint view_border_width; + + GtkWidget *image_view; +}; + + +static void gimp_image_chooser_constructed (GObject *object); +static void gimp_image_chooser_finalize (GObject *object); +static void gimp_image_chooser_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_image_chooser_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_image_chooser_item_activated (GimpContainerView *view, + GimpImage *image, + GimpImageChooser *chooser); +static void gimp_image_chooser_items_selected (GimpContainerView *view, + GimpImageChooser *chooser); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpImageChooser, gimp_image_chooser, GTK_TYPE_FRAME) + +#define parent_class gimp_image_chooser_parent_class + +static guint signals[LAST_SIGNAL]; + +static void +gimp_image_chooser_class_init (GimpImageChooserClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_image_chooser_constructed; + object_class->finalize = gimp_image_chooser_finalize; + object_class->get_property = gimp_image_chooser_get_property; + object_class->set_property = gimp_image_chooser_set_property; + + /** + * GimpImageChooser::activate: + * @chooser: + * + * Emitted when an image is activated, which is mostly forwarding when + * "item-activated" signal is emitted from the image view. + */ + signals[ACTIVATE] = + g_signal_new ("activate", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpImageChooserClass, activate), + NULL, NULL, NULL, + G_TYPE_NONE, 1, + GIMP_TYPE_OBJECT); + + g_object_class_install_property (object_class, PROP_CONTEXT, + g_param_spec_object ("context", + NULL, NULL, + GIMP_TYPE_CONTEXT, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_IMAGE, + g_param_spec_object ("image", + NULL, NULL, + GIMP_TYPE_IMAGE, + GIMP_PARAM_READWRITE | + G_PARAM_EXPLICIT_NOTIFY)); + + g_object_class_install_property (object_class, PROP_VIEW_SIZE, + g_param_spec_int ("view-size", + NULL, NULL, + 1, GIMP_VIEWABLE_MAX_PREVIEW_SIZE, + GIMP_VIEW_SIZE_MEDIUM, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); + + g_object_class_install_property (object_class, PROP_VIEW_BORDER_WIDTH, + g_param_spec_int ("view-border-width", + NULL, NULL, + 0, + GIMP_VIEW_MAX_BORDER_WIDTH, + 1, + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT)); +} + +static void +gimp_image_chooser_init (GimpImageChooser *chooser) +{ + chooser->priv = gimp_image_chooser_get_instance_private (chooser); + + chooser->priv->view_size = GIMP_VIEW_SIZE_SMALL; + chooser->priv->view_border_width = 1; +} + +static void +gimp_image_chooser_constructed (GObject *object) +{ + GimpImageChooser *chooser = GIMP_IMAGE_CHOOSER (object); + GtkWidget *hbox; + GtkWidget *vbox; + GtkWidget *label; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + gimp_assert (GIMP_IS_CONTEXT (chooser->priv->context)); + + gtk_frame_set_shadow_type (GTK_FRAME (chooser), GTK_SHADOW_OUT); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 6); + gtk_container_add (GTK_CONTAINER (chooser), hbox); + gtk_widget_show (hbox); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 4); + gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0); + gtk_widget_show (vbox); + + label = gtk_label_new (_("Images")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + chooser->priv->image_view = + gimp_container_tree_view_new (chooser->priv->context->gimp->images, + chooser->priv->context, + chooser->priv->view_size, + chooser->priv->view_border_width); + gimp_container_box_set_size_request (GIMP_CONTAINER_BOX (chooser->priv->image_view), + 4 * (chooser->priv->view_size + + 2 * chooser->priv->view_border_width), + 4 * (chooser->priv->view_size + + 2 * chooser->priv->view_border_width)); + gtk_box_pack_start (GTK_BOX (vbox), chooser->priv->image_view, TRUE, TRUE, 0); + gtk_widget_set_visible (chooser->priv->image_view, TRUE); + + g_signal_connect_object (chooser->priv->image_view, "item-activated", + G_CALLBACK (gimp_image_chooser_item_activated), + G_OBJECT (chooser), 0); + g_signal_connect_object (chooser->priv->image_view, "selection-changed", + G_CALLBACK (gimp_image_chooser_items_selected), + G_OBJECT (chooser), 0); +} + +static void +gimp_image_chooser_finalize (GObject *object) +{ + GimpImageChooser *chooser = GIMP_IMAGE_CHOOSER (object); + + g_clear_object (&chooser->priv->image); + g_clear_object (&chooser->priv->context); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_image_chooser_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpImageChooser *chooser = GIMP_IMAGE_CHOOSER (object); + + switch (property_id) + { + case PROP_CONTEXT: + chooser->priv->context = g_value_dup_object (value); + break; + + case PROP_VIEW_SIZE: + chooser->priv->view_size = g_value_get_int (value); + break; + + case PROP_VIEW_BORDER_WIDTH: + chooser->priv->view_border_width = g_value_get_int (value); + break; + + case PROP_IMAGE: + gimp_image_chooser_set_image (chooser, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_image_chooser_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpImageChooser *chooser = GIMP_IMAGE_CHOOSER (object); + + switch (property_id) + { + case PROP_CONTEXT: + g_value_set_object (value, chooser->priv->context); + break; + + case PROP_IMAGE: + g_value_set_object (value, chooser->priv->image); + break; + + case PROP_VIEW_SIZE: + g_value_set_int (value, chooser->priv->view_size); + break; + + case PROP_VIEW_BORDER_WIDTH: + g_value_set_int (value, chooser->priv->view_border_width); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +GtkWidget * +gimp_image_chooser_new (GimpContext *context, + gint view_size, + gint view_border_width) +{ + g_return_val_if_fail (GIMP_IS_CONTEXT (context), NULL); + g_return_val_if_fail (view_size > 0 && + view_size <= GIMP_VIEWABLE_MAX_POPUP_SIZE, NULL); + g_return_val_if_fail (view_border_width >= 0 && + view_border_width <= GIMP_VIEW_MAX_BORDER_WIDTH, + NULL); + + return g_object_new (GIMP_TYPE_IMAGE_CHOOSER, + "context", context, + "view-size", view_size, + "view-border-width", view_border_width, + NULL); +} + +GimpImage * +gimp_image_chooser_get_image (GimpImageChooser *chooser) +{ + g_return_val_if_fail (GIMP_IS_IMAGE_CHOOSER (chooser), NULL); + + return chooser->priv->image; +} + +void +gimp_image_chooser_set_image (GimpImageChooser *chooser, + GimpImage *image) +{ + if (! gtk_widget_in_destruction (GTK_WIDGET (chooser))) + { + g_signal_handlers_disconnect_by_func (chooser->priv->image_view, + G_CALLBACK (gimp_image_chooser_items_selected), + chooser); + + if (GIMP_IS_IMAGE (image)) + { + gimp_context_set_image (chooser->priv->context, image); + gimp_container_view_set_1_selected (GIMP_CONTAINER_VIEW (chooser->priv->image_view), + GIMP_VIEWABLE (image)); + } + + g_signal_connect_object (chooser->priv->image_view, "selection-changed", + G_CALLBACK (gimp_image_chooser_items_selected), + G_OBJECT (chooser), 0); + } + + if (image != chooser->priv->image) + { + g_clear_object (&chooser->priv->image); + chooser->priv->image = (image != NULL ? g_object_ref (image) : NULL); + g_object_notify (G_OBJECT (chooser), "image"); + } +} + + +/* private functions */ + +static void +gimp_image_chooser_item_activated (GimpContainerView *view, + GimpImage *image, + GimpImageChooser *chooser) +{ + g_signal_emit (chooser, signals[ACTIVATE], 0, image); +} + +static void +gimp_image_chooser_items_selected (GimpContainerView *view, + GimpImageChooser *chooser) +{ + GimpImage *image = NULL; + gint n_items; + GList *items; + + n_items = gimp_container_view_get_selected (view, &items); + + g_return_if_fail (n_items <= 1); + + if (items) + image = items->data; + + gimp_image_chooser_set_image (chooser, image); +} diff --git a/app/widgets/gimpimagechooser.h b/app/widgets/gimpimagechooser.h new file mode 100644 index 0000000000..ff1729fe30 --- /dev/null +++ b/app/widgets/gimpimagechooser.h @@ -0,0 +1,60 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpimagechooser.h + * + * 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 . + */ + +#pragma once + + +#define GIMP_TYPE_IMAGE_CHOOSER (gimp_image_chooser_get_type ()) +#define GIMP_IMAGE_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_IMAGE_CHOOSER, GimpImageChooser)) +#define GIMP_IMAGE_CHOOSER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_IMAGE_CHOOSER, GimpImageChooserClass)) +#define GIMP_IS_IMAGE_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_IMAGE_CHOOSER)) +#define GIMP_IS_IMAGE_CHOOSER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_IMAGE_CHOOSER)) +#define GIMP_IMAGE_CHOOSER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_IMAGE_CHOOSER, GimpImageChooserClass)) + + +typedef struct _GimpImageChooserPrivate GimpImageChooserPrivate; +typedef struct _GimpImageChooserClass GimpImageChooserClass; + +struct _GimpImageChooser +{ + GtkFrame parent_instance; + + GimpImageChooserPrivate *priv; +}; + +struct _GimpImageChooserClass +{ + GtkFrameClass parent_instance; + + /* Signals. */ + + void (* activate) (GimpImageChooser *view, + GimpImage *image); +}; + + +GType gimp_image_chooser_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_image_chooser_new (GimpContext *context, + gint view_size, + gint view_border_width); + +GimpImage * gimp_image_chooser_get_image (GimpImageChooser *chooser); +void gimp_image_chooser_set_image (GimpImageChooser *chooser, + GimpImage *image); diff --git a/app/widgets/gimpimageselect.c b/app/widgets/gimpimageselect.c new file mode 100644 index 0000000000..8533ab23d5 --- /dev/null +++ b/app/widgets/gimpimageselect.c @@ -0,0 +1,163 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpimageselect.c + * + * 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 "libgimpbase/gimpbase.h" +#include "libgimpwidgets/gimpwidgets.h" + +#include "widgets-types.h" + +#include "gegl/gimp-babl-compat.h" + +#include "core/gimp.h" +#include "core/gimpcontext.h" +#include "core/gimpimage.h" +#include "core/gimpparamspecs.h" +#include "core/gimptempbuf.h" + +#include "pdb/gimppdb.h" + +#include "gimpcontainerbox.h" +#include "gimpcontainerview.h" +#include "gimpimagechooser.h" +#include "gimpimageselect.h" +#include "gimplayermodebox.h" + +#include "gimp-intl.h" + + +static void gimp_image_select_constructed (GObject *object); + +static GimpValueArray * gimp_image_select_run_callback (GimpPdbDialog *dialog, + GimpObject *object, + gboolean closing, + GError **error); +static GimpObject * gimp_image_select_get_object (GimpPdbDialog *dialog); +static void gimp_image_select_set_object (GimpPdbDialog *dialog, + GimpObject *object); + + +static void gimp_image_select_activate (GimpImageSelect *select); +static void gimp_image_select_notify_image (GimpImageSelect *select); + +G_DEFINE_TYPE (GimpImageSelect, gimp_image_select, GIMP_TYPE_PDB_DIALOG) + +#define parent_class gimp_image_select_parent_class + + +static void +gimp_image_select_class_init (GimpImageSelectClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpPdbDialogClass *pdb_class = GIMP_PDB_DIALOG_CLASS (klass); + + object_class->constructed = gimp_image_select_constructed; + + pdb_class->run_callback = gimp_image_select_run_callback; + pdb_class->set_object = gimp_image_select_set_object; + pdb_class->get_object = gimp_image_select_get_object; +} + +static void +gimp_image_select_init (GimpImageSelect *select) +{ +} + +static void +gimp_image_select_constructed (GObject *object) +{ + GimpPdbDialog *dialog = GIMP_PDB_DIALOG (object); + GimpImageSelect *select = GIMP_IMAGE_SELECT (object); + GtkWidget *content_area; + + G_OBJECT_CLASS (parent_class)->constructed (object); + + content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + + select->chooser = gimp_image_chooser_new (dialog->context, + GIMP_VIEW_SIZE_LARGE, 1); + gimp_image_chooser_set_image (GIMP_IMAGE_CHOOSER (select->chooser), + GIMP_IMAGE (dialog->initial_object)); + g_signal_connect_swapped (select->chooser, "notify::image", + G_CALLBACK (gimp_image_select_notify_image), + select); + g_signal_connect_swapped (select->chooser, "activate", + G_CALLBACK (gimp_image_select_activate), + select); + + gtk_box_pack_start (GTK_BOX (content_area), select->chooser, TRUE, TRUE, 0); + gtk_widget_set_visible (select->chooser, TRUE); +} + +static GimpValueArray * +gimp_image_select_run_callback (GimpPdbDialog *dialog, + GimpObject *object, + gboolean closing, + GError **error) +{ + GimpImage *image = GIMP_IMAGE (object); + GimpValueArray *return_vals; + + return_vals = + gimp_pdb_execute_procedure_by_name (dialog->pdb, + dialog->caller_context, + NULL, error, + dialog->callback_name, + GIMP_TYPE_IMAGE, image, + G_TYPE_BOOLEAN, closing, + G_TYPE_NONE); + + return return_vals; +} + +static GimpObject * +gimp_image_select_get_object (GimpPdbDialog *dialog) +{ + GimpImageSelect *select = GIMP_IMAGE_SELECT (dialog); + + return (GimpObject *) gimp_image_chooser_get_image (GIMP_IMAGE_CHOOSER (select->chooser)); + +} + +static void +gimp_image_select_set_object (GimpPdbDialog *dialog, + GimpObject *object) +{ + GimpImageSelect *select = GIMP_IMAGE_SELECT (dialog); + + gimp_image_chooser_set_image (GIMP_IMAGE_CHOOSER (select->chooser), GIMP_IMAGE (object)); +} + + +static void +gimp_image_select_activate (GimpImageSelect *select) +{ + gimp_pdb_dialog_run_callback ((GimpPdbDialog **) &select, TRUE); + gtk_widget_destroy (GTK_WIDGET (select)); +} + +static void +gimp_image_select_notify_image (GimpImageSelect *select) +{ + gimp_pdb_dialog_run_callback ((GimpPdbDialog **) &select, FALSE); +} diff --git a/app/widgets/gimpimageselect.h b/app/widgets/gimpimageselect.h new file mode 100644 index 0000000000..596299b3f4 --- /dev/null +++ b/app/widgets/gimpimageselect.h @@ -0,0 +1,48 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpimageselect.h + * + * 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 . + */ + +#pragma once + +#include "gimppdbdialog.h" + + +#define GIMP_TYPE_IMAGE_SELECT (gimp_image_select_get_type ()) +#define GIMP_IMAGE_SELECT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_IMAGE_SELECT, GimpImageSelect)) +#define GIMP_IMAGE_SELECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_IMAGE_SELECT, GimpImageSelectClass)) +#define GIMP_IS_IMAGE_SELECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_IMAGE_SELECT)) +#define GIMP_IS_IMAGE_SELECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_IMAGE_SELECT)) +#define GIMP_IMAGE_SELECT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_IMAGE_SELECT, GimpImageSelectClass)) + + +typedef struct _GimpImageSelectClass GimpImageSelectClass; + +struct _GimpImageSelect +{ + GimpPdbDialog parent_instance; + + GtkWidget *chooser; +}; + +struct _GimpImageSelectClass +{ + GimpPdbDialogClass parent_class; +}; + + +GType gimp_image_select_get_type (void) G_GNUC_CONST; diff --git a/app/widgets/meson.build b/app/widgets/meson.build index d4f10287f5..8b63b0fe63 100644 --- a/app/widgets/meson.build +++ b/app/widgets/meson.build @@ -137,11 +137,13 @@ libappwidgets_sources = [ 'gimphistogrameditor.c', 'gimphistogramview.c', 'gimpiconpicker.c', + 'gimpimagechooser.c', 'gimpimagecommenteditor.c', 'gimpimageeditor.c', 'gimpimageparasiteview.c', 'gimpimageprofileview.c', 'gimpimagepropview.c', + 'gimpimageselect.c', 'gimpimageview.c', 'gimpitemtreeview.c', 'gimpitemtreeview-search.c', diff --git a/app/widgets/widgets-types.h b/app/widgets/widgets-types.h index 8806d82975..7dc5fb43b3 100644 --- a/app/widgets/widgets-types.h +++ b/app/widgets/widgets-types.h @@ -162,6 +162,7 @@ typedef struct _GimpViewableDialog GimpViewableDialog; typedef struct _GimpBrushSelect GimpBrushSelect; typedef struct _GimpFontSelect GimpFontSelect; typedef struct _GimpGradientSelect GimpGradientSelect; +typedef struct _GimpImageSelect GimpImageSelect; typedef struct _GimpPaletteSelect GimpPaletteSelect; typedef struct _GimpPatternSelect GimpPatternSelect; typedef struct _GimpPickableSelect GimpPickableSelect; @@ -204,6 +205,7 @@ typedef struct _GimpHighlightableButton GimpHighlightableButton; typedef struct _GimpHistogramBox GimpHistogramBox; typedef struct _GimpHistogramView GimpHistogramView; typedef struct _GimpIconPicker GimpIconPicker; +typedef struct _GimpImageChooser GimpImageChooser; typedef struct _GimpImageCommentEditor GimpImageCommentEditor; typedef struct _GimpImageParasiteView GimpImageParasiteView; typedef struct _GimpImageProfileView GimpImageProfileView; diff --git a/libgimp/gimp.def b/libgimp/gimp.def index 733854efa5..e0eb48909a 100644 --- a/libgimp/gimp.def +++ b/libgimp/gimp.def @@ -579,6 +579,9 @@ EXPORTS gimp_image_undo_is_enabled gimp_image_undo_thaw gimp_image_unset_active_channel + gimp_images_close_popup + gimp_images_popup + gimp_images_set_popup gimp_item_attach_parasite gimp_item_delete gimp_item_detach_parasite diff --git a/libgimp/gimpimagechooser.c b/libgimp/gimpimagechooser.c new file mode 100644 index 0000000000..7fda6af419 --- /dev/null +++ b/libgimp/gimpimagechooser.c @@ -0,0 +1,573 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpimagechooser.c + * + * 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 "gimpimagechooser.h" +#include "gimpuimarshal.h" + +#include "libgimp-intl.h" + + +/** + * SECTION: gimpimagechooser + * @title: GimpImageChooser + * @short_description: A widget allowing to select a image. + * + * The chooser contains an optional label and a button which queries the core + * process to pop up an image selection dialog. + * + * Since: 3.0 + **/ + +#define CELL_SIZE 40 +#define BUTTON_SIZE 96 + +enum +{ + PROP_0, + PROP_TITLE, + PROP_LABEL, + PROP_IMAGE, + N_PROPS +}; + +struct _GimpImageChooser +{ + GtkBox parent_instance; + + GimpImage *image; + gchar *title; + gchar *label; + gchar *callback; + + GBytes *thumbnail; + gint width; + gint height; + gint bpp; + + GtkWidget *label_widget; + GtkWidget *preview_frame; + GtkWidget *preview; + GtkWidget *preview_title; +}; + + +/* local function prototypes */ + +static void gimp_image_chooser_constructed (GObject *object); +static void gimp_image_chooser_dispose (GObject *object); +static void gimp_image_chooser_finalize (GObject *object); + +static void gimp_image_chooser_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_image_chooser_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_image_chooser_clicked (GimpImageChooser *chooser); + +static GimpValueArray * gimp_temp_callback_run (GimpProcedure *procedure, + GimpProcedureConfig *config, + GimpImageChooser *chooser); +static gboolean gimp_image_select_remove_after_run (const gchar *procedure_name); + +static void gimp_image_chooser_draw (GimpImageChooser *chooser); +static void gimp_image_chooser_get_thumbnail (GimpImageChooser *chooser, + gint width, + gint height); + + +static GParamSpec *image_button_props[N_PROPS] = { NULL, }; + +G_DEFINE_FINAL_TYPE (GimpImageChooser, gimp_image_chooser, GTK_TYPE_BOX) + + +static void +gimp_image_chooser_class_init (GimpImageChooserClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_image_chooser_constructed; + object_class->dispose = gimp_image_chooser_dispose; + object_class->finalize = gimp_image_chooser_finalize; + object_class->set_property = gimp_image_chooser_set_property; + object_class->get_property = gimp_image_chooser_get_property; + + /** + * GimpImageChooser:title: + * + * The title to be used for the image selection popup dialog. + * + * Since: 3.0 + */ + image_button_props[PROP_TITLE] = + g_param_spec_string ("title", + "Title", + "The title to be used for the image selection popup dialog", + "Image Selection", + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY); + + /** + * GimpImageChooser:label: + * + * Label text with mnemonic. + * + * Since: 3.0 + */ + image_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); + + /** + * GimpImageChooser:image: + * + * The currently selected image. + * + * Since: 3.0 + */ + image_button_props[PROP_IMAGE] = + gimp_param_spec_image ("image", + "Image", + "The currently selected image", + TRUE, + GIMP_PARAM_READWRITE | + G_PARAM_EXPLICIT_NOTIFY); + + g_object_class_install_properties (object_class, + N_PROPS, image_button_props); +} + +static void +gimp_image_chooser_init (GimpImageChooser *chooser) +{ + gtk_orientable_set_orientation (GTK_ORIENTABLE (chooser), + GTK_ORIENTATION_HORIZONTAL); + gtk_box_set_spacing (GTK_BOX (chooser), 6); + + chooser->thumbnail = NULL; +} + +static void +gimp_image_chooser_constructed (GObject *object) +{ + GimpImageChooser *chooser = GIMP_IMAGE_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_set_visible (GTK_WIDGET (chooser->label_widget), TRUE); + + 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_set_visible (button, TRUE); + + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2); + gtk_container_add (GTK_CONTAINER (button), box); + gtk_widget_set_visible (box, TRUE); + + 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_set_visible (chooser->preview_frame, TRUE); + + 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_set_visible (chooser->preview, TRUE); + + chooser->preview_title = gtk_label_new (_("Browse...")); + gtk_box_pack_start (GTK_BOX (box), chooser->preview_title, FALSE, FALSE, 0); + gtk_widget_set_visible (chooser->preview_title, TRUE); + + g_signal_connect_swapped (button, "clicked", + G_CALLBACK (gimp_image_chooser_clicked), + chooser); + + G_OBJECT_CLASS (gimp_image_chooser_parent_class)->constructed (object); +} + +static void +gimp_image_chooser_dispose (GObject *object) +{ + GimpImageChooser *chooser = GIMP_IMAGE_CHOOSER (object); + + if (chooser->callback) + { + gimp_images_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_image_chooser_parent_class)->dispose (object); +} + +static void +gimp_image_chooser_finalize (GObject *object) +{ + GimpImageChooser *chooser = GIMP_IMAGE_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_image_chooser_parent_class)->finalize (object); +} + +static void +gimp_image_chooser_set_property (GObject *object, + guint property_id, + const GValue *gvalue, + GParamSpec *pspec) +{ + GimpImageChooser *chooser = GIMP_IMAGE_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_IMAGE: + gimp_image_chooser_set_image (chooser, g_value_get_object (gvalue)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_image_chooser_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpImageChooser *chooser = GIMP_IMAGE_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_IMAGE: + g_value_set_object (value, chooser->image); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +/** + * gimp_image_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. + * @image: (nullable): Initial image. + * + * Creates a new #GtkWidget that lets a user choose an image. + * + * When @image is %NULL, initial choice is from context. + * + * Returns: A [class@GimpUi.ImageChooser. + * + * Since: 3.0 + */ +GtkWidget * +gimp_image_chooser_new (const gchar *title, + const gchar *label, + GimpImage *image) +{ + GtkWidget *chooser; + + g_return_val_if_fail (image == NULL, NULL); + + chooser = g_object_new (GIMP_TYPE_IMAGE_CHOOSER, + "title", title, + "label", label, + "image", image, + NULL); + + return chooser; +} + +/** + * gimp_image_chooser_get_image: + * @chooser: A #GimpImageChooser + * + * Gets the currently selected image. + * + * Returns: (transfer none): an internal copy of the image which must not be freed. + * + * Since: 3.0 + */ +GimpImage * +gimp_image_chooser_get_image (GimpImageChooser *chooser) +{ + g_return_val_if_fail (GIMP_IS_IMAGE_CHOOSER (chooser), NULL); + + return chooser->image; +} + +/** + * gimp_image_chooser_set_image: + * @chooser: A #GimpImageChooser + * @image: Image to set. + * + * Sets the currently selected image. + * This will select the image in both the button and any chooser popup. + * + * Since: 3.0 + */ +void +gimp_image_chooser_set_image (GimpImageChooser *chooser, + GimpImage *image) +{ + g_return_if_fail (GIMP_IS_IMAGE_CHOOSER (chooser)); + g_return_if_fail (image == NULL || GIMP_IS_IMAGE (image)); + + chooser->image = image; + + if (chooser->callback) + gimp_images_set_popup (chooser->callback, chooser->image); + + g_object_notify_by_pspec (G_OBJECT (chooser), image_button_props[PROP_IMAGE]); + + gimp_image_chooser_draw (chooser); +} + +/** + * gimp_image_chooser_get_label: + * @widget: A [class@ImageChooser]. + * + * Returns the label widget. + * + * Returns: (transfer none): the [class@Gtk.Widget] showing the label text. + * Since: 3.0 + */ +GtkWidget * +gimp_image_chooser_get_label (GimpImageChooser *chooser) +{ + g_return_val_if_fail (GIMP_IS_IMAGE_CHOOSER (chooser), NULL); + + return chooser->label_widget; +} + + +/* private functions */ + +static GimpValueArray * +gimp_temp_callback_run (GimpProcedure *procedure, + GimpProcedureConfig *config, + GimpImageChooser *chooser) +{ + GimpImage *image; + gboolean closing; + + g_object_get (config, + "image", &image, + "closing", &closing, + NULL); + + g_object_set (chooser, "image", image, NULL); + + if (closing) + { + g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, + (GSourceFunc) gimp_image_select_remove_after_run, + g_strdup (gimp_procedure_get_name (procedure)), + g_free); + g_clear_pointer (&chooser->callback, g_free); + } + + g_clear_object (&image); + + return gimp_procedure_new_return_values (procedure, GIMP_PDB_SUCCESS, NULL); +} + +static gboolean +gimp_image_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_image_chooser_clicked (GimpImageChooser *chooser) +{ + if (chooser->callback) + { + /* Popup already created. Calling setter raises the popup. */ + gimp_images_set_popup (chooser->callback, chooser->image); + } + 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_image_argument (callback_procedure, "image", + "Image", "The selected image", + 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_images_popup (gimp_procedure_get_name (callback_procedure), chooser->title, + chooser->image, handle)) + { + /* Allow callbacks to be watched */ + gimp_plug_in_persistent_enable (plug_in); + + chooser->callback = g_strdup (gimp_procedure_get_name (callback_procedure)); + } + else + { + g_warning ("%s: failed to open remote image select dialog.", G_STRFUNC); + gimp_plug_in_remove_temp_procedure (plug_in, gimp_procedure_get_name (callback_procedure)); + return; + } + gimp_images_set_popup (chooser->callback, chooser->image); + } +} + +static void +gimp_image_chooser_draw (GimpImageChooser *chooser) +{ + GimpPreviewArea *area = GIMP_PREVIEW_AREA (chooser->preview); + GtkAllocation allocation; + gint x = 0; + gint y = 0; + + gtk_widget_get_allocation (chooser->preview, &allocation); + + gimp_image_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_image_chooser_get_thumbnail (GimpImageChooser *chooser, + gint width, + gint height) +{ + if (chooser->width == width && + chooser->height == height) + /* Let's assume image contents is not changing in a single run. */ + return; + + g_clear_pointer (&chooser->thumbnail, g_bytes_unref); + + if (chooser->image) + { + chooser->width = BUTTON_SIZE; + chooser->height = BUTTON_SIZE; + + chooser->thumbnail = gimp_image_get_thumbnail_data (chooser->image, + &chooser->width, + &chooser->height, + &chooser->bpp); + } +} diff --git a/libgimp/gimpimagechooser.h b/libgimp/gimpimagechooser.h new file mode 100644 index 0000000000..4bc3d4b960 --- /dev/null +++ b/libgimp/gimpimagechooser.h @@ -0,0 +1,46 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpimagechooser.h + * + * 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 + * Lesser 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 + * . + */ + +#if !defined (__GIMP_UI_H_INSIDE__) && !defined (GIMP_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __GIMP_IMAGE_CHOOSER_H__ +#define __GIMP_IMAGE_CHOOSER_H__ + +G_BEGIN_DECLS + +#define GIMP_TYPE_IMAGE_CHOOSER (gimp_image_chooser_get_type ()) +G_DECLARE_FINAL_TYPE (GimpImageChooser, gimp_image_chooser, GIMP, IMAGE_CHOOSER, GtkBox) + + +GtkWidget * gimp_image_chooser_new (const gchar *title, + const gchar *label, + GimpImage *image); + +GimpImage * gimp_image_chooser_get_image (GimpImageChooser *chooser); +void gimp_image_chooser_set_image (GimpImageChooser *chooser, + GimpImage *image); + +GtkWidget * gimp_image_chooser_get_label (GimpImageChooser *widget); + +G_END_DECLS + +#endif /* __GIMP_IMAGE_CHOOSER_H__ */ diff --git a/libgimp/gimpimageselect_pdb.c b/libgimp/gimpimageselect_pdb.c index 3d0ac5aa31..b16b54801b 100644 --- a/libgimp/gimpimageselect_pdb.c +++ b/libgimp/gimpimageselect_pdb.c @@ -447,3 +447,114 @@ gimp_image_select_item (GimpImage *image, return success; } + +/** + * gimp_images_popup: + * @callback: The callback PDB proc to call when user chooses an image. + * @popup_title: Title of the image selection dialog. + * @initial_image: (nullable): The image to set as the initial choice. + * @parent_window: (nullable): An optional parent window handle for the popup to be set transient to. + * + * Invokes the image selection dialog. + * + * Opens a dialog letting a user choose an image. + * + * Returns: TRUE on success. + **/ +gboolean +gimp_images_popup (const gchar *callback, + const gchar *popup_title, + GimpImage *initial_image, + GBytes *parent_window) +{ + GimpValueArray *args; + GimpValueArray *return_vals; + gboolean success = TRUE; + + args = gimp_value_array_new_from_types (NULL, + G_TYPE_STRING, callback, + G_TYPE_STRING, popup_title, + GIMP_TYPE_IMAGE, initial_image, + G_TYPE_BYTES, parent_window, + G_TYPE_NONE); + + return_vals = _gimp_pdb_run_procedure_array (gimp_get_pdb (), + "gimp-images-popup", + args); + gimp_value_array_unref (args); + + success = GIMP_VALUES_GET_ENUM (return_vals, 0) == GIMP_PDB_SUCCESS; + + gimp_value_array_unref (return_vals); + + return success; +} + +/** + * gimp_images_close_popup: + * @callback: The name of the callback registered for this pop-up. + * + * Close the image selection dialog. + * + * Closes an open image selection dialog. + * + * Returns: TRUE on success. + **/ +gboolean +gimp_images_close_popup (const gchar *callback) +{ + GimpValueArray *args; + GimpValueArray *return_vals; + gboolean success = TRUE; + + args = gimp_value_array_new_from_types (NULL, + G_TYPE_STRING, callback, + G_TYPE_NONE); + + return_vals = _gimp_pdb_run_procedure_array (gimp_get_pdb (), + "gimp-images-close-popup", + args); + gimp_value_array_unref (args); + + success = GIMP_VALUES_GET_ENUM (return_vals, 0) == GIMP_PDB_SUCCESS; + + gimp_value_array_unref (return_vals); + + return success; +} + +/** + * gimp_images_set_popup: + * @callback: The name of the callback registered for this pop-up. + * @image: The image to set as selected. + * + * Sets the selected image in a image selection dialog. + * + * Sets the selected image in a image selection dialog. + * + * Returns: TRUE on success. + **/ +gboolean +gimp_images_set_popup (const gchar *callback, + GimpImage *image) +{ + GimpValueArray *args; + GimpValueArray *return_vals; + gboolean success = TRUE; + + args = gimp_value_array_new_from_types (NULL, + G_TYPE_STRING, callback, + GIMP_TYPE_IMAGE, image, + G_TYPE_NONE); + + return_vals = _gimp_pdb_run_procedure_array (gimp_get_pdb (), + "gimp-images-set-popup", + args); + gimp_value_array_unref (args); + + success = GIMP_VALUES_GET_ENUM (return_vals, 0) == GIMP_PDB_SUCCESS; + + gimp_value_array_unref (return_vals); + + return success; +} diff --git a/libgimp/gimpimageselect_pdb.h b/libgimp/gimpimageselect_pdb.h index dc8b887a9f..32160e9afb 100644 --- a/libgimp/gimpimageselect_pdb.h +++ b/libgimp/gimpimageselect_pdb.h @@ -68,6 +68,13 @@ gboolean gimp_image_select_polygon (GimpImage *image, gboolean gimp_image_select_item (GimpImage *image, GimpChannelOps operation, GimpItem *item); +gboolean gimp_images_popup (const gchar *callback, + const gchar *popup_title, + GimpImage *initial_image, + GBytes *parent_window); +gboolean gimp_images_close_popup (const gchar *callback); +gboolean gimp_images_set_popup (const gchar *callback, + GimpImage *image); G_END_DECLS diff --git a/libgimp/gimpproceduredialog.c b/libgimp/gimpproceduredialog.c index 7deb08718b..ff215d4451 100644 --- a/libgimp/gimpproceduredialog.c +++ b/libgimp/gimpproceduredialog.c @@ -913,6 +913,10 @@ gimp_procedure_dialog_get_widget (GimpProcedureDialog *dialog, { widget = gimp_prop_drawable_chooser_new (G_OBJECT (priv->config), property, NULL); } + else if (G_IS_PARAM_SPEC_OBJECT (pspec) && pspec->value_type == GIMP_TYPE_IMAGE) + { + widget = gimp_prop_image_chooser_new (G_OBJECT (priv->config), property, NULL); + } else if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_ENUM) { GimpIntStore *store; @@ -962,6 +966,8 @@ gimp_procedure_dialog_get_widget (GimpProcedureDialog *dialog, label = gimp_resource_chooser_get_label (GIMP_RESOURCE_CHOOSER (widget)); else if (GIMP_IS_DRAWABLE_CHOOSER (widget)) label = gimp_drawable_chooser_get_label (GIMP_DRAWABLE_CHOOSER (widget)); + else if (GIMP_IS_IMAGE_CHOOSER (widget)) + label = gimp_image_chooser_get_label (GIMP_IMAGE_CHOOSER (widget)); } if (label != NULL) @@ -2916,6 +2922,10 @@ gimp_procedure_dialog_check_mnemonic (GimpProcedureDialog *dialog, { label = gimp_drawable_chooser_get_label (GIMP_DRAWABLE_CHOOSER (widget)); } + else if (GIMP_IS_IMAGE_CHOOSER (widget)) + { + label = gimp_image_chooser_get_label (GIMP_IMAGE_CHOOSER (widget)); + } else if (GIMP_IS_FILE_CHOOSER (widget)) { label = gimp_file_chooser_get_label_widget (GIMP_FILE_CHOOSER (widget)); diff --git a/libgimp/gimppropwidgets.c b/libgimp/gimppropwidgets.c index ec13aee8f6..76c42c57c1 100644 --- a/libgimp/gimppropwidgets.c +++ b/libgimp/gimppropwidgets.c @@ -146,6 +146,66 @@ gimp_prop_pattern_chooser_new (GObject *config, config, property_name, chooser_title); } +/** + * gimp_prop_image_chooser_new: + * @config: Object to which property is attached. + * @property_name: Name of a [class@Gimp.Image] property. + * @chooser_title: (nullable): title for the poppable dialog. + * + * Creates a [class@GimpUi.ImageChooser] controlled by the specified property. + * + * Returns: (transfer full): A new [class@GimpUi.ImageChooser]. + * + * Since: 3.0 + */ +GtkWidget * +gimp_prop_image_chooser_new (GObject *config, + const gchar *property_name, + const gchar *chooser_title) +{ + GParamSpec *param_spec; + GtkWidget *prop_chooser; + GimpImage *initial_image = NULL; + gchar *title = NULL; + const gchar *label; + + param_spec = g_object_class_find_property (G_OBJECT_GET_CLASS (config), + property_name); + + g_return_val_if_fail (param_spec != NULL, NULL); + g_return_val_if_fail (g_type_is_a (G_TYPE_FROM_INSTANCE (param_spec), G_TYPE_PARAM_OBJECT) && + g_type_is_a (param_spec->value_type, GIMP_TYPE_IMAGE), NULL); + + g_object_get (config, + property_name, &initial_image, + NULL); + + label = g_param_spec_get_nick (param_spec); + + if (chooser_title == NULL) + { + gchar *canonical; + + canonical = gimp_utils_make_canonical_menu_label (label); + title = g_strdup_printf (_("Choose image: %s"), canonical); + g_free (canonical); + } + else + { + title = g_strdup (chooser_title); + } + + prop_chooser = gimp_image_chooser_new (title, label, initial_image); + g_clear_object (&initial_image); + g_free (title); + + g_object_bind_property (prop_chooser, "image", + config, property_name, + G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE); + + return prop_chooser; +} + /** * gimp_prop_drawable_chooser_new: * @config: Object to which property is attached. diff --git a/libgimp/gimppropwidgets.h b/libgimp/gimppropwidgets.h index 0db7063158..dddc006a8a 100644 --- a/libgimp/gimppropwidgets.h +++ b/libgimp/gimppropwidgets.h @@ -48,6 +48,9 @@ GtkWidget * gimp_prop_pattern_chooser_new (GObject *config, const gchar *property_name, const gchar *chooser_title); +GtkWidget * gimp_prop_image_chooser_new (GObject *config, + const gchar *property_name, + const gchar *chooser_title); GtkWidget * gimp_prop_drawable_chooser_new (GObject *config, const gchar *property_name, const gchar *chooser_title); diff --git a/libgimp/gimpui.def b/libgimp/gimpui.def index 3d8a9309d1..e7453c96b8 100644 --- a/libgimp/gimpui.def +++ b/libgimp/gimpui.def @@ -22,6 +22,11 @@ EXPORTS gimp_font_chooser_new gimp_gradient_chooser_get_type gimp_gradient_chooser_new + gimp_image_chooser_get_image + gimp_image_chooser_get_label + gimp_image_chooser_get_type + gimp_image_chooser_new + gimp_image_chooser_set_image gimp_image_combo_box_get_type gimp_image_combo_box_new gimp_layer_combo_box_get_type @@ -70,6 +75,7 @@ EXPORTS gimp_prop_drawable_chooser_new gimp_prop_font_chooser_new gimp_prop_gradient_chooser_new + gimp_prop_image_chooser_new gimp_prop_palette_chooser_new gimp_prop_pattern_chooser_new gimp_prop_resolution_entry_new diff --git a/libgimp/gimpui.h b/libgimp/gimpui.h index 25a01bf41f..81570cc854 100644 --- a/libgimp/gimpui.h +++ b/libgimp/gimpui.h @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include diff --git a/libgimp/gimpuitypes.h b/libgimp/gimpuitypes.h index 1ddfdef2cb..eb9ad8810a 100644 --- a/libgimp/gimpuitypes.h +++ b/libgimp/gimpuitypes.h @@ -50,6 +50,7 @@ typedef struct _GimpResourceChooser GimpResourceChooser; typedef struct _GimpBrushChooser GimpBrushChooser; typedef struct _GimpFontChooser GimpFontChooser; typedef struct _GimpGradientChooser GimpGradientChooser; +typedef struct _GimpImageChooser GimpImageChooser; typedef struct _GimpPaletteChooser GimpPaletteChooser; typedef struct _GimpPatternChooser GimpPatternChooser; diff --git a/libgimp/meson.build b/libgimp/meson.build index c290593d50..acb0580d61 100644 --- a/libgimp/meson.build +++ b/libgimp/meson.build @@ -303,6 +303,7 @@ libgimpui_sources_introspectable = [ 'gimpdrawablepreview.c', 'gimpfontchooser.c', 'gimpgradientchooser.c', + 'gimpimagechooser.c', 'gimpimagecombobox.c', 'gimpitemcombobox.c', 'gimppalettechooser.c', @@ -339,6 +340,7 @@ libgimpui_headers_introspectable = [ 'gimpdrawablepreview.h', 'gimpfontchooser.h', 'gimpgradientchooser.h', + 'gimpimagechooser.h', 'gimpimagecombobox.h', 'gimpitemcombobox.h', 'gimppalettechooser.h', diff --git a/pdb/groups/image_select.pdb b/pdb/groups/image_select.pdb index 8ae8647653..f8d0d8e47c 100644 --- a/pdb/groups/image_select.pdb +++ b/pdb/groups/image_select.pdb @@ -424,9 +424,91 @@ CODE } +sub images_popup { + $blurb = 'Invokes the image selection dialog.'; + $help = 'Opens a dialog letting a user choose an image.'; + + &alxsa_pdb_misc('2025'); + + @inargs = ( + { name => 'callback', type => 'string', non_empty => 1, + desc => 'The callback PDB proc to call when user chooses an image' }, + { name => 'popup_title', type => 'string', + desc => 'Title of the image selection dialog' }, + { name => 'initial_image', type => 'image', none_ok => 1, no_validate => 1, + desc => 'The image to set as the initial choice' }, + { name => 'parent_window', type => 'bytes', none_ok => 1, + desc => 'An optional parent window handle for the popup to be set transient to' } + ); + + %invoke = ( + code => <<'CODE' +{ + if (gimp->no_interface || + ! gimp_pdb_lookup_procedure (gimp->pdb, callback) || + ! gimp_pdb_dialog_new (gimp, context, progress, + GIMP_TYPE_IMAGE, + parent_window, popup_title, callback, + GIMP_OBJECT (initial_image), + NULL)) + success = FALSE; +} +CODE + ); +} + +sub images_close_popup { + $blurb = 'Close the image selection dialog.'; + $help = 'Closes an open image selection dialog.'; + + &alxsa_pdb_misc('2025'); + + @inargs = ( + { name => 'callback', type => 'string', non_empty => 1, + desc => 'The name of the callback registered for this pop-up' } + ); + + %invoke = ( + code => <<'CODE' +{ + if (gimp->no_interface || + ! gimp_pdb_lookup_procedure (gimp->pdb, callback) || + ! gimp_pdb_dialog_close (gimp, GIMP_TYPE_IMAGE, callback)) + success = FALSE; +} +CODE + ); +} + +sub images_set_popup { + $blurb = 'Sets the selected image in a image selection dialog.'; + $help = $blurb; + + &alxsa_pdb_misc('2025'); + + @inargs = ( + { name => 'callback', type => 'string', non_empty => 1, + desc => 'The name of the callback registered for this pop-up' }, + { name => 'image', type => 'image', no_validate => 1, + desc => 'The image to set as selected' } + ); + + %invoke = ( + code => <<'CODE' +{ + if (gimp->no_interface || + ! gimp_pdb_lookup_procedure (gimp->pdb, callback) || + ! gimp_pdb_dialog_set (gimp, GIMP_TYPE_IMAGE, callback, GIMP_OBJECT (image), NULL)) + success = FALSE; +} +CODE + ); +} @headers = qw("libgimpbase/gimpbase.h" + "core/gimp.h" "core/gimpchannel-select.h" + "core/gimpdatafactory.h" "gimppdb-utils.h" "gimppdbcontext.h" "gimp-intl.h"); @@ -437,7 +519,10 @@ CODE image_select_round_rectangle image_select_ellipse image_select_polygon - image_select_item); + image_select_item + images_popup + images_close_popup + images_set_popup); %exports = (app => [@procs], lib => [@procs]);