diff --git a/app/core/gimp-gui.c b/app/core/gimp-gui.c index 3e6c4e5248..6ec2a513ee 100644 --- a/app/core/gimp-gui.c +++ b/app/core/gimp-gui.c @@ -419,6 +419,7 @@ gimp_pdb_dialog_new (Gimp *gimp, 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) || + g_type_is_a (contents_type, GIMP_TYPE_ITEM) || 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); @@ -453,6 +454,7 @@ 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 || + contents_type == GIMP_TYPE_ITEM || 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); @@ -480,6 +482,7 @@ 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 || + contents_type == GIMP_TYPE_ITEM || contents_type == GIMP_TYPE_IMAGE, FALSE); g_return_val_if_fail (callback_name != NULL, FALSE); diff --git a/app/gui/gui-vtable.c b/app/gui/gui-vtable.c index 090d1d48d4..e8ab408f20 100644 --- a/app/gui/gui-vtable.c +++ b/app/gui/gui-vtable.c @@ -86,6 +86,7 @@ #include "widgets/gimphelp-ids.h" #include "widgets/gimpimageselect.h" #include "widgets/gimpimageview.h" +#include "widgets/gimpitemselect.h" #include "widgets/gimpmenufactory.h" #include "widgets/gimppaletteselect.h" #include "widgets/gimppatternselect.h" @@ -668,6 +669,11 @@ gui_pdb_dialog_new (Gimp *gimp, dialog_type = GIMP_TYPE_PICKABLE_SELECT; dialog_role = "gimp-pickable-selection"; } + else if (g_type_is_a (contents_type, GIMP_TYPE_ITEM)) + { + dialog_type = GIMP_TYPE_ITEM_SELECT; + dialog_role = "gimp-item-selection"; + } else { g_return_val_if_reached (FALSE); @@ -675,10 +681,10 @@ gui_pdb_dialog_new (Gimp *gimp, if (dialog_type != G_TYPE_NONE) { - if (! object && ! g_type_is_a (contents_type, GIMP_TYPE_DRAWABLE)) + if (! object && ! g_type_is_a (contents_type, GIMP_TYPE_ITEM)) object = gimp_context_get_by_type (context, contents_type); - if (object || g_type_is_a (contents_type, GIMP_TYPE_DRAWABLE)) + if (object || g_type_is_a (contents_type, GIMP_TYPE_ITEM)) { gint n_properties = 0; gchar **names = NULL; @@ -794,6 +800,10 @@ gui_pdb_dialog_set (Gimp *gimp, { klass = g_type_class_peek (GIMP_TYPE_PICKABLE_SELECT); } + else if (contents_type == GIMP_TYPE_ITEM) + { + klass = g_type_class_peek (GIMP_TYPE_ITEM_SELECT); + } g_return_val_if_fail (klass != NULL, FALSE); @@ -848,6 +858,8 @@ gui_pdb_dialog_close (Gimp *gimp, klass = g_type_class_peek (GIMP_TYPE_PATTERN_SELECT); else if (contents_type == GIMP_TYPE_DRAWABLE) klass = g_type_class_peek (GIMP_TYPE_PICKABLE_SELECT); + else if (contents_type == GIMP_TYPE_ITEM) + klass = g_type_class_peek (GIMP_TYPE_ITEM_SELECT); if (klass) { diff --git a/app/pdb/internal-procs.c b/app/pdb/internal-procs.c index a6aa965fc4..f9d6a3319a 100644 --- a/app/pdb/internal-procs.c +++ b/app/pdb/internal-procs.c @@ -30,7 +30,7 @@ #include "internal-procs.h" -/* 782 procedures registered total */ +/* 785 procedures registered total */ void internal_procs_init (GimpPDB *pdb) diff --git a/app/pdb/item-cmds.c b/app/pdb/item-cmds.c index bac87e621e..2a32a0772f 100644 --- a/app/pdb/item-cmds.c +++ b/app/pdb/item-cmds.c @@ -31,6 +31,8 @@ #include "pdb-types.h" +#include "core/gimp.h" +#include "core/gimpdatafactory.h" #include "core/gimpgrouplayer.h" #include "core/gimpimage.h" #include "core/gimpitem.h" @@ -1113,6 +1115,95 @@ item_get_parasite_list_invoker (GimpProcedure *procedure, return return_vals; } +static GimpValueArray * +items_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; + const gchar *item_type; + GimpItem *initial_item; + 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)); + item_type = g_value_get_string (gimp_value_array_index (args, 2)); + initial_item = g_value_get_object (gimp_value_array_index (args, 3)); + parent_window = g_value_get_boxed (gimp_value_array_index (args, 4)); + + if (success) + { + if (gimp->no_interface || + ! gimp_pdb_lookup_procedure (gimp->pdb, callback) || + ! gimp_pdb_dialog_new (gimp, context, progress, + g_type_from_name (item_type), + parent_window, popup_title, callback, + GIMP_OBJECT (initial_item), + NULL)) + success = FALSE; + } + + return gimp_procedure_get_return_values (procedure, success, + error ? *error : NULL); +} + +static GimpValueArray * +items_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_ITEM, callback)) + success = FALSE; + } + + return gimp_procedure_get_return_values (procedure, success, + error ? *error : NULL); +} + +static GimpValueArray * +items_set_popup_invoker (GimpProcedure *procedure, + Gimp *gimp, + GimpContext *context, + GimpProgress *progress, + const GimpValueArray *args, + GError **error) +{ + gboolean success = TRUE; + const gchar *callback; + GimpItem *item; + + callback = g_value_get_string (gimp_value_array_index (args, 0)); + item = 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_ITEM, callback, GIMP_OBJECT (item), NULL)) + success = FALSE; + } + + return gimp_procedure_get_return_values (procedure, success, + error ? *error : NULL); +} + void register_item_procs (GimpPDB *pdb) { @@ -2201,4 +2292,108 @@ register_item_procs (GimpPDB *pdb) GIMP_PARAM_READWRITE)); gimp_pdb_register_procedure (pdb, procedure); g_object_unref (procedure); + + /* + * gimp-items-popup + */ + procedure = gimp_procedure_new (items_popup_invoker, FALSE); + gimp_object_set_static_name (GIMP_OBJECT (procedure), + "gimp-items-popup"); + gimp_procedure_set_static_help (procedure, + "Invokes the item selection dialog.", + "Opens a dialog letting a user choose an item .", + 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 item", + FALSE, FALSE, TRUE, + NULL, + GIMP_PARAM_READWRITE)); + gimp_procedure_add_argument (procedure, + gimp_param_spec_string ("popup-title", + "popup title", + "Title of the item selection dialog", + FALSE, FALSE, FALSE, + NULL, + GIMP_PARAM_READWRITE)); + gimp_procedure_add_argument (procedure, + gimp_param_spec_string ("item-type", + "item type", + "The name of the GIMP_TYPE_ITEM subtype", + FALSE, FALSE, TRUE, + NULL, + GIMP_PARAM_READWRITE)); + gimp_procedure_add_argument (procedure, + gimp_param_spec_item ("initial-item", + "initial item", + "The item 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-items-close-popup + */ + procedure = gimp_procedure_new (items_close_popup_invoker, FALSE); + gimp_object_set_static_name (GIMP_OBJECT (procedure), + "gimp-items-close-popup"); + gimp_procedure_set_static_help (procedure, + "Close the item selection dialog.", + "Closes an open item 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-items-set-popup + */ + procedure = gimp_procedure_new (items_set_popup_invoker, FALSE); + gimp_object_set_static_name (GIMP_OBJECT (procedure), + "gimp-items-set-popup"); + gimp_procedure_set_static_help (procedure, + "Sets the selected item in a item selection dialog.", + "Sets the selected item in a item 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_item ("item", + "item", + "The item 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/widgets/gimpitemchooser.c b/app/widgets/gimpitemchooser.c new file mode 100644 index 0000000000..cf8412c5ed --- /dev/null +++ b/app/widgets/gimpitemchooser.c @@ -0,0 +1,639 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpitemchooser.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/gimpchannel.h" +#include "core/gimpcontext.h" +#include "core/gimpimage.h" +#include "core/gimpitem.h" +#include "core/gimplayer.h" +#include "core/gimpviewable.h" + +#include "path/gimppath.h" + +#include "gimpcontainertreeview.h" +#include "gimpcontainerview.h" +#include "gimpitemchooser.h" +#include "gimpviewrenderer.h" + +#include "gimp-intl.h" + + +enum +{ + ACTIVATE, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_CONTEXT, + PROP_ITEM_TYPE, + PROP_ITEM, + PROP_VIEW_SIZE, + PROP_VIEW_BORDER_WIDTH +}; + +struct _GimpItemChooserPrivate +{ + GType item_type; + GimpItem *item; + GimpContext *context; + + gint view_size; + gint view_border_width; + + GtkWidget *image_view; + GtkWidget *layer_view; + GtkWidget *channel_view; + GtkWidget *path_view; + GtkWidget *layer_label; +}; + + +static void gimp_item_chooser_constructed (GObject *object); +static void gimp_item_chooser_finalize (GObject *object); +static void gimp_item_chooser_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_item_chooser_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_item_chooser_image_changed (GimpContext *context, + GimpImage *image, + GimpItemChooser *chooser); +static void gimp_item_chooser_item_activated (GimpContainerView *view, + GimpItem *item, + GimpItemChooser *chooser); +static void gimp_item_chooser_items_selected (GimpContainerView *view, + GimpItemChooser *chooser); + + +G_DEFINE_TYPE_WITH_PRIVATE (GimpItemChooser, gimp_item_chooser, GTK_TYPE_FRAME) + +#define parent_class gimp_item_chooser_parent_class + +static guint signals[LAST_SIGNAL]; + +static void +gimp_item_chooser_class_init (GimpItemChooserClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_item_chooser_constructed; + object_class->finalize = gimp_item_chooser_finalize; + object_class->get_property = gimp_item_chooser_get_property; + object_class->set_property = gimp_item_chooser_set_property; + + /** + * GimpItemChooser::activate: + * @chooser: + * + * Emitted when a item is activated, which is mostly forwarding when + * "item-activated" signal is emitted from any of either the image, layer or + * channel view. E.g. this happens when one double-click on one of the + * items. + */ + signals[ACTIVATE] = + g_signal_new ("activate", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GimpItemChooserClass, 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_ITEM_TYPE, + g_param_spec_gtype ("item-type", + NULL, NULL, + GIMP_TYPE_ITEM, + GIMP_PARAM_WRITABLE | + G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property (object_class, PROP_ITEM, + g_param_spec_object ("item", + NULL, NULL, + GIMP_TYPE_ITEM, + 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_item_chooser_init (GimpItemChooser *chooser) +{ + chooser->priv = gimp_item_chooser_get_instance_private (chooser); + + chooser->priv->view_size = GIMP_VIEW_SIZE_SMALL; + chooser->priv->view_border_width = 1; + + chooser->priv->layer_view = NULL; + chooser->priv->channel_view = NULL; +} + +static void +gimp_item_chooser_constructed (GObject *object) +{ + GimpItemChooser *chooser = GIMP_ITEM_CHOOSER (object); + GtkWidget *hbox; + GtkWidget *vbox; + GtkWidget *label; + GtkWidget *notebook; + GimpImage *image; + + 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_set_visible (hbox, TRUE); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 4); + gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0); + gtk_widget_set_visible (vbox, TRUE); + + 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_set_visible (label, TRUE); + + 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_item_chooser_item_activated), + G_OBJECT (chooser), 0); + g_signal_connect_object (chooser->priv->image_view, "selection-changed", + G_CALLBACK (gimp_item_chooser_items_selected), + G_OBJECT (chooser), 0); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 4); + gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0); + gtk_widget_set_visible (vbox, TRUE); + + chooser->priv->layer_label = label = + gtk_label_new (_("Select an image in the left pane")); + gtk_label_set_xalign (GTK_LABEL (label), 0.0); + gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_END); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + gtk_widget_set_visible (label, TRUE); + + notebook = gtk_notebook_new (); + gtk_box_pack_start (GTK_BOX (vbox), notebook, TRUE, TRUE, 0); + gtk_widget_set_visible (notebook, TRUE); + + if (g_type_is_a (GIMP_TYPE_LAYER, chooser->priv->item_type)) + { + chooser->priv->layer_view = + gimp_container_tree_view_new (NULL, + chooser->priv->context, + chooser->priv->view_size, + chooser->priv->view_border_width); + gtk_tree_view_set_show_expanders (GTK_TREE_VIEW (GIMP_CONTAINER_TREE_VIEW (chooser->priv->layer_view)->view), + TRUE); + gimp_container_box_set_size_request (GIMP_CONTAINER_BOX (chooser->priv->layer_view), + 4 * (chooser->priv->view_size + + 2 * chooser->priv->view_border_width), + 4 * (chooser->priv->view_size + + 2 * chooser->priv->view_border_width)); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), + chooser->priv->layer_view, + gtk_label_new (_("Layers"))); + gtk_widget_set_visible (chooser->priv->layer_view, TRUE); + + g_signal_connect_object (chooser->priv->layer_view, "item-activated", + G_CALLBACK (gimp_item_chooser_item_activated), + G_OBJECT (chooser), 0); + g_signal_connect_object (chooser->priv->layer_view, "selection-changed", + G_CALLBACK (gimp_item_chooser_items_selected), + G_OBJECT (chooser), 0); + } + + if (g_type_is_a (GIMP_TYPE_CHANNEL, chooser->priv->item_type)) + { + chooser->priv->channel_view = + gimp_container_tree_view_new (NULL, + chooser->priv->context, + chooser->priv->view_size, + chooser->priv->view_border_width); + gimp_container_box_set_size_request (GIMP_CONTAINER_BOX (chooser->priv->channel_view), + 4 * (chooser->priv->view_size + + 2 * chooser->priv->view_border_width), + 4 * (chooser->priv->view_size + + 2 * chooser->priv->view_border_width)); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), + chooser->priv->channel_view, + gtk_label_new (_("Channels"))); + gtk_widget_set_visible (chooser->priv->channel_view, TRUE); + + g_signal_connect_object (chooser->priv->channel_view, "item-activated", + G_CALLBACK (gimp_item_chooser_item_activated), + G_OBJECT (chooser), 0); + g_signal_connect_object (chooser->priv->channel_view, "selection-changed", + G_CALLBACK (gimp_item_chooser_items_selected), + G_OBJECT (chooser), 0); + } + + if (g_type_is_a (GIMP_TYPE_PATH, chooser->priv->item_type)) + { + chooser->priv->path_view = + gimp_container_tree_view_new (NULL, + chooser->priv->context, + chooser->priv->view_size, + chooser->priv->view_border_width); + gimp_container_box_set_size_request (GIMP_CONTAINER_BOX (chooser->priv->path_view), + 4 * (chooser->priv->view_size + + 2 * chooser->priv->view_border_width), + 4 * (chooser->priv->view_size + + 2 * chooser->priv->view_border_width)); + gtk_notebook_append_page (GTK_NOTEBOOK (notebook), + chooser->priv->path_view, + gtk_label_new (_("Paths"))); + gtk_widget_set_visible (chooser->priv->path_view, TRUE); + + g_signal_connect_object (chooser->priv->path_view, "item-activated", + G_CALLBACK (gimp_item_chooser_item_activated), + G_OBJECT (chooser), 0); + g_signal_connect_object (chooser->priv->path_view, "selection-changed", + G_CALLBACK (gimp_item_chooser_items_selected), + G_OBJECT (chooser), 0); + } + + g_signal_connect_object (chooser->priv->context, "image-changed", + G_CALLBACK (gimp_item_chooser_image_changed), + G_OBJECT (chooser), 0); + + image = gimp_context_get_image (chooser->priv->context); + gimp_item_chooser_image_changed (chooser->priv->context, image, chooser); +} + +static void +gimp_item_chooser_finalize (GObject *object) +{ + GimpItemChooser *chooser = GIMP_ITEM_CHOOSER (object); + + g_clear_object (&chooser->priv->item); + g_clear_object (&chooser->priv->context); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gimp_item_chooser_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GimpItemChooser *chooser = GIMP_ITEM_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_ITEM_TYPE: + g_return_if_fail (g_value_get_gtype (value) == GIMP_TYPE_LAYER || + g_value_get_gtype (value) == GIMP_TYPE_CHANNEL || + g_value_get_gtype (value) == GIMP_TYPE_DRAWABLE || + g_value_get_gtype (value) == GIMP_TYPE_PATH || + g_value_get_gtype (value) == GIMP_TYPE_IMAGE || + g_value_get_gtype (value) == GIMP_TYPE_ITEM); + chooser->priv->item_type = g_value_get_gtype (value); + break; + + case PROP_ITEM: + g_return_if_fail (g_value_get_object (value) == NULL || + g_type_is_a (G_TYPE_FROM_INSTANCE (g_value_get_object (value)), + chooser->priv->item_type)); + gimp_item_chooser_set_item (chooser, g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_item_chooser_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpItemChooser *chooser = GIMP_ITEM_CHOOSER (object); + + switch (property_id) + { + case PROP_CONTEXT: + g_value_set_object (value, chooser->priv->context); + break; + + case PROP_ITEM_TYPE: + g_value_set_gtype (value, chooser->priv->item_type); + break; + + case PROP_ITEM: + g_value_set_object (value, chooser->priv->item); + 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_item_chooser_new (GimpContext *context, + GType item_type, + 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_ITEM_CHOOSER, + "context", context, + "item-type", item_type, + "view-size", view_size, + "view-border-width", view_border_width, + NULL); +} + +GimpItem * +gimp_item_chooser_get_item (GimpItemChooser *chooser) +{ + g_return_val_if_fail (GIMP_IS_ITEM_CHOOSER (chooser), NULL); + + return chooser->priv->item; +} + +void +gimp_item_chooser_set_item (GimpItemChooser *chooser, + GimpItem *item) +{ + if (! gtk_widget_in_destruction (GTK_WIDGET (chooser))) + { + g_signal_handlers_disconnect_by_func (chooser->priv->image_view, + G_CALLBACK (gimp_item_chooser_items_selected), + chooser); + if (chooser->priv->layer_view != NULL) + g_signal_handlers_disconnect_by_func (chooser->priv->layer_view, + G_CALLBACK (gimp_item_chooser_items_selected), + chooser); + else + g_return_if_fail (! GIMP_IS_LAYER (item)); + + if (chooser->priv->channel_view != NULL) + g_signal_handlers_disconnect_by_func (chooser->priv->channel_view, + G_CALLBACK (gimp_item_chooser_items_selected), + chooser); + else + g_return_if_fail (! GIMP_IS_CHANNEL (item)); + + if (chooser->priv->path_view != NULL) + g_signal_handlers_disconnect_by_func (chooser->priv->path_view, + G_CALLBACK (gimp_item_chooser_items_selected), + chooser); + else + g_return_if_fail (! GIMP_IS_PATH (item)); + + if (GIMP_IS_IMAGE (item)) + { + gimp_container_view_set_1_selected (GIMP_CONTAINER_VIEW (chooser->priv->image_view), + GIMP_VIEWABLE (item)); + gimp_context_set_image (chooser->priv->context, GIMP_IMAGE (item)); + } + else if (GIMP_IS_LAYER (item)) + { + gimp_context_set_image (chooser->priv->context, gimp_item_get_image (GIMP_ITEM (item))); + gimp_container_view_set_1_selected (GIMP_CONTAINER_VIEW (chooser->priv->layer_view), + GIMP_VIEWABLE (item)); + } + else if (GIMP_IS_CHANNEL (item)) + { + gimp_context_set_image (chooser->priv->context, gimp_item_get_image (GIMP_ITEM (item))); + gimp_container_view_set_1_selected (GIMP_CONTAINER_VIEW (chooser->priv->channel_view), + GIMP_VIEWABLE (item)); + } + else if (GIMP_IS_PATH (item)) + { + gimp_context_set_image (chooser->priv->context, gimp_item_get_image (GIMP_ITEM (item))); + gimp_container_view_set_1_selected (GIMP_CONTAINER_VIEW (chooser->priv->path_view), + GIMP_VIEWABLE (item)); + } + else + { + g_return_if_fail (item == NULL); + gimp_container_view_set_1_selected (GIMP_CONTAINER_VIEW (chooser->priv->image_view), NULL); + gimp_context_set_image (chooser->priv->context, NULL); + } + g_signal_connect_object (chooser->priv->image_view, "selection-changed", + G_CALLBACK (gimp_item_chooser_items_selected), + G_OBJECT (chooser), 0); + if (chooser->priv->layer_view != NULL) + g_signal_connect_object (chooser->priv->layer_view, "selection-changed", + G_CALLBACK (gimp_item_chooser_items_selected), + G_OBJECT (chooser), 0); + if (chooser->priv->channel_view != NULL) + g_signal_connect_object (chooser->priv->channel_view, "selection-changed", + G_CALLBACK (gimp_item_chooser_items_selected), + G_OBJECT (chooser), 0); + if (chooser->priv->path_view != NULL) + g_signal_connect_object (chooser->priv->path_view, "selection-changed", + G_CALLBACK (gimp_item_chooser_items_selected), + G_OBJECT (chooser), 0); + } + + if (item != chooser->priv->item) + { + g_clear_object (&chooser->priv->item); + chooser->priv->item = (item != NULL ? g_object_ref (item) : NULL); + g_object_notify (G_OBJECT (chooser), "item"); + } +} + + +/* private functions */ + +static void +gimp_item_chooser_image_changed (GimpContext *context, + GimpImage *image, + GimpItemChooser *chooser) +{ + GimpContainer *layers = NULL; + GimpContainer *channels = NULL; + GimpContainer *paths = NULL; + + if (image) + { + gchar *desc; + + layers = gimp_image_get_layers (image); + channels = gimp_image_get_channels (image); + paths = gimp_image_get_paths (image); + + desc = gimp_viewable_get_description (GIMP_VIEWABLE (image), NULL); + gtk_label_set_text (GTK_LABEL (chooser->priv->layer_label), desc); + g_free (desc); + } + else + { + gtk_label_set_text (GTK_LABEL (chooser->priv->layer_label), + _("Select an image in the left pane")); + } + + g_signal_handlers_disconnect_by_func (chooser->priv->image_view, + G_CALLBACK (gimp_item_chooser_items_selected), + chooser); + if (chooser->priv->layer_view != NULL) + { + g_signal_handlers_disconnect_by_func (chooser->priv->layer_view, + G_CALLBACK (gimp_item_chooser_items_selected), + chooser); + gimp_container_view_set_container (GIMP_CONTAINER_VIEW (chooser->priv->layer_view), + layers); + g_signal_connect_object (chooser->priv->layer_view, "selection-changed", + G_CALLBACK (gimp_item_chooser_items_selected), + G_OBJECT (chooser), 0); + } + if (chooser->priv->channel_view != NULL) + { + g_signal_handlers_disconnect_by_func (chooser->priv->channel_view, + G_CALLBACK (gimp_item_chooser_items_selected), + chooser); + gimp_container_view_set_container (GIMP_CONTAINER_VIEW (chooser->priv->channel_view), + channels); + g_signal_connect_object (chooser->priv->channel_view, "selection-changed", + G_CALLBACK (gimp_item_chooser_items_selected), + G_OBJECT (chooser), 0); + } + if (chooser->priv->path_view != NULL) + { + g_signal_handlers_disconnect_by_func (chooser->priv->path_view, + G_CALLBACK (gimp_item_chooser_items_selected), + chooser); + gimp_container_view_set_container (GIMP_CONTAINER_VIEW (chooser->priv->path_view), + paths); + g_signal_connect_object (chooser->priv->path_view, "selection-changed", + G_CALLBACK (gimp_item_chooser_items_selected), + G_OBJECT (chooser), 0); + } + g_signal_connect_object (chooser->priv->image_view, "selection-changed", + G_CALLBACK (gimp_item_chooser_items_selected), + G_OBJECT (chooser), 0); +} + +static void +gimp_item_chooser_item_activated (GimpContainerView *view, + GimpItem *item, + GimpItemChooser *chooser) +{ + g_signal_emit (chooser, signals[ACTIVATE], 0, item); +} + +static void +gimp_item_chooser_items_selected (GimpContainerView *view, + GimpItemChooser *chooser) +{ + GimpItem *item = NULL; + gint n_items; + GList *items; + + n_items = gimp_container_view_get_selected (view, &items); + + g_return_if_fail (n_items <= 1); + + if (items) + item = items->data; + + gimp_item_chooser_set_item (chooser, item); +} diff --git a/app/widgets/gimpitemchooser.h b/app/widgets/gimpitemchooser.h new file mode 100644 index 0000000000..0017dad652 --- /dev/null +++ b/app/widgets/gimpitemchooser.h @@ -0,0 +1,61 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpitemchooser.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_ITEM_CHOOSER (gimp_item_chooser_get_type ()) +#define GIMP_ITEM_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ITEM_CHOOSER, GimpItemChooser)) +#define GIMP_ITEM_CHOOSER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ITEM_CHOOSER, GimpItemChooserClass)) +#define GIMP_IS_ITEM_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ITEM_CHOOSER)) +#define GIMP_IS_ITEM_CHOOSER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_ITEM_CHOOSER)) +#define GIMP_ITEM_CHOOSER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_ITEM_CHOOSER, GimpItemChooserClass)) + + +typedef struct _GimpItemChooserPrivate GimpItemChooserPrivate; +typedef struct _GimpItemChooserClass GimpItemChooserClass; + +struct _GimpItemChooser +{ + GtkFrame parent_instance; + + GimpItemChooserPrivate *priv; +}; + +struct _GimpItemChooserClass +{ + GtkFrameClass parent_instance; + + /* Signals. */ + + void (* activate) (GimpItemChooser *view, + GimpItem *item); +}; + + +GType gimp_item_chooser_get_type (void) G_GNUC_CONST; + +GtkWidget * gimp_item_chooser_new (GimpContext *context, + GType item_type, + gint view_size, + gint view_border_width); + +GimpItem * gimp_item_chooser_get_item (GimpItemChooser *chooser); +void gimp_item_chooser_set_item (GimpItemChooser *chooser, + GimpItem *item); diff --git a/app/widgets/gimpitemselect.c b/app/widgets/gimpitemselect.c new file mode 100644 index 0000000000..241af3bca0 --- /dev/null +++ b/app/widgets/gimpitemselect.c @@ -0,0 +1,174 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpitemselect.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/gimpitem.h" +#include "core/gimpparamspecs.h" +#include "core/gimptempbuf.h" + +#include "pdb/gimppdb.h" + +#include "gimpitemchooser.h" +#include "gimpitemselect.h" +#include "gimpcontainerbox.h" + +#include "gimp-intl.h" + + +static void gimp_item_select_constructed (GObject *object); + +static GimpValueArray * gimp_item_select_run_callback (GimpPdbDialog *dialog, + GimpObject *object, + gboolean closing, + GError **error); +static GimpObject * gimp_item_select_get_object (GimpPdbDialog *dialog); +static void gimp_item_select_set_object (GimpPdbDialog *dialog, + GimpObject *object); + +static void gimp_item_select_activate (GimpItemSelect *select); +static void gimp_item_select_notify_item (GimpItemSelect *select); + + +G_DEFINE_TYPE (GimpItemSelect, gimp_item_select, GIMP_TYPE_PDB_DIALOG) + +#define parent_class gimp_item_select_parent_class + + +static void +gimp_item_select_class_init (GimpItemSelectClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GimpPdbDialogClass *pdb_class = GIMP_PDB_DIALOG_CLASS (klass); + + object_class->constructed = gimp_item_select_constructed; + + pdb_class->run_callback = gimp_item_select_run_callback; + pdb_class->get_object = gimp_item_select_get_object; + pdb_class->set_object = gimp_item_select_set_object; +} + +static void +gimp_item_select_init (GimpItemSelect *select) +{ +} + +static void +gimp_item_select_constructed (GObject *object) +{ + GimpPdbDialog *dialog = GIMP_PDB_DIALOG (object); + GimpItemSelect *select = GIMP_ITEM_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_item_chooser_new (dialog->context, dialog->select_type, + GIMP_VIEW_SIZE_LARGE, 1); + gimp_item_chooser_set_item (GIMP_ITEM_CHOOSER (select->chooser), + GIMP_ITEM (dialog->initial_object)); + g_signal_connect_swapped (select->chooser, "notify::item", + G_CALLBACK (gimp_item_select_notify_item), + select); + g_signal_connect_swapped (select->chooser, "activate", + G_CALLBACK (gimp_item_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_item_select_run_callback (GimpPdbDialog *dialog, + GimpObject *object, + gboolean closing, + GError **error) +{ + GimpItem *item = GIMP_ITEM (object); + GimpValueArray *return_vals; + + return_vals = + gimp_pdb_execute_procedure_by_name (dialog->pdb, + dialog->caller_context, + NULL, error, + dialog->callback_name, + GIMP_TYPE_ITEM, item, + G_TYPE_BOOLEAN, closing, + G_TYPE_NONE); + + return return_vals; +} + +static GimpObject * +gimp_item_select_get_object (GimpPdbDialog *dialog) +{ + GimpItemSelect *select = GIMP_ITEM_SELECT (dialog); + + return (GimpObject *) gimp_item_chooser_get_item (GIMP_ITEM_CHOOSER (select->chooser)); +} + +static void +gimp_item_select_set_object (GimpPdbDialog *dialog, + GimpObject *object) +{ + GimpItemSelect *select = GIMP_ITEM_SELECT (dialog); + + gimp_item_chooser_set_item (GIMP_ITEM_CHOOSER (select->chooser), GIMP_ITEM (object)); +} + +static void +gimp_item_select_activate (GimpItemSelect *select) +{ + gimp_pdb_dialog_run_callback ((GimpPdbDialog **) &select, TRUE); + gtk_widget_destroy (GTK_WIDGET (select)); +} + +static void +gimp_item_select_notify_item (GimpItemSelect *select) +{ + GtkWidget *button; + GimpItemChooser *chooser = GIMP_ITEM_CHOOSER (select->chooser); + + button = gtk_dialog_get_widget_for_response (GTK_DIALOG (select), + GTK_RESPONSE_OK); + + /* Clicking Okay when an image but not an item has been + * selected causes the widget to "lock up" and not reappear when you + * click on it again. For now, we'll disable the Okay button until + * an item is selected */ + if (GIMP_IS_IMAGE (gimp_item_chooser_get_item (chooser))) + gtk_widget_set_sensitive (button, FALSE); + else + gtk_widget_set_sensitive (button, TRUE); + + gimp_pdb_dialog_run_callback ((GimpPdbDialog **) &select, FALSE); +} diff --git a/app/widgets/gimpitemselect.h b/app/widgets/gimpitemselect.h new file mode 100644 index 0000000000..365a633c27 --- /dev/null +++ b/app/widgets/gimpitemselect.h @@ -0,0 +1,48 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * gimpitemselect.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_ITEM_SELECT (gimp_item_select_get_type ()) +#define GIMP_ITEM_SELECT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GIMP_TYPE_ITEM_SELECT, GimpItemSelect)) +#define GIMP_ITEM_SELECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GIMP_TYPE_ITEM_SELECT, GimpItemSelectClass)) +#define GIMP_IS_ITEM_SELECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GIMP_TYPE_ITEM_SELECT)) +#define GIMP_IS_ITEM_SELECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GIMP_TYPE_ITEM_SELECT)) +#define GIMP_ITEM_SELECT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GIMP_TYPE_ITEM_SELECT, GimpItemSelectClass)) + + +typedef struct _GimpItemSelectClass GimpItemSelectClass; + +struct _GimpItemSelect +{ + GimpPdbDialog parent_instance; + + GtkWidget *chooser; +}; + +struct _GimpItemSelectClass +{ + GimpPdbDialogClass parent_class; +}; + + +GType gimp_item_select_get_type (void) G_GNUC_CONST; diff --git a/app/widgets/meson.build b/app/widgets/meson.build index 8b63b0fe63..4b75ba84eb 100644 --- a/app/widgets/meson.build +++ b/app/widgets/meson.build @@ -145,6 +145,8 @@ libappwidgets_sources = [ 'gimpimagepropview.c', 'gimpimageselect.c', 'gimpimageview.c', + 'gimpitemchooser.c', + 'gimpitemselect.c', 'gimpitemtreeview.c', 'gimpitemtreeview-search.c', 'gimplanguagecombobox.c', diff --git a/app/widgets/widgets-types.h b/app/widgets/widgets-types.h index 7dc5fb43b3..e59fb997be 100644 --- a/app/widgets/widgets-types.h +++ b/app/widgets/widgets-types.h @@ -163,6 +163,7 @@ typedef struct _GimpBrushSelect GimpBrushSelect; typedef struct _GimpFontSelect GimpFontSelect; typedef struct _GimpGradientSelect GimpGradientSelect; typedef struct _GimpImageSelect GimpImageSelect; +typedef struct _GimpItemSelect GimpItemSelect; typedef struct _GimpPaletteSelect GimpPaletteSelect; typedef struct _GimpPatternSelect GimpPatternSelect; typedef struct _GimpPickableSelect GimpPickableSelect; @@ -210,6 +211,7 @@ typedef struct _GimpImageCommentEditor GimpImageCommentEditor; typedef struct _GimpImageParasiteView GimpImageParasiteView; typedef struct _GimpImageProfileView GimpImageProfileView; typedef struct _GimpImagePropView GimpImagePropView; +typedef struct _GimpItemChooser GimpItemChooser; typedef struct _GimpLanguageComboBox GimpLanguageComboBox; typedef struct _GimpLanguageEntry GimpLanguageEntry; typedef struct _GimpLanguageStore GimpLanguageStore; diff --git a/libgimp/gimp.def b/libgimp/gimp.def index e0eb48909a..4078b8ef5a 100644 --- a/libgimp/gimp.def +++ b/libgimp/gimp.def @@ -643,6 +643,9 @@ EXPORTS gimp_item_transform_scale gimp_item_transform_shear gimp_item_transform_translate + gimp_items_close_popup + gimp_items_popup + gimp_items_set_popup gimp_layer_add_alpha gimp_layer_add_mask gimp_layer_color_space_get_type diff --git a/libgimp/gimpitem_pdb.c b/libgimp/gimpitem_pdb.c index 577a3fb246..1b9cc65a80 100644 --- a/libgimp/gimpitem_pdb.c +++ b/libgimp/gimpitem_pdb.c @@ -1427,3 +1427,117 @@ gimp_item_get_parasite_list (GimpItem *item) return parasites; } + +/** + * gimp_items_popup: + * @callback: The callback PDB proc to call when user chooses an item. + * @popup_title: Title of the item selection dialog. + * @item_type: The name of the GIMP_TYPE_ITEM subtype. + * @initial_item: (nullable): The item to set as the initial choice. + * @parent_window: (nullable): An optional parent window handle for the popup to be set transient to. + * + * Invokes the item selection dialog. + * + * Opens a dialog letting a user choose an item . + * + * Returns: TRUE on success. + **/ +gboolean +gimp_items_popup (const gchar *callback, + const gchar *popup_title, + const gchar *item_type, + GimpItem *initial_item, + 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, + G_TYPE_STRING, item_type, + GIMP_TYPE_ITEM, initial_item, + G_TYPE_BYTES, parent_window, + G_TYPE_NONE); + + return_vals = _gimp_pdb_run_procedure_array (gimp_get_pdb (), + "gimp-items-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_items_close_popup: + * @callback: The name of the callback registered for this pop-up. + * + * Close the item selection dialog. + * + * Closes an open item selection dialog. + * + * Returns: TRUE on success. + **/ +gboolean +gimp_items_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-items-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_items_set_popup: + * @callback: The name of the callback registered for this pop-up. + * @item: The item to set as selected. + * + * Sets the selected item in a item selection dialog. + * + * Sets the selected item in a item selection dialog. + * + * Returns: TRUE on success. + **/ +gboolean +gimp_items_set_popup (const gchar *callback, + GimpItem *item) +{ + GimpValueArray *args; + GimpValueArray *return_vals; + gboolean success = TRUE; + + args = gimp_value_array_new_from_types (NULL, + G_TYPE_STRING, callback, + GIMP_TYPE_ITEM, item, + G_TYPE_NONE); + + return_vals = _gimp_pdb_run_procedure_array (gimp_get_pdb (), + "gimp-items-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/gimpitem_pdb.h b/libgimp/gimpitem_pdb.h index a4801f6565..d417def45c 100644 --- a/libgimp/gimpitem_pdb.h +++ b/libgimp/gimpitem_pdb.h @@ -79,6 +79,14 @@ gboolean gimp_item_detach_parasite (GimpItem *item, GimpParasite* gimp_item_get_parasite (GimpItem *item, const gchar *name); gchar** gimp_item_get_parasite_list (GimpItem *item); +gboolean gimp_items_popup (const gchar *callback, + const gchar *popup_title, + const gchar *item_type, + GimpItem *initial_item, + GBytes *parent_window); +gboolean gimp_items_close_popup (const gchar *callback); +gboolean gimp_items_set_popup (const gchar *callback, + GimpItem *item); G_END_DECLS diff --git a/libgimp/gimpitemchooser.c b/libgimp/gimpitemchooser.c new file mode 100644 index 0000000000..8a07cfaa32 --- /dev/null +++ b/libgimp/gimpitemchooser.c @@ -0,0 +1,630 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpitemchooser.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 + * 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 "gimpitemchooser.h" +#include "gimpuimarshal.h" + +#include "libgimp-intl.h" + + +/** + * SECTION: gimpitemchooser + * @title: GimpItemChooser + * @short_description: A widget allowing to select an item. + * + * The chooser contains an optional label and a button which queries the core + * process to pop up a item selection dialog. + * + * Since: 3.0 + **/ + +#define CELL_SIZE 40 + +enum +{ + PROP_0, + PROP_TITLE, + PROP_LABEL, + PROP_ITEM, + PROP_ITEM_TYPE, + N_PROPS +}; + +struct _GimpItemChooser +{ + GtkBox parent_instance; + + GType item_type; + GimpItem *item; + gchar *title; + gchar *label; + gchar *callback; + + GBytes *thumbnail; + GimpItem *thumbnail_item; + gint width; + gint height; + gint bpp; + + GtkWidget *label_widget; + GtkWidget *preview_frame; + GtkWidget *preview; + GtkWidget *preview_title; +}; + + +/* local function prototypes */ + +static void gimp_item_chooser_constructed (GObject *object); +static void gimp_item_chooser_dispose (GObject *object); +static void gimp_item_chooser_finalize (GObject *object); + +static void gimp_item_chooser_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); +static void gimp_item_chooser_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void gimp_item_chooser_clicked (GimpItemChooser *chooser); + +static GimpValueArray * gimp_temp_callback_run (GimpProcedure *procedure, + GimpProcedureConfig *config, + GimpItemChooser *chooser); +static gboolean gimp_item_select_remove_after_run (const gchar *procedure_name); + +static void gimp_item_chooser_draw (GimpItemChooser *chooser); +static void gimp_item_chooser_get_thumbnail (GimpItemChooser *chooser, + gint width, + gint height); + + +static GParamSpec *item_button_props[N_PROPS] = { NULL, }; + +G_DEFINE_FINAL_TYPE (GimpItemChooser, gimp_item_chooser, GTK_TYPE_BOX) + + +static void +gimp_item_chooser_class_init (GimpItemChooserClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gimp_item_chooser_constructed; + object_class->dispose = gimp_item_chooser_dispose; + object_class->finalize = gimp_item_chooser_finalize; + object_class->set_property = gimp_item_chooser_set_property; + object_class->get_property = gimp_item_chooser_get_property; + + /** + * GimpItemChooser:title: + * + * The title to be used for the item selection popup dialog. + * + * Since: 3.0 + */ + item_button_props[PROP_TITLE] = + g_param_spec_string ("title", + "Title", + "The title to be used for the item selection popup dialog", + "Item Selection", + GIMP_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY); + + /** + * GimpItemChooser:label: + * + * Label text with mnemonic. + * + * Since: 3.0 + */ + item_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); + + /** + * GimpItemChooser:item: + * + * The currently selected item. + * + * Since: 3.0 + */ + item_button_props[PROP_ITEM] = + gimp_param_spec_item ("item", + "Item", + "The currently selected item", + TRUE, + GIMP_PARAM_READWRITE | + G_PARAM_EXPLICIT_NOTIFY); + + /** + * GimpItemChooser:item-type: + * + * Allowed item types, which must be either GIMP_TYPE_ITEM or a + * subtype. + * + * Since: 3.0 + */ + item_button_props[PROP_ITEM_TYPE] = + g_param_spec_gtype ("item-type", + "Allowed item Type", + "The GType of the item property", + GIMP_TYPE_ITEM, + G_PARAM_CONSTRUCT_ONLY | + GIMP_PARAM_READWRITE); + + g_object_class_install_properties (object_class, + N_PROPS, item_button_props); +} + +static void +gimp_item_chooser_init (GimpItemChooser *chooser) +{ + gtk_orientable_set_orientation (GTK_ORIENTABLE (chooser), + GTK_ORIENTATION_HORIZONTAL); + gtk_box_set_spacing (GTK_BOX (chooser), 6); + + chooser->thumbnail_item = NULL; + chooser->thumbnail = NULL; +} + +static void +gimp_item_chooser_constructed (GObject *object) +{ + GimpItemChooser *chooser = GIMP_ITEM_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_item_chooser_clicked), + chooser); + + G_OBJECT_CLASS (gimp_item_chooser_parent_class)->constructed (object); +} + +static void +gimp_item_chooser_dispose (GObject *object) +{ + GimpItemChooser *chooser = GIMP_ITEM_CHOOSER (object); + + if (chooser->callback) + { + gimp_items_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_item_chooser_parent_class)->dispose (object); +} + +static void +gimp_item_chooser_finalize (GObject *object) +{ + GimpItemChooser *chooser = GIMP_ITEM_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_item_chooser_parent_class)->finalize (object); +} + +static void +gimp_item_chooser_set_property (GObject *object, + guint property_id, + const GValue *gvalue, + GParamSpec *pspec) +{ + GimpItemChooser *chooser = GIMP_ITEM_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_ITEM: + g_return_if_fail (g_value_get_object (gvalue) == NULL || + g_type_is_a (G_TYPE_FROM_INSTANCE (g_value_get_object (gvalue)), + chooser->item_type)); + gimp_item_chooser_set_item (chooser, g_value_get_object (gvalue)); + break; + + case PROP_ITEM_TYPE: + chooser->item_type = g_value_get_gtype (gvalue); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gimp_item_chooser_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GimpItemChooser *chooser = GIMP_ITEM_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_ITEM: + g_value_set_object (value, chooser->item); + break; + + case PROP_ITEM_TYPE: + g_value_set_gtype (value, chooser->item_type); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +/** + * gimp_item_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. + * @item_type: the acceptable subtype of choosable items. + * @item: (nullable): Initial item. + * + * Creates a new #GtkWidget that lets a user choose a item which must be of + * type @item_type. @item_type of values %G_TYPE_NONE and + * %GIMP_TYPE_ITEM are equivalent. Otherwise it must be a subtype of + * %GIMP_TYPE_ITEM. + * + * When @item is %NULL, initial choice is from context. + * + * Returns: A [class@GimpUi.ItemChooser. + * + * Since: 3.0 + */ +GtkWidget * +gimp_item_chooser_new (const gchar *title, + const gchar *label, + GType item_type, + GimpItem *item) +{ + GtkWidget *chooser; + + if (item_type == G_TYPE_NONE) + item_type = GIMP_TYPE_ITEM; + + g_return_val_if_fail (g_type_is_a (item_type, GIMP_TYPE_ITEM), NULL); + g_return_val_if_fail (item == NULL || + g_type_is_a (G_TYPE_FROM_INSTANCE (item), item_type), + NULL); + + chooser = g_object_new (GIMP_TYPE_ITEM_CHOOSER, + "title", title, + "label", label, + "item", item, + "item-type", item_type, + NULL); + + return chooser; +} + +/** + * gimp_item_chooser_get_item: + * @chooser: A #GimpItemChooser + * + * Gets the currently selected item. + * + * Returns: (transfer none): an internal copy of the item which must not be freed. + * + * Since: 3.0 + */ +GimpItem * +gimp_item_chooser_get_item (GimpItemChooser *chooser) +{ + g_return_val_if_fail (GIMP_IS_ITEM_CHOOSER (chooser), NULL); + + return chooser->item; +} + +/** + * gimp_item_chooser_set_item: + * @chooser: A #GimpItemChooser + * @item: Item to set. + * + * Sets the currently selected item. + * This will select the item in both the button and any chooser popup. + * + * Since: 3.0 + */ +void +gimp_item_chooser_set_item (GimpItemChooser *chooser, + GimpItem *item) +{ + g_return_if_fail (GIMP_IS_ITEM_CHOOSER (chooser)); + g_return_if_fail (item == NULL || GIMP_IS_ITEM (item)); + + chooser->item = item; + + if (chooser->callback) + gimp_items_set_popup (chooser->callback, chooser->item); + + g_object_notify_by_pspec (G_OBJECT (chooser), item_button_props[PROP_ITEM]); + + gimp_item_chooser_draw (chooser); +} + +/** + * gimp_item_chooser_get_label: + * @widget: A [class@ItemChooser]. + * + * Returns the label widget. + * + * Returns: (transfer none): the [class@Gtk.Widget] showing the label text. + * Since: 3.0 + */ +GtkWidget * +gimp_item_chooser_get_label (GimpItemChooser *chooser) +{ + g_return_val_if_fail (GIMP_IS_ITEM_CHOOSER (chooser), NULL); + + return chooser->label_widget; +} + + +/* private functions */ + +static GimpValueArray * +gimp_temp_callback_run (GimpProcedure *procedure, + GimpProcedureConfig *config, + GimpItemChooser *chooser) +{ + GimpItem *item; + gboolean closing; + + g_object_get (config, + "item", &item, + "closing", &closing, + NULL); + + g_object_set (chooser, "item", item, NULL); + + if (closing) + { + g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, + (GSourceFunc) gimp_item_select_remove_after_run, + g_strdup (gimp_procedure_get_name (procedure)), + g_free); + g_clear_pointer (&chooser->callback, g_free); + } + + g_clear_object (&item); + + return gimp_procedure_new_return_values (procedure, GIMP_PDB_SUCCESS, NULL); +} + +static gboolean +gimp_item_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_item_chooser_clicked (GimpItemChooser *chooser) +{ + if (chooser->callback) + { + /* Popup already created. Calling setter raises the popup. */ + gimp_items_set_popup (chooser->callback, chooser->item); + } + 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_item_argument (callback_procedure, "item", + "Item", "The selected item", + 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_items_popup (gimp_procedure_get_name (callback_procedure), chooser->title, + g_type_name (chooser->item_type), chooser->item, 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 item select dialog.", G_STRFUNC); + gimp_plug_in_remove_temp_procedure (plug_in, gimp_procedure_get_name (callback_procedure)); + return; + } + gimp_items_set_popup (chooser->callback, chooser->item); + } +} + +static void +gimp_item_chooser_draw (GimpItemChooser *chooser) +{ + GimpPreviewArea *area = GIMP_PREVIEW_AREA (chooser->preview); + GtkAllocation allocation; + gint x = 0; + gint y = 0; + + gtk_widget_get_allocation (chooser->preview, &allocation); + + gimp_item_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); + } + else + { + /* TODO: GimpItems like GimpPath do not have a built-in drawable, + * but it is possible to get a preview from it. We should add a + * standard way to generate this preview. */ + if (chooser->item) + gtk_label_set_text (GTK_LABEL (chooser->preview_title), + gimp_item_get_name (chooser->item)); + } +} + +static void +gimp_item_chooser_get_thumbnail (GimpItemChooser *chooser, + gint width, + gint height) +{ + if (chooser->item == chooser->thumbnail_item && + chooser->width == width && + chooser->height == height) + /* Let's assume item contents is not changing in a single run. */ + return; + + g_clear_pointer (&chooser->thumbnail, g_bytes_unref); + chooser->width = chooser->height = 0; + + chooser->thumbnail_item = chooser->item; + if (chooser->item) + { + if (GIMP_IS_DRAWABLE (chooser->item)) + { + chooser->thumbnail = + gimp_drawable_get_thumbnail_data (GIMP_DRAWABLE (chooser->item), + width, height, + &chooser->width, + &chooser->height, + &chooser->bpp); + } + } +} diff --git a/libgimp/gimpitemchooser.h b/libgimp/gimpitemchooser.h new file mode 100644 index 0000000000..00099fffa0 --- /dev/null +++ b/libgimp/gimpitemchooser.h @@ -0,0 +1,47 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpitemchooser.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 + * 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_ITEM_CHOOSER_H__ +#define __GIMP_ITEM_CHOOSER_H__ + +G_BEGIN_DECLS + +#define GIMP_TYPE_ITEM_CHOOSER (gimp_item_chooser_get_type ()) +G_DECLARE_FINAL_TYPE (GimpItemChooser, gimp_item_chooser, GIMP, ITEM_CHOOSER, GtkBox) + + +GtkWidget * gimp_item_chooser_new (const gchar *title, + const gchar *label, + GType item_type, + GimpItem *item); + +GimpItem * gimp_item_chooser_get_item (GimpItemChooser *chooser); +void gimp_item_chooser_set_item (GimpItemChooser *chooser, + GimpItem *item); +GtkWidget * gimp_item_chooser_get_label (GimpItemChooser *widget); + + +G_END_DECLS + +#endif /* __GIMP_ITEM_CHOOSER_H__ */ diff --git a/libgimp/gimpproceduredialog.c b/libgimp/gimpproceduredialog.c index ff215d4451..8c6a231016 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_PATH)) + { + widget = gimp_prop_item_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); @@ -966,6 +970,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_ITEM_CHOOSER (widget)) + label = gimp_item_chooser_get_label (GIMP_ITEM_CHOOSER (widget)); else if (GIMP_IS_IMAGE_CHOOSER (widget)) label = gimp_image_chooser_get_label (GIMP_IMAGE_CHOOSER (widget)); } @@ -2922,6 +2928,10 @@ gimp_procedure_dialog_check_mnemonic (GimpProcedureDialog *dialog, { label = gimp_drawable_chooser_get_label (GIMP_DRAWABLE_CHOOSER (widget)); } + else if (GIMP_IS_ITEM_CHOOSER (widget)) + { + label = gimp_item_chooser_get_label (GIMP_ITEM_CHOOSER (widget)); + } else if (GIMP_IS_IMAGE_CHOOSER (widget)) { label = gimp_image_chooser_get_label (GIMP_IMAGE_CHOOSER (widget)); diff --git a/libgimp/gimppropwidgets.c b/libgimp/gimppropwidgets.c index 76c42c57c1..c1ba42c8e5 100644 --- a/libgimp/gimppropwidgets.c +++ b/libgimp/gimppropwidgets.c @@ -206,6 +206,74 @@ gimp_prop_image_chooser_new (GObject *config, return prop_chooser; } +/** + * gimp_prop_item_chooser_new: + * @config: Object to which property is attached. + * @property_name: Name of a [class@Gimp.Item] property. + * @chooser_title: (nullable): title for the poppable dialog. + * + * Creates a [class@GimpUi.ItemChooser] controlled by the specified property. + * + * Returns: (transfer full): A new [class@GimpUi.ItemChooser]. + * + * Since: 3.0 + */ +GtkWidget * +gimp_prop_item_chooser_new (GObject *config, + const gchar *property_name, + const gchar *chooser_title) +{ + GParamSpec *param_spec; + GtkWidget *prop_chooser; + GimpItem *initial_item = 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_ITEM), NULL); + + g_object_get (config, + property_name, &initial_item, + NULL); + + label = g_param_spec_get_nick (param_spec); + + if (chooser_title == NULL) + { + gchar *canonical; + + canonical = gimp_utils_make_canonical_menu_label (label); + if (g_type_is_a (param_spec->value_type, GIMP_TYPE_LAYER)) + title = g_strdup_printf (_("Choose layer: %s"), canonical); + if (g_type_is_a (param_spec->value_type, GIMP_TYPE_CHANNEL)) + title = g_strdup_printf (_("Choose channel: %s"), canonical); + if (g_type_is_a (param_spec->value_type, GIMP_TYPE_PATH)) + title = g_strdup_printf (_("Choose path: %s"), canonical); + else + title = g_strdup_printf (_("Choose item: %s"), canonical); + g_free (canonical); + } + else + { + title = g_strdup (chooser_title); + } + + prop_chooser = gimp_item_chooser_new (title, label, param_spec->value_type, + initial_item); + g_clear_object (&initial_item); + g_free (title); + + g_object_bind_property (prop_chooser, "item", + 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 dddc006a8a..32c2022074 100644 --- a/libgimp/gimppropwidgets.h +++ b/libgimp/gimppropwidgets.h @@ -51,6 +51,9 @@ GtkWidget * gimp_prop_pattern_chooser_new (GObject *config, GtkWidget * gimp_prop_image_chooser_new (GObject *config, const gchar *property_name, const gchar *chooser_title); +GtkWidget * gimp_prop_item_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 e7453c96b8..947c3f2a35 100644 --- a/libgimp/gimpui.def +++ b/libgimp/gimpui.def @@ -29,6 +29,11 @@ EXPORTS gimp_image_chooser_set_image gimp_image_combo_box_get_type gimp_image_combo_box_new + gimp_item_chooser_get_item + gimp_item_chooser_get_label + gimp_item_chooser_get_type + gimp_item_chooser_new + gimp_item_chooser_set_item gimp_layer_combo_box_get_type gimp_layer_combo_box_new gimp_palette_chooser_get_type @@ -76,6 +81,7 @@ EXPORTS gimp_prop_font_chooser_new gimp_prop_gradient_chooser_new gimp_prop_image_chooser_new + gimp_prop_item_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 81570cc854..f3818535e7 100644 --- a/libgimp/gimpui.h +++ b/libgimp/gimpui.h @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include diff --git a/libgimp/gimpuitypes.h b/libgimp/gimpuitypes.h index eb9ad8810a..7ed4814bf6 100644 --- a/libgimp/gimpuitypes.h +++ b/libgimp/gimpuitypes.h @@ -51,6 +51,7 @@ typedef struct _GimpBrushChooser GimpBrushChooser; typedef struct _GimpFontChooser GimpFontChooser; typedef struct _GimpGradientChooser GimpGradientChooser; typedef struct _GimpImageChooser GimpImageChooser; +typedef struct _GimpItemChooser GimpItemChooser; typedef struct _GimpPaletteChooser GimpPaletteChooser; typedef struct _GimpPatternChooser GimpPatternChooser; diff --git a/libgimp/meson.build b/libgimp/meson.build index acb0580d61..c12e60c2cd 100644 --- a/libgimp/meson.build +++ b/libgimp/meson.build @@ -305,6 +305,7 @@ libgimpui_sources_introspectable = [ 'gimpgradientchooser.c', 'gimpimagechooser.c', 'gimpimagecombobox.c', + 'gimpitemchooser.c', 'gimpitemcombobox.c', 'gimppalettechooser.c', 'gimppatternchooser.c', @@ -342,6 +343,7 @@ libgimpui_headers_introspectable = [ 'gimpgradientchooser.h', 'gimpimagechooser.h', 'gimpimagecombobox.h', + 'gimpitemchooser.h', 'gimpitemcombobox.h', 'gimppalettechooser.h', 'gimppatternchooser.h', diff --git a/pdb/groups/item.pdb b/pdb/groups/item.pdb index 6b5fcf7f86..0abad7c502 100644 --- a/pdb/groups/item.pdb +++ b/pdb/groups/item.pdb @@ -1121,7 +1121,92 @@ CODE ); } -@headers = qw("core/gimpgrouplayer.h" +sub items_popup { + $blurb = 'Invokes the item selection dialog.'; + $help = 'Opens a dialog letting a user choose an item .'; + + &alxsa_pdb_misc('2025'); + + @inargs = ( + { name => 'callback', type => 'string', non_empty => 1, + desc => 'The callback PDB proc to call when user chooses an item ' }, + { name => 'popup_title', type => 'string', + desc => 'Title of the item selection dialog' }, + { name => 'item_type', type => 'string', non_empty => 1, + desc => 'The name of the GIMP_TYPE_ITEM subtype' }, + { name => 'initial_item', type => 'item ', none_ok => 1, no_validate => 1, + desc => 'The item 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, + g_type_from_name (item_type), + parent_window, popup_title, callback, + GIMP_OBJECT (initial_item), + NULL)) + success = FALSE; +} +CODE + ); +} + +sub items_close_popup { + $blurb = 'Close the item selection dialog.'; + $help = 'Closes an open item 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_ITEM, callback)) + success = FALSE; +} +CODE + ); +} + +sub items_set_popup { + $blurb = 'Sets the selected item in a item 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 => 'item', type => 'item', no_validate => 1, + desc => 'The item 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_ITEM, callback, GIMP_OBJECT (item), NULL)) + success = FALSE; +} +CODE + ); +} + +@headers = qw("core/gimp.h" + "core/gimpdatafactory.h" + "core/gimpgrouplayer.h" "core/gimplayermask.h" "core/gimplinklayer.h" "core/gimplist.h" @@ -1159,7 +1244,10 @@ CODE item_get_tattoo item_set_tattoo item_attach_parasite item_detach_parasite item_get_parasite - item_get_parasite_list); + item_get_parasite_list + items_popup + items_close_popup + items_set_popup); %exports = (app => [@procs], lib => [@procs]);