diff --git a/app/pdb/drawable-select-cmds.c b/app/pdb/drawable-select-cmds.c
new file mode 100644
index 0000000..467cbe4
--- /dev/null
+++ b/app/pdb/drawable-select-cmds.c
@@ -0,0 +1,243 @@
+/* PIKA - Photo and Image Kooker Application
+ * a rebranding of The GNU Image Manipulation Program (created with heckimp)
+ * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
+ *
+ * Original copyright, applying to most contents (license remains unchanged):
+ * Copyright (C) 1995-2003 Spencer Kimball and Peter Mattis
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+/* NOTE: This file is auto-generated by pdbgen.pl. */
+
+#include "config.h"
+
+#include "stamp-pdbgen.h"
+
+#include
+
+#include
+
+#include "libpikabase/pikabase.h"
+
+#include "pdb-types.h"
+
+#include "core/pika.h"
+#include "core/pikadatafactory.h"
+#include "core/pikadrawable.h"
+#include "core/pikaparamspecs.h"
+
+#include "pikapdb.h"
+#include "pikaprocedure.h"
+#include "internal-procs.h"
+
+
+static PikaValueArray *
+drawables_popup_invoker (PikaProcedure *procedure,
+ Pika *pika,
+ PikaContext *context,
+ PikaProgress *progress,
+ const PikaValueArray *args,
+ GError **error)
+{
+ gboolean success = TRUE;
+ const gchar *callback;
+ const gchar *popup_title;
+ const gchar *drawable_type;
+ PikaDrawable *initial_drawable;
+ GBytes *parent_window;
+
+ callback = g_value_get_string (pika_value_array_index (args, 0));
+ popup_title = g_value_get_string (pika_value_array_index (args, 1));
+ drawable_type = g_value_get_string (pika_value_array_index (args, 2));
+ initial_drawable = g_value_get_object (pika_value_array_index (args, 3));
+ parent_window = g_value_get_boxed (pika_value_array_index (args, 4));
+
+ if (success)
+ {
+ if (pika->no_interface ||
+ ! pika_pdb_lookup_procedure (pika->pdb, callback) ||
+ ! pika_pdb_dialog_new (pika, context, progress,
+ g_type_from_name (drawable_type),
+ parent_window, popup_title, callback,
+ PIKA_OBJECT (initial_drawable),
+ NULL))
+ success = FALSE;
+ }
+
+ return pika_procedure_get_return_values (procedure, success,
+ error ? *error : NULL);
+}
+
+static PikaValueArray *
+drawables_close_popup_invoker (PikaProcedure *procedure,
+ Pika *pika,
+ PikaContext *context,
+ PikaProgress *progress,
+ const PikaValueArray *args,
+ GError **error)
+{
+ gboolean success = TRUE;
+ const gchar *callback;
+
+ callback = g_value_get_string (pika_value_array_index (args, 0));
+
+ if (success)
+ {
+ if (pika->no_interface ||
+ ! pika_pdb_lookup_procedure (pika->pdb, callback) ||
+ ! pika_pdb_dialog_close (pika, PIKA_TYPE_DRAWABLE, callback))
+ success = FALSE;
+ }
+
+ return pika_procedure_get_return_values (procedure, success,
+ error ? *error : NULL);
+}
+
+static PikaValueArray *
+drawables_set_popup_invoker (PikaProcedure *procedure,
+ Pika *pika,
+ PikaContext *context,
+ PikaProgress *progress,
+ const PikaValueArray *args,
+ GError **error)
+{
+ gboolean success = TRUE;
+ const gchar *callback;
+ PikaDrawable *drawable;
+
+ callback = g_value_get_string (pika_value_array_index (args, 0));
+ drawable = g_value_get_object (pika_value_array_index (args, 1));
+
+ if (success)
+ {
+ if (pika->no_interface ||
+ ! pika_pdb_lookup_procedure (pika->pdb, callback) ||
+ ! pika_pdb_dialog_set (pika, PIKA_TYPE_DRAWABLE, callback, PIKA_OBJECT (drawable), NULL))
+ success = FALSE;
+ }
+
+ return pika_procedure_get_return_values (procedure, success,
+ error ? *error : NULL);
+}
+
+void
+register_drawable_select_procs (PikaPDB *pdb)
+{
+ PikaProcedure *procedure;
+
+ /*
+ * pika-drawables-popup
+ */
+ procedure = pika_procedure_new (drawables_popup_invoker);
+ pika_object_set_static_name (PIKA_OBJECT (procedure),
+ "pika-drawables-popup");
+ pika_procedure_set_static_help (procedure,
+ "Invokes the drawable selection dialog.",
+ "Opens a dialog letting a user choose an drawable.",
+ NULL);
+ pika_procedure_set_static_attribution (procedure,
+ "Jehan",
+ "Jehan",
+ "2023");
+ pika_procedure_add_argument (procedure,
+ pika_param_spec_string ("callback",
+ "callback",
+ "The callback PDB proc to call when user chooses an drawable",
+ FALSE, FALSE, TRUE,
+ NULL,
+ PIKA_PARAM_READWRITE));
+ pika_procedure_add_argument (procedure,
+ pika_param_spec_string ("popup-title",
+ "popup title",
+ "Title of the drawable selection dialog",
+ FALSE, FALSE, FALSE,
+ NULL,
+ PIKA_PARAM_READWRITE));
+ pika_procedure_add_argument (procedure,
+ pika_param_spec_string ("drawable-type",
+ "drawable type",
+ "The name of the PIKA_TYPE_DRAWABLE subtype",
+ FALSE, FALSE, TRUE,
+ NULL,
+ PIKA_PARAM_READWRITE));
+ pika_procedure_add_argument (procedure,
+ pika_param_spec_drawable ("initial-drawable",
+ "initial drawable",
+ "The drawable to set as the initial choice",
+ FALSE,
+ PIKA_PARAM_READWRITE | PIKA_PARAM_NO_VALIDATE));
+ pika_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,
+ PIKA_PARAM_READWRITE));
+ pika_pdb_register_procedure (pdb, procedure);
+ g_object_unref (procedure);
+
+ /*
+ * pika-drawables-close-popup
+ */
+ procedure = pika_procedure_new (drawables_close_popup_invoker);
+ pika_object_set_static_name (PIKA_OBJECT (procedure),
+ "pika-drawables-close-popup");
+ pika_procedure_set_static_help (procedure,
+ "Close the drawable selection dialog.",
+ "Closes an open drawable selection dialog.",
+ NULL);
+ pika_procedure_set_static_attribution (procedure,
+ "Jehan",
+ "Jehan",
+ "2023");
+ pika_procedure_add_argument (procedure,
+ pika_param_spec_string ("callback",
+ "callback",
+ "The name of the callback registered for this pop-up",
+ FALSE, FALSE, TRUE,
+ NULL,
+ PIKA_PARAM_READWRITE));
+ pika_pdb_register_procedure (pdb, procedure);
+ g_object_unref (procedure);
+
+ /*
+ * pika-drawables-set-popup
+ */
+ procedure = pika_procedure_new (drawables_set_popup_invoker);
+ pika_object_set_static_name (PIKA_OBJECT (procedure),
+ "pika-drawables-set-popup");
+ pika_procedure_set_static_help (procedure,
+ "Sets the selected drawable in a drawable selection dialog.",
+ "Sets the selected drawable in a drawable selection dialog.",
+ NULL);
+ pika_procedure_set_static_attribution (procedure,
+ "Jehan",
+ "Jehan",
+ "2023");
+ pika_procedure_add_argument (procedure,
+ pika_param_spec_string ("callback",
+ "callback",
+ "The name of the callback registered for this pop-up",
+ FALSE, FALSE, TRUE,
+ NULL,
+ PIKA_PARAM_READWRITE));
+ pika_procedure_add_argument (procedure,
+ pika_param_spec_drawable ("drawable",
+ "drawable",
+ "The drawable to set as selected",
+ FALSE,
+ PIKA_PARAM_READWRITE | PIKA_PARAM_NO_VALIDATE));
+ pika_pdb_register_procedure (pdb, procedure);
+ g_object_unref (procedure);
+}
diff --git a/app/widgets/pikapickablechooser.c b/app/widgets/pikapickablechooser.c
new file mode 100644
index 0000000..5138044
--- /dev/null
+++ b/app/widgets/pikapickablechooser.c
@@ -0,0 +1,589 @@
+/* PIKA - Photo and Image Kooker Application
+ * a rebranding of The GNU Image Manipulation Program (created with heckimp)
+ * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
+ *
+ * Original copyright, applying to most contents (license remains unchanged):
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * pikapickablechooser.c
+ * Copyright (C) 2023 Jehan
+ *
+ * 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 "libpikabase/pikabase.h"
+#include "libpikawidgets/pikawidgets.h"
+
+#include "widgets-types.h"
+
+#include "core/pika.h"
+#include "core/pikachannel.h"
+#include "core/pikacontext.h"
+#include "core/pikaimage.h"
+#include "core/pikalayer.h"
+#include "core/pikapickable.h"
+#include "core/pikaviewable.h"
+
+#include "pikacontainertreeview.h"
+#include "pikacontainerview.h"
+#include "pikapickablechooser.h"
+#include "pikaviewrenderer.h"
+
+#include "pika-intl.h"
+
+
+enum
+{
+ ACTIVATE,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_CONTEXT,
+ PROP_PICKABLE_TYPE,
+ PROP_PICKABLE,
+ PROP_VIEW_SIZE,
+ PROP_VIEW_BORDER_WIDTH
+};
+
+struct _PikaPickableChooserPrivate
+{
+ GType pickable_type;
+ PikaPickable *pickable;
+ PikaContext *context;
+
+ gint view_size;
+ gint view_border_width;
+
+ GtkWidget *image_view;
+ GtkWidget *layer_view;
+ GtkWidget *channel_view;
+ GtkWidget *layer_label;
+};
+
+
+static void pika_pickable_chooser_constructed (GObject *object);
+static void pika_pickable_chooser_finalize (GObject *object);
+static void pika_pickable_chooser_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void pika_pickable_chooser_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void pika_pickable_chooser_image_changed (PikaContext *context,
+ PikaImage *image,
+ PikaPickableChooser *chooser);
+static void pika_pickable_chooser_item_activate (PikaContainerView *view,
+ PikaPickable *pickable,
+ gpointer unused,
+ PikaPickableChooser *chooser);
+static void pika_pickable_chooser_items_selected (PikaContainerView *view,
+ GList *items,
+ GList *paths,
+ PikaPickableChooser *chooser);
+
+
+G_DEFINE_TYPE_WITH_PRIVATE (PikaPickableChooser, pika_pickable_chooser, GTK_TYPE_FRAME)
+
+#define parent_class pika_pickable_chooser_parent_class
+
+static guint signals[LAST_SIGNAL];
+
+static void
+pika_pickable_chooser_class_init (PikaPickableChooserClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = pika_pickable_chooser_constructed;
+ object_class->finalize = pika_pickable_chooser_finalize;
+ object_class->get_property = pika_pickable_chooser_get_property;
+ object_class->set_property = pika_pickable_chooser_set_property;
+
+ /**
+ * PikaPickableChooser::activate:
+ * @chooser:
+ *
+ * Emitted when a pickable is activated, which is mostly forwarding when
+ * "activate-item" 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
+ * pickables.
+ */
+ signals[ACTIVATE] =
+ g_signal_new ("activate",
+ G_OBJECT_CLASS_TYPE (object_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (PikaPickableChooserClass, activate),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ PIKA_TYPE_OBJECT);
+
+ g_object_class_install_property (object_class, PROP_CONTEXT,
+ g_param_spec_object ("context",
+ NULL, NULL,
+ PIKA_TYPE_CONTEXT,
+ PIKA_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_PICKABLE_TYPE,
+ g_param_spec_gtype ("pickable-type",
+ NULL, NULL,
+ PIKA_TYPE_PICKABLE,
+ PIKA_PARAM_WRITABLE |
+ G_PARAM_CONSTRUCT_ONLY));
+
+ g_object_class_install_property (object_class, PROP_PICKABLE,
+ g_param_spec_object ("pickable",
+ NULL, NULL,
+ PIKA_TYPE_PICKABLE,
+ PIKA_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, PIKA_VIEWABLE_MAX_PREVIEW_SIZE,
+ PIKA_VIEW_SIZE_MEDIUM,
+ PIKA_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,
+ PIKA_VIEW_MAX_BORDER_WIDTH,
+ 1,
+ PIKA_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT));
+}
+
+static void
+pika_pickable_chooser_init (PikaPickableChooser *chooser)
+{
+ chooser->priv = pika_pickable_chooser_get_instance_private (chooser);
+
+ chooser->priv->view_size = PIKA_VIEW_SIZE_SMALL;
+ chooser->priv->view_border_width = 1;
+
+ chooser->priv->layer_view = NULL;
+ chooser->priv->channel_view = NULL;
+}
+
+static void
+pika_pickable_chooser_constructed (GObject *object)
+{
+ PikaPickableChooser *chooser = PIKA_PICKABLE_CHOOSER (object);
+ GtkWidget *hbox;
+ GtkWidget *vbox;
+ GtkWidget *label;
+ GtkWidget *notebook;
+ PikaImage *image;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ pika_assert (PIKA_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 =
+ pika_container_tree_view_new (chooser->priv->context->pika->images,
+ chooser->priv->context,
+ chooser->priv->view_size,
+ chooser->priv->view_border_width);
+ pika_container_box_set_size_request (PIKA_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_show (chooser->priv->image_view);
+
+ g_signal_connect_object (chooser->priv->image_view, "activate-item",
+ G_CALLBACK (pika_pickable_chooser_item_activate),
+ G_OBJECT (chooser), 0);
+ g_signal_connect_object (chooser->priv->image_view, "select-items",
+ G_CALLBACK (pika_pickable_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_show (vbox);
+
+ 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_show (label);
+
+ notebook = gtk_notebook_new ();
+ gtk_box_pack_start (GTK_BOX (vbox), notebook, TRUE, TRUE, 0);
+ gtk_widget_show (notebook);
+
+ if (g_type_is_a (PIKA_TYPE_LAYER, chooser->priv->pickable_type))
+ {
+ chooser->priv->layer_view =
+ pika_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 (PIKA_CONTAINER_TREE_VIEW (chooser->priv->layer_view)->view),
+ TRUE);
+ pika_container_box_set_size_request (PIKA_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_show (chooser->priv->layer_view);
+
+ g_signal_connect_object (chooser->priv->layer_view, "activate-item",
+ G_CALLBACK (pika_pickable_chooser_item_activate),
+ G_OBJECT (chooser), 0);
+ g_signal_connect_object (chooser->priv->layer_view, "select-items",
+ G_CALLBACK (pika_pickable_chooser_items_selected),
+ G_OBJECT (chooser), 0);
+ }
+
+ if (g_type_is_a (PIKA_TYPE_CHANNEL, chooser->priv->pickable_type))
+ {
+ chooser->priv->channel_view =
+ pika_container_tree_view_new (NULL,
+ chooser->priv->context,
+ chooser->priv->view_size,
+ chooser->priv->view_border_width);
+ pika_container_box_set_size_request (PIKA_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_show (chooser->priv->channel_view);
+
+ g_signal_connect_object (chooser->priv->channel_view, "activate-item",
+ G_CALLBACK (pika_pickable_chooser_item_activate),
+ G_OBJECT (chooser), 0);
+ g_signal_connect_object (chooser->priv->channel_view, "select-items",
+ G_CALLBACK (pika_pickable_chooser_items_selected),
+ G_OBJECT (chooser), 0);
+ }
+
+ g_signal_connect_object (chooser->priv->context, "image-changed",
+ G_CALLBACK (pika_pickable_chooser_image_changed),
+ G_OBJECT (chooser), 0);
+
+ image = pika_context_get_image (chooser->priv->context);
+ pika_pickable_chooser_image_changed (chooser->priv->context, image, chooser);
+}
+
+static void
+pika_pickable_chooser_finalize (GObject *object)
+{
+ PikaPickableChooser *chooser = PIKA_PICKABLE_CHOOSER (object);
+
+ g_clear_object (&chooser->priv->pickable);
+ g_clear_object (&chooser->priv->context);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+pika_pickable_chooser_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PikaPickableChooser *chooser = PIKA_PICKABLE_CHOOSER (object);
+
+ switch (property_id)
+ {
+ case PROP_CONTEXT:
+ if (chooser->priv->context)
+ g_object_unref (chooser->priv->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_PICKABLE_TYPE:
+ g_return_if_fail (g_value_get_gtype (value) == PIKA_TYPE_LAYER ||
+ g_value_get_gtype (value) == PIKA_TYPE_CHANNEL ||
+ g_value_get_gtype (value) == PIKA_TYPE_DRAWABLE ||
+ g_value_get_gtype (value) == PIKA_TYPE_IMAGE ||
+ g_value_get_gtype (value) == PIKA_TYPE_PICKABLE);
+ chooser->priv->pickable_type = g_value_get_gtype (value);
+ break;
+
+ case PROP_PICKABLE:
+ 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->pickable_type));
+ pika_pickable_chooser_set_pickable (chooser, g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+pika_pickable_chooser_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PikaPickableChooser *chooser = PIKA_PICKABLE_CHOOSER (object);
+
+ switch (property_id)
+ {
+ case PROP_CONTEXT:
+ g_value_set_object (value, chooser->priv->context);
+ break;
+
+ case PROP_PICKABLE_TYPE:
+ g_value_set_gtype (value, chooser->priv->pickable_type);
+ break;
+
+ case PROP_PICKABLE:
+ g_value_set_object (value, chooser->priv->pickable);
+ 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 *
+pika_pickable_chooser_new (PikaContext *context,
+ GType pickable_type,
+ gint view_size,
+ gint view_border_width)
+{
+ g_return_val_if_fail (PIKA_IS_CONTEXT (context), NULL);
+ g_return_val_if_fail (view_size > 0 &&
+ view_size <= PIKA_VIEWABLE_MAX_POPUP_SIZE, NULL);
+ g_return_val_if_fail (view_border_width >= 0 &&
+ view_border_width <= PIKA_VIEW_MAX_BORDER_WIDTH,
+ NULL);
+
+ return g_object_new (PIKA_TYPE_PICKABLE_CHOOSER,
+ "context", context,
+ "pickable-type", pickable_type,
+ "view-size", view_size,
+ "view-border-width", view_border_width,
+ NULL);
+}
+
+PikaPickable *
+pika_pickable_chooser_get_pickable (PikaPickableChooser *chooser)
+{
+ g_return_val_if_fail (PIKA_IS_PICKABLE_CHOOSER (chooser), NULL);
+
+ return chooser->priv->pickable;
+}
+
+void
+pika_pickable_chooser_set_pickable (PikaPickableChooser *chooser,
+ PikaPickable *pickable)
+{
+ if (! gtk_widget_in_destruction (GTK_WIDGET (chooser)))
+ {
+ g_signal_handlers_disconnect_by_func (chooser->priv->image_view,
+ G_CALLBACK (pika_pickable_chooser_items_selected),
+ chooser);
+ if (chooser->priv->layer_view != NULL)
+ g_signal_handlers_disconnect_by_func (chooser->priv->layer_view,
+ G_CALLBACK (pika_pickable_chooser_items_selected),
+ chooser);
+ else
+ g_return_if_fail (! PIKA_IS_LAYER (pickable));
+
+ if (chooser->priv->channel_view != NULL)
+ g_signal_handlers_disconnect_by_func (chooser->priv->channel_view,
+ G_CALLBACK (pika_pickable_chooser_items_selected),
+ chooser);
+ else
+ g_return_if_fail (! PIKA_IS_CHANNEL (pickable));
+
+ if (PIKA_IS_IMAGE (pickable))
+ {
+ pika_container_view_select_item (PIKA_CONTAINER_VIEW (chooser->priv->image_view),
+ PIKA_VIEWABLE (pickable));
+ pika_context_set_image (chooser->priv->context, PIKA_IMAGE (pickable));
+ }
+ else if (PIKA_IS_LAYER (pickable))
+ {
+ pika_context_set_image (chooser->priv->context, pika_item_get_image (PIKA_ITEM (pickable)));
+ pika_container_view_select_item (PIKA_CONTAINER_VIEW (chooser->priv->layer_view),
+ PIKA_VIEWABLE (pickable));
+ }
+ else if (PIKA_IS_CHANNEL (pickable))
+ {
+ pika_context_set_image (chooser->priv->context, pika_item_get_image (PIKA_ITEM (pickable)));
+ pika_container_view_select_item (PIKA_CONTAINER_VIEW (chooser->priv->channel_view),
+ PIKA_VIEWABLE (pickable));
+ }
+ else
+ {
+ g_return_if_fail (pickable == NULL);
+ pika_container_view_select_item (PIKA_CONTAINER_VIEW (chooser->priv->image_view), NULL);
+ pika_context_set_image (chooser->priv->context, NULL);
+ }
+ g_signal_connect_object (chooser->priv->image_view, "select-items",
+ G_CALLBACK (pika_pickable_chooser_items_selected),
+ G_OBJECT (chooser), 0);
+ if (chooser->priv->layer_view != NULL)
+ g_signal_connect_object (chooser->priv->layer_view, "select-items",
+ G_CALLBACK (pika_pickable_chooser_items_selected),
+ G_OBJECT (chooser), 0);
+ if (chooser->priv->channel_view != NULL)
+ g_signal_connect_object (chooser->priv->channel_view, "select-items",
+ G_CALLBACK (pika_pickable_chooser_items_selected),
+ G_OBJECT (chooser), 0);
+ }
+
+ if (pickable != chooser->priv->pickable)
+ {
+ g_clear_object (&chooser->priv->pickable);
+ chooser->priv->pickable = (pickable != NULL ? g_object_ref (pickable) : NULL);
+ g_object_notify (G_OBJECT (chooser), "pickable");
+ }
+}
+
+
+/* private functions */
+
+static void
+pika_pickable_chooser_image_changed (PikaContext *context,
+ PikaImage *image,
+ PikaPickableChooser *chooser)
+{
+ PikaContainer *layers = NULL;
+ PikaContainer *channels = NULL;
+
+ if (image)
+ {
+ gchar *desc;
+
+ layers = pika_image_get_layers (image);
+ channels = pika_image_get_channels (image);
+
+ desc = pika_viewable_get_description (PIKA_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 (pika_pickable_chooser_items_selected),
+ chooser);
+ if (chooser->priv->layer_view != NULL)
+ {
+ g_signal_handlers_disconnect_by_func (chooser->priv->layer_view,
+ G_CALLBACK (pika_pickable_chooser_items_selected),
+ chooser);
+ pika_container_view_set_container (PIKA_CONTAINER_VIEW (chooser->priv->layer_view),
+ layers);
+ g_signal_connect_object (chooser->priv->layer_view, "select-items",
+ G_CALLBACK (pika_pickable_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 (pika_pickable_chooser_items_selected),
+ chooser);
+ pika_container_view_set_container (PIKA_CONTAINER_VIEW (chooser->priv->channel_view),
+ channels);
+ g_signal_connect_object (chooser->priv->channel_view, "select-items",
+ G_CALLBACK (pika_pickable_chooser_items_selected),
+ G_OBJECT (chooser), 0);
+ }
+ g_signal_connect_object (chooser->priv->image_view, "select-items",
+ G_CALLBACK (pika_pickable_chooser_items_selected),
+ G_OBJECT (chooser), 0);
+}
+
+static void
+pika_pickable_chooser_item_activate (PikaContainerView *view,
+ PikaPickable *pickable,
+ gpointer unused,
+ PikaPickableChooser *chooser)
+{
+ g_signal_emit (chooser, signals[ACTIVATE], 0, pickable);
+}
+
+static void
+pika_pickable_chooser_items_selected (PikaContainerView *view,
+ GList *items,
+ GList *paths,
+ PikaPickableChooser *chooser)
+{
+ PikaPickable *pickable = NULL;
+
+ g_return_if_fail (g_list_length (items) <= 1);
+
+ if (items != NULL)
+ pickable = items->data;
+
+ pika_pickable_chooser_set_pickable (chooser, pickable);
+}
diff --git a/app/widgets/pikapickablechooser.h b/app/widgets/pikapickablechooser.h
new file mode 100644
index 0000000..89dadbf
--- /dev/null
+++ b/app/widgets/pikapickablechooser.h
@@ -0,0 +1,70 @@
+/* PIKA - Photo and Image Kooker Application
+ * a rebranding of The GNU Image Manipulation Program (created with heckimp)
+ * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
+ *
+ * Original copyright, applying to most contents (license remains unchanged):
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * pikapickablechooser.h
+ * Copyright (C) 2023 Jehan
+ *
+ * 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 .
+ */
+
+#ifndef __PIKA_PICKABLE_CHOOSER_H__
+#define __PIKA_PICKABLE_CHOOSER_H__
+
+
+#define PIKA_TYPE_PICKABLE_CHOOSER (pika_pickable_chooser_get_type ())
+#define PIKA_PICKABLE_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIKA_TYPE_PICKABLE_CHOOSER, PikaPickableChooser))
+#define PIKA_PICKABLE_CHOOSER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PIKA_TYPE_PICKABLE_CHOOSER, PikaPickableChooserClass))
+#define PIKA_IS_PICKABLE_CHOOSER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PIKA_TYPE_PICKABLE_CHOOSER))
+#define PIKA_IS_PICKABLE_CHOOSER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PIKA_TYPE_PICKABLE_CHOOSER))
+#define PIKA_PICKABLE_CHOOSER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PIKA_TYPE_PICKABLE_CHOOSER, PikaPickableChooserClass))
+
+
+typedef struct _PikaPickableChooserPrivate PikaPickableChooserPrivate;
+typedef struct _PikaPickableChooserClass PikaPickableChooserClass;
+
+struct _PikaPickableChooser
+{
+ GtkFrame parent_instance;
+
+ PikaPickableChooserPrivate *priv;
+};
+
+struct _PikaPickableChooserClass
+{
+ GtkFrameClass parent_instance;
+
+ /* Signals. */
+
+ void (* activate) (PikaPickableChooser *view,
+ PikaPickable *pickable);
+};
+
+
+GType pika_pickable_chooser_get_type (void) G_GNUC_CONST;
+
+GtkWidget * pika_pickable_chooser_new (PikaContext *context,
+ GType pickable_type,
+ gint view_size,
+ gint view_border_width);
+
+PikaPickable * pika_pickable_chooser_get_pickable (PikaPickableChooser *chooser);
+void pika_pickable_chooser_set_pickable (PikaPickableChooser *chooser,
+ PikaPickable *pickable);
+
+
+#endif /* __PIKA_PICKABLE_CHOOSER_H__ */
diff --git a/app/widgets/pikapickableselect.c b/app/widgets/pikapickableselect.c
new file mode 100644
index 0000000..1fa3229
--- /dev/null
+++ b/app/widgets/pikapickableselect.c
@@ -0,0 +1,164 @@
+/* PIKA - Photo and Image Kooker Application
+ * a rebranding of The GNU Image Manipulation Program (created with heckimp)
+ * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
+ *
+ * Original copyright, applying to most contents (license remains unchanged):
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * pikapickableselect.c
+ * Copyright (C) 2023 Jehan
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "config.h"
+
+#include
+#include
+
+#include "libpikabase/pikabase.h"
+#include "libpikawidgets/pikawidgets.h"
+
+#include "widgets-types.h"
+
+#include "gegl/pika-babl-compat.h"
+
+#include "core/pika.h"
+#include "core/pikacontext.h"
+#include "core/pikadrawable.h"
+#include "core/pikapickable.h"
+#include "core/pikaparamspecs.h"
+#include "core/pikatempbuf.h"
+
+#include "pdb/pikapdb.h"
+
+#include "pikapickablechooser.h"
+#include "pikapickableselect.h"
+#include "pikacontainerbox.h"
+
+#include "pika-intl.h"
+
+
+static void pika_pickable_select_constructed (GObject *object);
+
+static PikaValueArray * pika_pickable_select_run_callback (PikaPdbDialog *dialog,
+ PikaObject *object,
+ gboolean closing,
+ GError **error);
+static PikaObject * pika_pickable_select_get_object (PikaPdbDialog *dialog);
+static void pika_pickable_select_set_object (PikaPdbDialog *dialog,
+ PikaObject *object);
+
+static void pika_pickable_select_activate (PikaPickableSelect *select);
+static void pika_pickable_select_notify_pickable (PikaPickableSelect *select);
+
+
+G_DEFINE_TYPE (PikaPickableSelect, pika_pickable_select, PIKA_TYPE_PDB_DIALOG)
+
+#define parent_class pika_pickable_select_parent_class
+
+
+static void
+pika_pickable_select_class_init (PikaPickableSelectClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ PikaPdbDialogClass *pdb_class = PIKA_PDB_DIALOG_CLASS (klass);
+
+ object_class->constructed = pika_pickable_select_constructed;
+
+ pdb_class->run_callback = pika_pickable_select_run_callback;
+ pdb_class->get_object = pika_pickable_select_get_object;
+ pdb_class->set_object = pika_pickable_select_set_object;
+}
+
+static void
+pika_pickable_select_init (PikaPickableSelect *select)
+{
+}
+
+static void
+pika_pickable_select_constructed (GObject *object)
+{
+ PikaPdbDialog *dialog = PIKA_PDB_DIALOG (object);
+ PikaPickableSelect *select = PIKA_PICKABLE_SELECT (object);
+ GtkWidget *content_area;
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
+
+ select->chooser = pika_pickable_chooser_new (dialog->context, dialog->select_type,
+ PIKA_VIEW_SIZE_LARGE, 1);
+ pika_pickable_chooser_set_pickable (PIKA_PICKABLE_CHOOSER (select->chooser),
+ PIKA_PICKABLE (dialog->initial_object));
+ g_signal_connect_swapped (select->chooser, "notify::pickable",
+ G_CALLBACK (pika_pickable_select_notify_pickable),
+ select);
+ g_signal_connect_swapped (select->chooser, "activate",
+ G_CALLBACK (pika_pickable_select_activate),
+ select);
+ gtk_box_pack_start (GTK_BOX (content_area), select->chooser, TRUE, TRUE, 0);
+ gtk_widget_show (select->chooser);
+}
+
+static PikaValueArray *
+pika_pickable_select_run_callback (PikaPdbDialog *dialog,
+ PikaObject *object,
+ gboolean closing,
+ GError **error)
+{
+ PikaPickable *pickable = PIKA_PICKABLE (object);
+ PikaValueArray *return_vals;
+
+ return_vals =
+ pika_pdb_execute_procedure_by_name (dialog->pdb,
+ dialog->caller_context,
+ NULL, error,
+ dialog->callback_name,
+ PIKA_TYPE_DRAWABLE, pickable,
+ G_TYPE_BOOLEAN, closing,
+ G_TYPE_NONE);
+
+ return return_vals;
+}
+
+static PikaObject *
+pika_pickable_select_get_object (PikaPdbDialog *dialog)
+{
+ PikaPickableSelect *select = PIKA_PICKABLE_SELECT (dialog);
+
+ return (PikaObject *) pika_pickable_chooser_get_pickable (PIKA_PICKABLE_CHOOSER (select->chooser));
+}
+
+static void
+pika_pickable_select_set_object (PikaPdbDialog *dialog,
+ PikaObject *object)
+{
+ PikaPickableSelect *select = PIKA_PICKABLE_SELECT (dialog);
+
+ pika_pickable_chooser_set_pickable (PIKA_PICKABLE_CHOOSER (select->chooser), PIKA_PICKABLE (object));
+}
+
+static void
+pika_pickable_select_activate (PikaPickableSelect *select)
+{
+ pika_pdb_dialog_run_callback ((PikaPdbDialog **) &select, TRUE);
+ gtk_widget_destroy (GTK_WIDGET (select));
+}
+
+static void
+pika_pickable_select_notify_pickable (PikaPickableSelect *select)
+{
+ pika_pdb_dialog_run_callback ((PikaPdbDialog **) &select, FALSE);
+}
diff --git a/app/widgets/pikapickableselect.h b/app/widgets/pikapickableselect.h
new file mode 100644
index 0000000..ba94013
--- /dev/null
+++ b/app/widgets/pikapickableselect.h
@@ -0,0 +1,61 @@
+/* PIKA - Photo and Image Kooker Application
+ * a rebranding of The GNU Image Manipulation Program (created with heckimp)
+ * A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
+ *
+ * Original copyright, applying to most contents (license remains unchanged):
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * pikapickableselect.h
+ * Copyright (C) 2023 Jehan
+ *
+ * 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 .
+ */
+
+#ifndef __PIKA_PICKABLE_SELECT_H__
+#define __PIKA_PICKABLE_SELECT_H__
+
+#include "pikapdbdialog.h"
+
+G_BEGIN_DECLS
+
+
+#define PIKA_TYPE_PICKABLE_SELECT (pika_pickable_select_get_type ())
+#define PIKA_PICKABLE_SELECT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIKA_TYPE_PICKABLE_SELECT, PikaPickableSelect))
+#define PIKA_PICKABLE_SELECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PIKA_TYPE_PICKABLE_SELECT, PikaPickableSelectClass))
+#define PIKA_IS_PICKABLE_SELECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PIKA_TYPE_PICKABLE_SELECT))
+#define PIKA_IS_PICKABLE_SELECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PIKA_TYPE_PICKABLE_SELECT))
+#define PIKA_PICKABLE_SELECT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PIKA_TYPE_PICKABLE_SELECT, PikaPickableSelectClass))
+
+
+typedef struct _PikaPickableSelectClass PikaPickableSelectClass;
+
+struct _PikaPickableSelect
+{
+ PikaPdbDialog parent_instance;
+
+ GtkWidget *chooser;
+};
+
+struct _PikaPickableSelectClass
+{
+ PikaPdbDialogClass parent_class;
+};
+
+
+GType pika_pickable_select_get_type (void) G_GNUC_CONST;
+
+
+G_END_DECLS
+
+#endif /* __PIKA_PICKABLE_SELECT_H__ */
diff --git a/build/windows/patches/0001-clang-rc-files-fix.patch b/build/windows/patches/0001-clang-rc-files-fix.patch
new file mode 100644
index 0000000..7ba71ca
--- /dev/null
+++ b/build/windows/patches/0001-clang-rc-files-fix.patch
@@ -0,0 +1,24 @@
+diff -urN pika-2.10.30-orig/build/windows/pika-plug-ins.rc.in pika-2.10.30/build/windows/pika-plug-ins.rc.in
+--- pika-2.10.30-orig/build/windows/pika-plug-ins.rc.in 2021-12-20 04:48:33.000000000 +0800
++++ pika-2.10.30/build/windows/pika-plug-ins.rc.in 2022-01-05 20:54:00.814712400 +0800
+@@ -63,5 +63,5 @@
+ END
+
+ #include "winuser.h"
+-1 ICON QUOTE(TOP_SRCDIR) "/build/windows/plug-ins.ico"
+-CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST QUOTE(TOP_SRCDIR) "/build/windows/pika.manifest"
++1 ICON "./build/windows/plug-ins.ico"
++CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "./build/windows/pika.manifest"
+diff -urN pika-2.10.30-orig/build/windows/pika.rc.in pika-2.10.30/build/windows/pika.rc.in
+--- pika-2.10.30-orig/build/windows/pika.rc.in 2021-12-20 04:48:33.000000000 +0800
++++ pika-2.10.30/build/windows/pika.rc.in 2022-01-05 20:52:37.741960900 +0800
+@@ -64,6 +64,6 @@
+ END
+
+ #include "winuser.h"
+-1 ICON QUOTE(TOP_SRCDIR) "/build/windows/wilber.ico"
+-2 ICON QUOTE(TOP_SRCDIR) "/build/windows/fileicon.ico"
+-CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST QUOTE(TOP_SRCDIR) "/build/windows/pika.manifest"
++1 ICON "./build/windows/wilber.ico"
++2 ICON "./build/windows/fileicon.ico"
++CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "./build/windows/pika.manifest"
diff --git a/build/windows/patches/0004-clang-windres.patch b/build/windows/patches/0004-clang-windres.patch
new file mode 100644
index 0000000..271ac83
--- /dev/null
+++ b/build/windows/patches/0004-clang-windres.patch
@@ -0,0 +1,600 @@
+diff -bur pika-2.99.12-c/app/meson.build pika-2.99.12/app/meson.build
+--- pika-2.99.12-c/app/meson.build 2022-08-21 13:21:38.000000000 -0600
++++ pika-2.99.12/app/meson.build 2022-08-26 11:05:18.272156800 -0600
+@@ -151,9 +151,9 @@
+ console_rc_file = windows.compile_resources(
+ pika_app_console_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(console_rc_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(console_rc_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(console_rc_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(console_rc_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+@@ -169,9 +169,9 @@
+ gui_rc_file = windows.compile_resources(
+ pika_app_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(gui_rc_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(gui_rc_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(gui_rc_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(gui_rc_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+diff -bur pika-2.99.12-c/extensions/goat-exercises/meson.build pika-2.99.12/extensions/goat-exercises/meson.build
+--- pika-2.99.12-c/extensions/goat-exercises/meson.build 2022-08-21 13:21:38.000000000 -0600
++++ pika-2.99.12/extensions/goat-exercises/meson.build 2022-08-26 11:05:21.367326100 -0600
+@@ -11,9 +11,9 @@
+ plugin_sources += windows.compile_resources(
+ pika_plugins_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(plug_in_name + '-c.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(plug_in_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(plug_in_name + '-c.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(plug_in_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+diff -bur pika-2.99.12-c/plug-ins/common/meson.build pika-2.99.12/plug-ins/common/meson.build
+--- pika-2.99.12-c/plug-ins/common/meson.build 2022-08-26 10:58:35.055807300 -0600
++++ pika-2.99.12/plug-ins/common/meson.build 2022-08-26 11:05:18.323293100 -0600
+@@ -178,9 +178,9 @@
+ plugin_sources += windows.compile_resources(
+ plugin_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(plugin_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(plugin_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(plugin_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(plugin_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+diff -bur pika-2.99.12-c/plug-ins/file-bmp/meson.build pika-2.99.12/plug-ins/file-bmp/meson.build
+--- pika-2.99.12-c/plug-ins/file-bmp/meson.build 2022-08-21 13:21:38.000000000 -0600
++++ pika-2.99.12/plug-ins/file-bmp/meson.build 2022-08-26 11:05:18.326295200 -0600
+@@ -10,9 +10,9 @@
+ plugin_sources += windows.compile_resources(
+ pika_plugins_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(plugin_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(plugin_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(plugin_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(plugin_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+diff -bur pika-2.99.12-c/plug-ins/file-dds/meson.build pika-2.99.12/plug-ins/file-dds/meson.build
+--- pika-2.99.12-c/plug-ins/file-dds/meson.build 2022-08-21 13:21:38.000000000 -0600
++++ pika-2.99.12/plug-ins/file-dds/meson.build 2022-08-26 11:05:18.328294700 -0600
+@@ -14,9 +14,9 @@
+ plugin_sources += windows.compile_resources(
+ pika_plugins_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(plugin_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(plugin_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(plugin_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(plugin_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+diff -bur pika-2.99.12-c/plug-ins/file-exr/meson.build pika-2.99.12/plug-ins/file-exr/meson.build
+--- pika-2.99.12-c/plug-ins/file-exr/meson.build 2022-08-21 13:21:38.000000000 -0600
++++ pika-2.99.12/plug-ins/file-exr/meson.build 2022-08-26 11:05:18.330294400 -0600
+@@ -11,9 +11,9 @@
+ plugin_sources += windows.compile_resources(
+ pika_plugins_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(plugin_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(plugin_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(plugin_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(plugin_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+diff -bur pika-2.99.12-c/plug-ins/file-faxg3/meson.build pika-2.99.12/plug-ins/file-faxg3/meson.build
+--- pika-2.99.12-c/plug-ins/file-faxg3/meson.build 2022-08-21 13:21:38.000000000 -0600
++++ pika-2.99.12/plug-ins/file-faxg3/meson.build 2022-08-26 11:05:18.332294000 -0600
+@@ -9,9 +9,9 @@
+ plugin_sources += windows.compile_resources(
+ pika_plugins_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(plugin_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(plugin_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(plugin_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(plugin_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+--- pika-2.99.16/plug-ins/file-fits/meson.build.orig 2023-07-06 07:48:46.078752900 +0200
++++ pika-2.99.16/plug-ins/file-fits/meson.build 2023-07-06 07:49:41.120861200 +0200
+@@ -9,9 +9,9 @@
+ plugin_sources += windows.compile_resources(
+ pika_plugins_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(plugin_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(plugin_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(plugin_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(plugin_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+diff -bur pika-2.99.12-c/plug-ins/file-fli/meson.build pika-2.99.12/plug-ins/file-fli/meson.build
+--- pika-2.99.12-c/plug-ins/file-fli/meson.build 2022-08-21 13:21:38.000000000 -0600
++++ pika-2.99.12/plug-ins/file-fli/meson.build 2022-08-26 11:05:18.336295400 -0600
+@@ -9,9 +9,9 @@
+ plugin_sources += windows.compile_resources(
+ pika_plugins_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(plugin_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(plugin_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(plugin_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(plugin_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+diff -bur pika-2.99.12-c/plug-ins/file-ico/meson.build pika-2.99.12/plug-ins/file-ico/meson.build
+--- pika-2.99.12-c/plug-ins/file-ico/meson.build 2022-08-21 13:21:38.000000000 -0600
++++ pika-2.99.12/plug-ins/file-ico/meson.build 2022-08-26 11:05:18.338293600 -0600
+@@ -11,9 +11,9 @@
+ plugin_sources += windows.compile_resources(
+ pika_plugins_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(plugin_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(plugin_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(plugin_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(plugin_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+diff -bur pika-2.99.12-c/plug-ins/file-jpeg/meson.build pika-2.99.12/plug-ins/file-jpeg/meson.build
+--- pika-2.99.12-c/plug-ins/file-jpeg/meson.build 2022-08-21 13:21:38.000000000 -0600
++++ pika-2.99.12/plug-ins/file-jpeg/meson.build 2022-08-26 11:05:18.340292800 -0600
+@@ -13,9 +13,9 @@
+ plugin_sources += windows.compile_resources(
+ pika_plugins_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(plugin_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(plugin_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(plugin_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(plugin_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+diff -bur pika-2.99.12-c/plug-ins/file-psd/meson.build pika-2.99.12/plug-ins/file-psd/meson.build
+--- pika-2.99.12-c/plug-ins/file-psd/meson.build 2022-08-21 13:21:38.000000000 -0600
++++ pika-2.99.12/plug-ins/file-psd/meson.build 2022-08-26 11:05:18.342293000 -0600
+@@ -14,9 +14,9 @@
+ plugin_sources += windows.compile_resources(
+ pika_plugins_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(plugin_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(plugin_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(plugin_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(plugin_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+diff -bur pika-2.99.12-c/plug-ins/file-raw/meson.build pika-2.99.12/plug-ins/file-raw/meson.build
+--- pika-2.99.12-c/plug-ins/file-raw/meson.build 2022-08-21 13:21:38.000000000 -0600
++++ pika-2.99.12/plug-ins/file-raw/meson.build 2022-08-26 11:05:18.344293200 -0600
+@@ -21,9 +21,9 @@
+ plugin_sources += windows.compile_resources(
+ plugin_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(plugin_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(plugin_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(plugin_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(plugin_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+diff -bur pika-2.99.12-c/plug-ins/file-sgi/meson.build pika-2.99.12/plug-ins/file-sgi/meson.build
+--- pika-2.99.12-c/plug-ins/file-sgi/meson.build 2022-08-21 13:21:38.000000000 -0600
++++ pika-2.99.12/plug-ins/file-sgi/meson.build 2022-08-26 11:05:18.346292800 -0600
+@@ -9,9 +9,9 @@
+ plugin_sources += windows.compile_resources(
+ pika_plugins_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(plugin_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(plugin_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(plugin_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(plugin_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+diff -bur pika-2.99.12-c/plug-ins/file-tiff/meson.build pika-2.99.12/plug-ins/file-tiff/meson.build
+--- pika-2.99.12-c/plug-ins/file-tiff/meson.build 2022-08-21 13:21:38.000000000 -0600
++++ pika-2.99.12/plug-ins/file-tiff/meson.build 2022-08-26 11:05:18.348293500 -0600
+@@ -11,9 +11,9 @@
+ plugin_sources += windows.compile_resources(
+ pika_plugins_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(plugin_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(plugin_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(plugin_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(plugin_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+diff -bur pika-2.99.12-c/plug-ins/file-webp/meson.build pika-2.99.12/plug-ins/file-webp/meson.build
+--- pika-2.99.12-c/plug-ins/file-webp/meson.build 2022-08-21 13:21:38.000000000 -0600
++++ pika-2.99.12/plug-ins/file-webp/meson.build 2022-08-26 11:05:18.350294300 -0600
+@@ -13,9 +13,9 @@
+ plugin_sources += windows.compile_resources(
+ pika_plugins_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(plugin_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(plugin_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(plugin_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(plugin_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+diff -bur pika-2.99.12-c/plug-ins/flame/meson.build pika-2.99.12/plug-ins/flame/meson.build
+--- pika-2.99.12-c/plug-ins/flame/meson.build 2022-08-21 13:21:38.000000000 -0600
++++ pika-2.99.12/plug-ins/flame/meson.build 2022-08-26 11:05:18.353294700 -0600
+@@ -11,9 +11,9 @@
+ plugin_sources += windows.compile_resources(
+ pika_plugins_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(plugin_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(plugin_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(plugin_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(plugin_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+diff -bur pika-2.99.12-c/plug-ins/fractal-explorer/meson.build pika-2.99.12/plug-ins/fractal-explorer/meson.build
+--- pika-2.99.12-c/plug-ins/fractal-explorer/meson.build 2022-08-21 13:21:38.000000000 -0600
++++ pika-2.99.12/plug-ins/fractal-explorer/meson.build 2022-08-26 11:05:18.355294200 -0600
+@@ -11,9 +11,9 @@
+ plugin_sources += windows.compile_resources(
+ pika_plugins_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(plugin_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(plugin_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(plugin_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(plugin_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+diff -bur pika-2.99.12-c/plug-ins/gfig/meson.build pika-2.99.12/plug-ins/gfig/meson.build
+--- pika-2.99.12-c/plug-ins/gfig/meson.build 2022-08-21 13:21:38.000000000 -0600
++++ pika-2.99.12/plug-ins/gfig/meson.build 2022-08-26 11:05:18.358466600 -0600
+@@ -27,9 +27,9 @@
+ plugin_sources += windows.compile_resources(
+ pika_plugins_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(plugin_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(plugin_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(plugin_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(plugin_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+diff -bur pika-2.99.12-c/plug-ins/pikaressionist/meson.build pika-2.99.12/plug-ins/pikaressionist/meson.build
+--- pika-2.99.12-c/plug-ins/pikaressionist/meson.build 2022-08-21 13:21:38.000000000 -0600
++++ pika-2.99.12/plug-ins/pikaressionist/meson.build 2022-08-26 11:05:18.361592500 -0600
+@@ -29,9 +29,9 @@
+ plugin_sources += windows.compile_resources(
+ pika_plugins_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(plugin_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(plugin_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(plugin_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(plugin_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+diff -bur pika-2.99.12-c/plug-ins/gradient-flare/meson.build pika-2.99.12/plug-ins/gradient-flare/meson.build
+--- pika-2.99.12-c/plug-ins/gradient-flare/meson.build 2022-08-21 13:21:38.000000000 -0600
++++ pika-2.99.12/plug-ins/gradient-flare/meson.build 2022-08-26 11:05:18.365447800 -0600
+@@ -10,9 +10,9 @@
+ plugin_sources += windows.compile_resources(
+ pika_plugins_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(plugin_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(plugin_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(plugin_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(plugin_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+diff -bur pika-2.99.12-c/plug-ins/help/meson.build pika-2.99.12/plug-ins/help/meson.build
+--- pika-2.99.12-c/plug-ins/help/meson.build 2022-08-21 13:21:38.000000000 -0600
++++ pika-2.99.12/plug-ins/help/meson.build 2022-08-26 11:05:18.367447200 -0600
+@@ -13,9 +13,9 @@
+ plugin_sources += windows.compile_resources(
+ pika_plugins_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(plugin_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(plugin_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(plugin_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(plugin_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+diff -bur pika-2.99.12-c/plug-ins/help-browser/meson.build pika-2.99.12/plug-ins/help-browser/meson.build
+--- pika-2.99.12-c/plug-ins/help-browser/meson.build 2022-08-21 13:21:38.000000000 -0600
++++ pika-2.99.12/plug-ins/help-browser/meson.build 2022-08-26 11:05:18.369447300 -0600
+@@ -12,9 +12,9 @@
+ plugin_sources += windows.compile_resources(
+ pika_plugins_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(plugin_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(plugin_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(plugin_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(plugin_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+diff -bur pika-2.99.12-c/plug-ins/ifs-compose/meson.build pika-2.99.12/plug-ins/ifs-compose/meson.build
+--- pika-2.99.12-c/plug-ins/ifs-compose/meson.build 2022-08-21 13:21:38.000000000 -0600
++++ pika-2.99.12/plug-ins/ifs-compose/meson.build 2022-08-26 11:05:18.371447900 -0600
+@@ -10,9 +10,9 @@
+ plugin_sources += windows.compile_resources(
+ pika_plugins_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(plugin_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(plugin_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(plugin_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(plugin_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+diff -bur pika-2.99.12-c/plug-ins/imagemap/meson.build pika-2.99.12/plug-ins/imagemap/meson.build
+--- pika-2.99.12-c/plug-ins/imagemap/meson.build 2022-08-21 13:21:38.000000000 -0600
++++ pika-2.99.12/plug-ins/imagemap/meson.build 2022-08-26 11:05:18.373620200 -0600
+@@ -73,9 +73,9 @@
+ plugin_sources += windows.compile_resources(
+ pika_plugins_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(plugin_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(plugin_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(plugin_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(plugin_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+diff -bur pika-2.99.12-c/plug-ins/lighting/meson.build pika-2.99.12/plug-ins/lighting/meson.build
+--- pika-2.99.12-c/plug-ins/lighting/meson.build 2022-08-21 13:21:38.000000000 -0600
++++ pika-2.99.12/plug-ins/lighting/meson.build 2022-08-26 11:05:18.376619900 -0600
+@@ -17,9 +17,9 @@
+ plugin_sources += windows.compile_resources(
+ pika_plugins_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(plugin_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(plugin_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(plugin_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(plugin_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+diff -bur pika-2.99.12-c/plug-ins/map-object/meson.build pika-2.99.12/plug-ins/map-object/meson.build
+--- pika-2.99.12-c/plug-ins/map-object/meson.build 2022-08-21 13:21:38.000000000 -0600
++++ pika-2.99.12/plug-ins/map-object/meson.build 2022-08-26 11:05:18.378618900 -0600
+@@ -16,9 +16,9 @@
+ plugin_sources += windows.compile_resources(
+ pika_plugins_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(plugin_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(plugin_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(plugin_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(plugin_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+diff -bur pika-2.99.12-c/plug-ins/metadata/meson.build pika-2.99.12/plug-ins/metadata/meson.build
+--- pika-2.99.12-c/plug-ins/metadata/meson.build 2022-08-21 13:21:38.000000000 -0600
++++ pika-2.99.12/plug-ins/metadata/meson.build 2022-08-26 11:05:18.381620300 -0600
+@@ -17,9 +17,9 @@
+ plugin_sources += windows.compile_resources(
+ plugin_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(plugin_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(plugin_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(plugin_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(plugin_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+@@ -55,9 +55,9 @@
+ plugin_sources += windows.compile_resources(
+ plugin_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(plugin_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(plugin_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(plugin_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(plugin_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+diff -bur pika-2.99.12-c/plug-ins/pagecurl/meson.build pika-2.99.12/plug-ins/pagecurl/meson.build
+--- pika-2.99.12-c/plug-ins/pagecurl/meson.build 2022-08-21 13:21:38.000000000 -0600
++++ pika-2.99.12/plug-ins/pagecurl/meson.build 2022-08-26 11:05:18.383620200 -0600
+@@ -41,9 +41,9 @@
+ plugin_sources += windows.compile_resources(
+ pika_plugins_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(plugin_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(plugin_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(plugin_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(plugin_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+diff -bur pika-2.99.12-c/plug-ins/print/meson.build pika-2.99.12/plug-ins/print/meson.build
+--- pika-2.99.12-c/plug-ins/print/meson.build 2022-08-21 13:21:38.000000000 -0600
++++ pika-2.99.12/plug-ins/print/meson.build 2022-08-26 11:05:18.385620400 -0600
+@@ -17,9 +17,9 @@
+ plugin_sources += windows.compile_resources(
+ pika_plugins_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(plugin_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(plugin_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(plugin_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(plugin_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+diff -bur pika-2.99.12-c/plug-ins/script-fu/interpreter/meson.build pika-2.99.12/plug-ins/script-fu/interpreter/meson.build
+--- pika-2.99.12-c/plug-ins/script-fu/interpreter/meson.build 2022-08-21 13:21:38.000000000 -0600
++++ pika-2.99.12/plug-ins/script-fu/interpreter/meson.build 2022-08-26 11:05:18.388620100 -0600
+@@ -12,9 +12,9 @@
+ plugin_sources += windows.compile_resources(
+ plugin_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(executable_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(executable_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(executable_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(executable_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+diff -bur pika-2.99.12-c/plug-ins/script-fu/meson.build pika-2.99.12/plug-ins/script-fu/meson.build
+--- pika-2.99.12-c/plug-ins/script-fu/meson.build 2022-08-21 13:21:38.000000000 -0600
++++ pika-2.99.12/plug-ins/script-fu/meson.build 2022-08-26 11:05:18.392620300 -0600
+@@ -31,9 +31,9 @@
+ plugin_sources += windows.compile_resources(
+ plugin_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(executable_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(executable_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(executable_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(executable_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+diff -bur pika-2.99.12-c/plug-ins/script-fu/server/meson.build pika-2.99.12/plug-ins/script-fu/server/meson.build
+--- pika-2.99.12-c/plug-ins/script-fu/server/meson.build 2022-08-21 13:21:38.000000000 -0600
++++ pika-2.99.12/plug-ins/script-fu/server/meson.build 2022-08-26 11:05:18.394619900 -0600
+@@ -12,9 +12,9 @@
+ plugin_sources += windows.compile_resources(
+ plugin_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(executable_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(executable_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(executable_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(executable_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+diff -bur pika-2.99.12-c/plug-ins/selection-to-path/meson.build pika-2.99.12/plug-ins/selection-to-path/meson.build
+--- pika-2.99.12-c/plug-ins/selection-to-path/meson.build 2022-08-21 13:21:38.000000000 -0600
++++ pika-2.99.12/plug-ins/selection-to-path/meson.build 2022-08-26 11:05:18.396618000 -0600
+@@ -16,9 +16,9 @@
+ plugin_sources += windows.compile_resources(
+ pika_plugins_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(plugin_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(plugin_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(plugin_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(plugin_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+diff -bur pika-2.99.12-c/plug-ins/twain/meson.build pika-2.99.12/plug-ins/twain/meson.build
+--- pika-2.99.12-c/plug-ins/twain/meson.build 2022-08-21 13:21:38.000000000 -0600
++++ pika-2.99.12/plug-ins/twain/meson.build 2022-08-26 11:05:18.398619200 -0600
+@@ -14,9 +14,9 @@
+ plugin_sources += windows.compile_resources(
+ pika_plugins_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(plugin_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(plugin_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(plugin_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(plugin_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
+--- pika-2.99.14/plug-ins/file-icns/meson.build.orig 2022-11-14 00:04:38.000000000 +0100
++++ pika-2.99.14/plug-ins/file-icns/meson.build 2022-11-19 21:03:14.665912600 +0100
+@@ -11,9 +11,9 @@
+ plugin_sources += windows.compile_resources(
+ pika_plugins_rc,
+ args: [
+- '--define', 'ORIGINALFILENAME_STR="@0@"'.format(plugin_name+'.exe'),
+- '--define', 'INTERNALNAME_STR="@0@"' .format(plugin_name),
+- '--define', 'TOP_SRCDIR="@0@"' .format(meson.project_source_root()),
++ '--define', 'ORIGINALFILENAME_STR=@0@'.format(plugin_name+'.exe'),
++ '--define', 'INTERNALNAME_STR=@0@' .format(plugin_name),
++ '--define', 'TOP_SRCDIR=@0@' .format(meson.project_source_root()),
+ ],
+ include_directories: [
+ rootInclude, appInclude,
diff --git a/libpika/pikabrush.c b/libpika/pikabrush.c
new file mode 100644
index 0000000..40900f6
--- /dev/null
+++ b/libpika/pikabrush.c
@@ -0,0 +1,312 @@
+/* LIBPIKA - The PIKA Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * pikabrush.c
+ * Copyright (C) 2023 Jehan
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * 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
+ * .
+ */
+
+#include "config.h"
+
+#include "pika.h"
+
+#include "pikabrush.h"
+
+struct _PikaBrush
+{
+ PikaResource parent_instance;
+
+ /* Native size buffers of the brush contents. */
+ GeglBuffer *buffer;
+ GeglBuffer *mask;
+};
+
+G_DEFINE_TYPE (PikaBrush, pika_brush, PIKA_TYPE_RESOURCE);
+
+static void pika_brush_finalize (GObject *object);
+static void pika_brush_get_data (PikaBrush *brush);
+static GeglBuffer * pika_brush_scale (GeglBuffer *buffer,
+ gint max_width,
+ gint max_height);
+static const Babl * pika_brush_data_get_format (gint bpp,
+ gboolean with_alpha);
+
+
+static void pika_brush_class_init (PikaBrushClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = pika_brush_finalize;
+}
+
+static void pika_brush_init (PikaBrush *brush)
+{
+ brush->buffer = NULL;
+ brush->mask = NULL;
+}
+
+static void
+pika_brush_finalize (GObject *object)
+{
+ PikaBrush *brush = PIKA_BRUSH (object);
+
+ g_clear_object (&brush->buffer);
+ g_clear_object (&brush->mask);
+
+ G_OBJECT_CLASS (pika_brush_parent_class)->finalize (object);
+}
+
+
+/**
+ * pika_brush_get_buffer:
+ * @brush: a [class@Brush].
+ * @max_width: a maximum width for the returned buffer.
+ * @max_height: a maximum height for the returned buffer.
+ * @format: an optional Babl format.
+ *
+ * Gets pixel data of the brush within the bounding box specified by @max_width
+ * and @max_height. The data will be scaled down so that it fits within this
+ * size without changing its ratio. If the brush is smaller than this size to
+ * begin with, it will not be scaled up.
+ *
+ * If @max_width or @max_height are %NULL, the buffer is returned in the brush's
+ * native size.
+ *
+ * When the brush is parametric or a raster mask, only the mask (as returned by
+ * [method@Gimp.Brush.get_mask]) will be set. The returned buffer will be NULL.
+ *
+ * Make sure you called [func@Gegl.init] before calling any function using
+ * `GEGL`.
+ *
+ * Returns: (transfer full): a [class@Gegl.Buffer] of %NULL if the brush is parametric
+ * or mask only.
+*/
+GeglBuffer *
+pika_brush_get_buffer (PikaBrush *brush,
+ gint max_width,
+ gint max_height,
+ const Babl *format)
+{
+ pika_brush_get_data (brush);
+
+ if (brush->buffer == NULL)
+ return NULL;
+
+ if (max_width == 0 || max_height == 0 ||
+ (gegl_buffer_get_width (brush->buffer) <= max_width &&
+ gegl_buffer_get_height (brush->buffer) <= max_height))
+ return gegl_buffer_dup (brush->buffer);
+
+ return pika_brush_scale (brush->buffer, max_width, max_height);
+}
+
+/**
+ * pika_brush_get_mask:
+ * @brush: a [class@Brush].
+ * @max_width: a maximum width for the returned buffer.
+ * @max_height: a maximum height for the returned buffer.
+ * @format: an optional Babl format.
+ *
+ * Gets mask data of the brush within the bounding box specified by @max_width
+ * and @max_height. The data will be scaled down so that it fits within this
+ * size without changing its ratio. If the brush is smaller than this size to
+ * begin with, it will not be scaled up.
+ *
+ * If @max_width or @max_height are %NULL, the buffer is returned in the brush's
+ * native size.
+ *
+ * Make sure you called [func@Gegl.init] before calling any function using
+ * `GEGL`.
+ *
+ * Returns: (transfer full): a [class@Gegl.Buffer] representing the @brush mask.
+*/
+GeglBuffer *
+pika_brush_get_mask (PikaBrush *brush,
+ gint max_width,
+ gint max_height,
+ const Babl *format)
+{
+ pika_brush_get_data (brush);
+
+ g_return_val_if_fail (brush->mask != NULL, NULL);
+
+ if (max_width == 0 || max_height == 0 ||
+ (gegl_buffer_get_width (brush->mask) <= max_width &&
+ gegl_buffer_get_height (brush->mask) <= max_height))
+ return gegl_buffer_dup (brush->mask);
+
+ return pika_brush_scale (brush->mask, max_width, max_height);
+}
+
+static void
+pika_brush_get_data (PikaBrush *brush)
+{
+ gint width;
+ gint height;
+ gint mask_bpp;
+ GBytes *mask_bytes;
+ gint color_bpp;
+ GBytes *color_bytes;
+ const guchar *mask;
+ gsize mask_size;
+ const guchar *color;
+ gsize color_size;
+
+ /* We check the mask because the buffer might be NULL.
+ *
+ * This check assumes that the brush contents doesn't change, which is not a
+ * perfect assumption. We could maybe add a PDB call which would return
+ * the new brush data only if it changed since last call (which can be
+ * verified with some kind of internal runtime version to pass from one call
+ * to another). TODO
+ */
+ if (brush->mask != NULL)
+ return;
+
+ g_clear_object (&brush->buffer);
+ g_clear_object (&brush->mask);
+
+ _pika_brush_get_pixels (brush, &width, &height,
+ &mask_bpp, &mask_bytes,
+ &color_bpp, &color_bytes);
+
+ mask = g_bytes_unref_to_data (mask_bytes, &mask_size);
+ color = g_bytes_unref_to_data (color_bytes, &color_size);
+
+ brush->mask = gegl_buffer_linear_new_from_data ((const gpointer) mask,
+ pika_brush_data_get_format (mask_bpp, FALSE),
+ GEGL_RECTANGLE (0, 0, width, height),
+ 0, g_free, NULL);
+ if (color_bpp > 0)
+ {
+ GeglBufferIterator *gi;
+ GeglBuffer *buffer;
+
+ buffer = gegl_buffer_linear_new_from_data ((const gpointer) color,
+ pika_brush_data_get_format (color_bpp, FALSE),
+ GEGL_RECTANGLE (0, 0, width, height),
+ 0, g_free, NULL);
+ brush->buffer = gegl_buffer_new (GEGL_RECTANGLE (0, 0, width, height),
+ pika_brush_data_get_format (color_bpp, TRUE));
+
+ gi = gegl_buffer_iterator_new (brush->buffer, NULL, 0, NULL,
+ GEGL_ACCESS_WRITE, GEGL_ABYSS_NONE, 3);
+ gegl_buffer_iterator_add (gi, buffer, GEGL_RECTANGLE (0, 0, width, height),
+ 0, NULL, GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
+ gegl_buffer_iterator_add (gi, brush->mask, GEGL_RECTANGLE (0, 0, width, height),
+ 0, NULL, GEGL_ACCESS_READ, GEGL_ABYSS_NONE);
+ while (gegl_buffer_iterator_next (gi))
+ {
+ guint8 *out = gi->items[0].data;
+ guint8 *color = gi->items[1].data;
+ guint8 *alpha = gi->items[2].data;
+
+ for (gint i = 0; i < gi->length; i++)
+ {
+ gint c;
+
+ for (c = 0; c < color_bpp; c++)
+ out[c] = color[c];
+
+ out[c] = alpha[0];
+
+ out += color_bpp + 1;
+ color += color_bpp;
+ alpha += 1;
+ }
+ }
+
+ g_object_unref (buffer);
+ }
+}
+
+static GeglBuffer *
+pika_brush_scale (GeglBuffer *buffer,
+ gint max_width,
+ gint max_height)
+{
+ GeglBuffer *scaled = NULL;
+ GeglNode *graph;
+ GeglNode *source;
+ GeglNode *op;
+ GeglNode *sink;
+ gdouble width;
+ gdouble height;
+ gdouble scale;
+
+ height = (gdouble) max_height;
+ width = (gdouble) gegl_buffer_get_width (buffer) / gegl_buffer_get_height (buffer) * height;
+ if (width > (gdouble) max_width)
+ {
+ width = (gdouble) max_width;
+ height = (gdouble) gegl_buffer_get_height (buffer) / gegl_buffer_get_width (buffer) * width;
+ }
+ scale = width / gegl_buffer_get_width (buffer);
+
+ graph = gegl_node_new ();
+ source = gegl_node_new_child (graph,
+ "operation", "gegl:buffer-source",
+ "buffer", buffer,
+ NULL);
+ op = gegl_node_new_child (graph,
+ "operation", "gegl:scale-ratio",
+ "origin-x", 0.0,
+ "origin-y", 0.0,
+ "sampler", PIKA_INTERPOLATION_LINEAR,
+ "abyss-policy", GEGL_ABYSS_CLAMP,
+ "x", scale,
+ "y", scale,
+ NULL);
+ sink = gegl_node_new_child (graph,
+ "operation", "gegl:buffer-sink",
+ "buffer", &scaled,
+ "format", gegl_buffer_get_format (buffer),
+ NULL);
+ gegl_node_link_many (source, op, sink, NULL);
+ gegl_node_process (sink);
+
+ g_object_unref (graph);
+
+ return scaled;
+}
+
+static const Babl *
+pika_brush_data_get_format (gint bpp,
+ gboolean with_alpha)
+{
+ /* It's an ugly way to determine the proper format but pika_brush_get_pixels()
+ * doesn't give more info.
+ */
+ switch (bpp)
+ {
+ case 1:
+ if (! with_alpha)
+ return babl_format ("Y' u8");
+ /* fallthrough */
+ case 2:
+ return babl_format ("Y'A u8");
+ case 3:
+ if (! with_alpha)
+ return babl_format ("R'G'B' u8");
+ /* fallthrough */
+ case 4:
+ return babl_format ("R'G'B'A u8");
+ default:
+ g_return_val_if_reached (NULL);
+ }
+
+ g_return_val_if_reached (NULL);
+}
diff --git a/libpika/pikabrush.h b/libpika/pikabrush.h
new file mode 100644
index 0000000..289b56b
--- /dev/null
+++ b/libpika/pikabrush.h
@@ -0,0 +1,52 @@
+/* LIBPIKA - The PIKA Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * pikabrush.h
+ * Copyright (C) 2023 Jehan
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * 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 (__PIKA_H_INSIDE__) && !defined (PIKA_COMPILATION)
+#error "Only can be included directly."
+#endif
+
+#ifndef __PIKA_BRUSH_H__
+#define __PIKA_BRUSH_H__
+
+G_BEGIN_DECLS
+
+/* For information look into the C source or the html documentation */
+
+
+#include
+
+
+#define PIKA_TYPE_BRUSH (pika_brush_get_type ())
+G_DECLARE_FINAL_TYPE (PikaBrush, pika_brush, PIKA, BRUSH, PikaResource)
+
+
+GeglBuffer * pika_brush_get_buffer (PikaBrush *brush,
+ gint max_width,
+ gint max_height,
+ const Babl *format) G_GNUC_WARN_UNUSED_RESULT;
+GeglBuffer * pika_brush_get_mask (PikaBrush *brush,
+ gint max_width,
+ gint max_height,
+ const Babl *format) G_GNUC_WARN_UNUSED_RESULT;
+
+G_END_DECLS
+
+#endif /* __PIKA_BRUSH_H__ */
diff --git a/libpika/pikabrushchooser.c b/libpika/pikabrushchooser.c
new file mode 100644
index 0000000..73553f5
--- /dev/null
+++ b/libpika/pikabrushchooser.c
@@ -0,0 +1,456 @@
+/* LIBPIKA - The PIKA Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * pikabrushchooser.c
+ * Copyright (C) 1998 Andy Thomas
+ *
+ * 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 "libpikawidgets/pikawidgets.h"
+
+#include "pika.h"
+
+#include "pikauitypes.h"
+#include "pikabrushchooser.h"
+#include "pikauimarshal.h"
+
+#include "libpika-intl.h"
+
+
+/**
+ * SECTION: pikabrushchooser
+ * @title: PikaBrushChooser
+ * @short_description: A button which pops up a brush selection dialog.
+ *
+ * A button which pops up a brush selection dialog.
+ *
+ * Note that this widget draws itself using `GEGL` code. You **must** call
+ * [func@Gegl.init] first to be able to use this chooser.
+ **/
+
+#define CELL_SIZE 40
+
+struct _PikaBrushChooser
+{
+ PikaResourceChooser parent_instance;
+
+ GtkWidget *preview;
+ GtkWidget *popup;
+
+ PikaBrush *brush;
+ gint width;
+ gint height;
+ GeglBuffer *buffer;
+ GeglBuffer *mask;
+};
+
+
+static void pika_brush_chooser_finalize (GObject *object);
+
+static gboolean pika_brush_select_on_preview_events (GtkWidget *widget,
+ GdkEvent *event,
+ PikaBrushChooser *button);
+
+static void pika_brush_select_preview_draw (PikaBrushChooser *chooser,
+ PikaPreviewArea *area);
+
+static void pika_brush_chooser_draw (PikaBrushChooser *self);
+static void pika_brush_chooser_get_brush_bitmap (PikaBrushChooser *self,
+ gint width,
+ gint height);
+
+/* Popup methods. */
+static void pika_brush_chooser_open_popup (PikaBrushChooser *button,
+ gint x,
+ gint y);
+static void pika_brush_chooser_close_popup (PikaBrushChooser *button);
+
+
+static const GtkTargetEntry drag_target = { "application/x-pika-brush-name", 0, 0 };
+
+G_DEFINE_FINAL_TYPE (PikaBrushChooser, pika_brush_chooser, PIKA_TYPE_RESOURCE_CHOOSER)
+
+
+static void
+pika_brush_chooser_class_init (PikaBrushChooserClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ PikaResourceChooserClass *res_class = PIKA_RESOURCE_CHOOSER_CLASS (klass);
+
+ res_class->draw_interior = (void (*)(PikaResourceChooser *)) pika_brush_chooser_draw;
+ res_class->resource_type = PIKA_TYPE_BRUSH;
+
+ object_class->finalize = pika_brush_chooser_finalize;
+}
+
+static void
+pika_brush_chooser_init (PikaBrushChooser *chooser)
+{
+ GtkWidget *widget;
+ gint scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (chooser));
+
+ chooser->brush = NULL;
+ chooser->buffer = NULL;
+ chooser->mask = NULL;
+
+ widget = gtk_aspect_frame_new (NULL, 0.5, 0.5, 1.0, FALSE);
+ gtk_frame_set_shadow_type (GTK_FRAME (widget), GTK_SHADOW_IN);
+ gtk_box_pack_start (GTK_BOX (chooser), widget, FALSE, FALSE, 0);
+ gtk_widget_show (widget);
+
+ chooser->preview = pika_preview_area_new ();
+ gtk_widget_add_events (chooser->preview,
+ GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
+ gtk_widget_set_size_request (chooser->preview, scale_factor * CELL_SIZE, scale_factor * CELL_SIZE);
+ gtk_container_add (GTK_CONTAINER (widget), chooser->preview);
+ gtk_widget_show (chooser->preview);
+
+ g_signal_connect_swapped (chooser->preview, "size-allocate",
+ G_CALLBACK (pika_brush_chooser_draw),
+ chooser);
+
+ g_signal_connect (chooser->preview, "event",
+ G_CALLBACK (pika_brush_select_on_preview_events),
+ chooser);
+
+ widget = gtk_button_new_with_mnemonic (_("_Browse..."));
+ gtk_box_pack_start (GTK_BOX (chooser), widget, FALSE, FALSE, 0);
+
+ pika_resource_chooser_set_drag_target (PIKA_RESOURCE_CHOOSER (chooser),
+ chooser->preview,
+ &drag_target);
+
+ pika_resource_chooser_set_clickable (PIKA_RESOURCE_CHOOSER (chooser),
+ widget);
+ gtk_widget_show (widget);
+}
+
+static void
+pika_brush_chooser_finalize (GObject *object)
+{
+ PikaBrushChooser *chooser = PIKA_BRUSH_CHOOSER (object);
+
+ g_clear_object (&chooser->buffer);
+ g_clear_object (&chooser->mask);
+
+ G_OBJECT_CLASS (pika_brush_chooser_parent_class)->finalize (object);
+}
+
+/**
+ * pika_brush_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.
+ * @resource: (nullable): Initial brush.
+ *
+ * Creates a new #GtkWidget that lets a user choose a brush.
+ * You can put this widget in a plug-in dialog.
+ *
+ * When brush is NULL, initial choice is from context.
+ *
+ * Returns: A #GtkWidget that you can use in your UI.
+ *
+ * Since: 2.4
+ */
+GtkWidget *
+pika_brush_chooser_new (const gchar *title,
+ const gchar *label,
+ PikaResource *resource)
+{
+ GtkWidget *self;
+
+ if (resource == NULL)
+ resource = PIKA_RESOURCE (pika_context_get_brush ());
+
+ if (title)
+ self = g_object_new (PIKA_TYPE_BRUSH_CHOOSER,
+ "title", title,
+ "label", label,
+ "resource", resource,
+ NULL);
+ else
+ self = g_object_new (PIKA_TYPE_BRUSH_CHOOSER,
+ "label", label,
+ "resource", resource,
+ NULL);
+
+ return self;
+}
+
+/* private functions */
+
+static void
+pika_brush_chooser_draw (PikaBrushChooser *chooser)
+{
+ GtkAllocation allocation;
+
+ gtk_widget_get_allocation (chooser->preview, &allocation);
+
+ pika_brush_chooser_get_brush_bitmap (chooser, allocation.width, allocation.height);
+ pika_brush_select_preview_draw (chooser, PIKA_PREVIEW_AREA (chooser->preview));
+}
+
+static void
+pika_brush_chooser_get_brush_bitmap (PikaBrushChooser *chooser,
+ gint width,
+ gint height)
+{
+ PikaBrush *brush;
+
+ g_object_get (chooser, "resource", &brush, NULL);
+
+ if (chooser->brush == brush &&
+ chooser->width == width &&
+ chooser->height == height)
+ {
+ /* Let's assume brush contents is not changing in a single run. */
+ g_object_unref (brush);
+ return;
+ }
+
+ g_clear_object (&chooser->buffer);
+ g_clear_object (&chooser->mask);
+ chooser->width = chooser->height = 0;
+
+ chooser->brush = brush;
+ chooser->buffer = pika_brush_get_buffer (brush, width, height, NULL);
+
+ if (chooser->buffer)
+ {
+ GeglColor *white_color = gegl_color_new ("rgb(1.0,1.0,1.0)");
+ GeglNode *graph;
+ GeglNode *white;
+ GeglNode *source;
+ GeglNode *op;
+
+ chooser->width = gegl_buffer_get_width (chooser->buffer);
+ chooser->height = gegl_buffer_get_height (chooser->buffer);
+
+ graph = gegl_node_new ();
+ white = gegl_node_new_child (graph,
+ "operation", "gegl:rectangle",
+ "x", 0.0,
+ "y", 0.0,
+ "width", (gdouble) gegl_buffer_get_width (chooser->buffer),
+ "height", (gdouble) gegl_buffer_get_height (chooser->buffer),
+ "color", white_color,
+ NULL);
+ source = gegl_node_new_child (graph,
+ "operation", "gegl:buffer-source",
+ "buffer", chooser->buffer,
+ NULL);
+ op = gegl_node_new_child (graph,
+ "operation", "svg:src-over",
+ NULL);
+ gegl_node_link_many (white, op, NULL);
+ gegl_node_connect (source, "output", op, "aux");
+ gegl_node_blit_buffer (op, chooser->buffer, NULL, 0, GEGL_ABYSS_NONE);
+
+ g_object_unref (graph);
+ g_object_unref (white_color);
+ }
+ else
+ {
+ GeglBufferIterator *iter;
+
+ chooser->mask = pika_brush_get_mask (brush, width, height, NULL);
+ chooser->width = gegl_buffer_get_width (chooser->mask);
+ chooser->height = gegl_buffer_get_height (chooser->mask);
+
+ /* More common to display mask brushes as black on white. */
+ iter = gegl_buffer_iterator_new (chooser->mask, NULL, 0,
+ babl_format ("Y' u8"),
+ GEGL_ACCESS_READWRITE,
+ GEGL_ABYSS_NONE, 1);
+
+ while (gegl_buffer_iterator_next (iter))
+ {
+ guint8 *data = iter->items[0].data;
+
+ for (gint i = 0; i < iter->length; i++)
+ {
+ *data = 255 - *data;
+ data++;
+ }
+ }
+ }
+
+ g_object_unref (brush);
+}
+
+/* On mouse events in self's preview, popup a zoom view of entire brush */
+static gboolean
+pika_brush_select_on_preview_events (GtkWidget *widget,
+ GdkEvent *event,
+ PikaBrushChooser *self)
+{
+ GdkEventButton *bevent;
+
+ switch (event->type)
+ {
+ case GDK_BUTTON_PRESS:
+ bevent = (GdkEventButton *) event;
+
+ if (bevent->button == 1)
+ {
+ gtk_grab_add (widget);
+ pika_brush_chooser_open_popup (self,
+ bevent->x, bevent->y);
+ }
+ break;
+
+ case GDK_BUTTON_RELEASE:
+ bevent = (GdkEventButton *) event;
+
+ if (bevent->button == 1)
+ {
+ gtk_grab_remove (widget);
+ pika_brush_chooser_close_popup (self);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+/* Draw a PikaPreviewArea with a given bitmap. */
+static void
+pika_brush_select_preview_draw (PikaBrushChooser *chooser,
+ PikaPreviewArea *preview)
+{
+ PikaPreviewArea *area = PIKA_PREVIEW_AREA (preview);
+ GeglBuffer *src_buffer;
+ const Babl *format;
+ guchar *src;
+ PikaImageType type;
+ gint rowstride;
+ GtkAllocation allocation;
+ gint x = 0;
+ gint y = 0;
+
+ gtk_widget_get_allocation (GTK_WIDGET (preview), &allocation);
+
+ /* Fill with white. */
+ if (chooser->width < allocation.width ||
+ chooser->height < allocation.height)
+ {
+ pika_preview_area_fill (area,
+ 0, 0,
+ allocation.width,
+ allocation.height,
+ 0xFF, 0xFF, 0xFF);
+
+ x = ((allocation.width - chooser->width) / 2);
+ y = ((allocation.height - chooser->height) / 2);
+ }
+
+ /* Draw the brush. */
+ if (chooser->buffer)
+ {
+ src_buffer = chooser->buffer;
+ format = gegl_buffer_get_format (src_buffer);
+ rowstride = chooser->width * babl_format_get_bytes_per_pixel (format);
+ if (babl_format_has_alpha (format))
+ type = PIKA_RGBA_IMAGE;
+ else
+ type = PIKA_RGB_IMAGE;
+ }
+ else
+ {
+ src_buffer = chooser->mask;
+ format = babl_format ("Y' u8");
+ rowstride = chooser->width;
+ type = PIKA_GRAY_IMAGE;
+ }
+
+ src = g_try_malloc (sizeof (guchar) * rowstride * chooser->height);
+
+ gegl_buffer_get (src_buffer, NULL, 1.0, format, src, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+ pika_preview_area_draw (area, x, y, chooser->width, chooser->height, type, src, rowstride);
+
+ g_free (src);
+}
+
+/* popup methods. */
+
+static void
+pika_brush_chooser_open_popup (PikaBrushChooser *chooser,
+ gint x,
+ gint y)
+{
+ GtkWidget *frame;
+ GtkWidget *preview;
+ GdkMonitor *monitor;
+ GdkRectangle workarea;
+ gint x_org;
+ gint y_org;
+
+ if (chooser->popup)
+ pika_brush_chooser_close_popup (chooser);
+
+ if (chooser->width <= CELL_SIZE && chooser->height <= CELL_SIZE)
+ return;
+
+ chooser->popup = gtk_window_new (GTK_WINDOW_POPUP);
+ gtk_window_set_type_hint (GTK_WINDOW (chooser->popup), GDK_WINDOW_TYPE_HINT_DND);
+ gtk_window_set_screen (GTK_WINDOW (chooser->popup),
+ gtk_widget_get_screen (GTK_WIDGET (chooser)));
+
+ frame = gtk_frame_new (NULL);
+ gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
+ gtk_container_add (GTK_CONTAINER (chooser->popup), frame);
+ gtk_widget_show (frame);
+
+ preview = pika_preview_area_new ();
+ gtk_widget_set_size_request (preview, chooser->width, chooser->height);
+ gtk_container_add (GTK_CONTAINER (frame), preview);
+ gtk_widget_show (preview);
+
+ /* decide where to put the popup: near the preview i.e. at mousedown coords */
+ gdk_window_get_origin (gtk_widget_get_window (chooser->preview),
+ &x_org, &y_org);
+
+ monitor = pika_widget_get_monitor (GTK_WIDGET (chooser));
+ gdk_monitor_get_workarea (monitor, &workarea);
+
+ x = x_org + x - (chooser->width / 2);
+ y = y_org + y - (chooser->height / 2);
+
+ x = CLAMP (x, workarea.x, workarea.x + workarea.width - chooser->width);
+ y = CLAMP (y, workarea.y, workarea.y + workarea.height - chooser->height);
+
+ gtk_window_move (GTK_WINDOW (chooser->popup), x, y);
+
+ gtk_widget_show (chooser->popup);
+
+ /* Draw popup now. Usual events do not cause a draw. */
+ pika_brush_select_preview_draw (chooser, PIKA_PREVIEW_AREA (preview));
+ gdk_window_set_transient_for (gtk_widget_get_window (chooser->popup),
+ gtk_widget_get_window (gtk_widget_get_toplevel (GTK_WIDGET (chooser))));
+}
+
+static void
+pika_brush_chooser_close_popup (PikaBrushChooser *self)
+{
+ g_clear_pointer (&self->popup, gtk_widget_destroy);
+}
diff --git a/libpika/pikabrushchooser.h b/libpika/pikabrushchooser.h
new file mode 100644
index 0000000..3c2b399
--- /dev/null
+++ b/libpika/pikabrushchooser.h
@@ -0,0 +1,43 @@
+/* LIBPIKA - The PIKA Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * pikabrushchooser.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 (__PIKA_UI_H_INSIDE__) && !defined (PIKA_COMPILATION)
+#error "Only can be included directly."
+#endif
+
+#ifndef __PIKA_BRUSH_CHOOSER_H__
+#define __PIKA_BRUSH_CHOOSER_H__
+
+#include
+
+G_BEGIN_DECLS
+
+#define PIKA_TYPE_BRUSH_CHOOSER (pika_brush_chooser_get_type ())
+G_DECLARE_FINAL_TYPE (PikaBrushChooser, pika_brush_chooser, PIKA, BRUSH_CHOOSER, PikaResourceChooser)
+
+
+GtkWidget * pika_brush_chooser_new (const gchar *title,
+ const gchar *label,
+ PikaResource *resource);
+
+
+G_END_DECLS
+
+#endif /* __PIKA_BRUSH_CHOOSER_H__ */
diff --git a/libpika/pikadrawablechooser.c b/libpika/pikadrawablechooser.c
new file mode 100644
index 0000000..807ff70
--- /dev/null
+++ b/libpika/pikadrawablechooser.c
@@ -0,0 +1,615 @@
+/* LIBPIKA - The PIKA Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * pikadrawablechooser.h
+ * Copyright (C) 2023 Jehan
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see
+ * .
+ */
+
+#include "config.h"
+
+#include
+#include
+
+#include "libpikawidgets/pikawidgets.h"
+
+#include "pika.h"
+
+#include "pikauitypes.h"
+#include "pikadrawablechooser.h"
+#include "pikauimarshal.h"
+
+#include "libpika-intl.h"
+
+
+/**
+ * SECTION: pikadrawablechooser
+ * @title: PikaDrawableChooser
+ * @short_description: A widget allowing to select a drawable.
+ *
+ * The chooser contains an optional label and a button which queries the core
+ * process to pop up a drawable selection dialog.
+ *
+ * Since: 3.0
+ **/
+
+#define CELL_SIZE 40
+
+enum
+{
+ PROP_0,
+ PROP_TITLE,
+ PROP_LABEL,
+ PROP_DRAWABLE,
+ PROP_DRAWABLE_TYPE,
+ N_PROPS
+};
+
+struct _PikaDrawableChooser
+{
+ GtkBox parent_instance;
+
+ GType drawable_type;
+ PikaDrawable *drawable;
+ gchar *title;
+ gchar *label;
+ gchar *callback;
+
+ GBytes *thumbnail;
+ PikaDrawable *thumbnail_drawable;
+ gint width;
+ gint height;
+ gint bpp;
+
+ GtkWidget *label_widget;
+ GtkWidget *preview_frame;
+ GtkWidget *preview;
+ GtkWidget *preview_title;
+};
+
+
+/* local function prototypes */
+
+static void pika_drawable_chooser_constructed (GObject *object);
+static void pika_drawable_chooser_dispose (GObject *object);
+static void pika_drawable_chooser_finalize (GObject *object);
+
+static void pika_drawable_chooser_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void pika_drawable_chooser_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void pika_drawable_chooser_clicked (PikaDrawableChooser *chooser);
+
+static PikaValueArray * pika_temp_callback_run (PikaProcedure *procedure,
+ PikaProcedureConfig *config,
+ PikaDrawableChooser *chooser);
+static gboolean pika_drawable_select_remove_after_run (const gchar *procedure_name);
+
+static void pika_drawable_chooser_draw (PikaDrawableChooser *chooser);
+static void pika_drawable_chooser_get_thumbnail (PikaDrawableChooser *chooser,
+ gint width,
+ gint height);
+
+
+static GParamSpec *drawable_button_props[N_PROPS] = { NULL, };
+
+G_DEFINE_FINAL_TYPE (PikaDrawableChooser, pika_drawable_chooser, GTK_TYPE_BOX)
+
+
+static void
+pika_drawable_chooser_class_init (PikaDrawableChooserClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = pika_drawable_chooser_constructed;
+ object_class->dispose = pika_drawable_chooser_dispose;
+ object_class->finalize = pika_drawable_chooser_finalize;
+ object_class->set_property = pika_drawable_chooser_set_property;
+ object_class->get_property = pika_drawable_chooser_get_property;
+
+ /**
+ * PikaDrawableChooser:title:
+ *
+ * The title to be used for the drawable selection popup dialog.
+ *
+ * Since: 3.0
+ */
+ drawable_button_props[PROP_TITLE] =
+ g_param_spec_string ("title",
+ "Title",
+ "The title to be used for the drawable selection popup dialog",
+ "Drawable Selection",
+ PIKA_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY);
+
+ /**
+ * PikaDrawableChooser:label:
+ *
+ * Label text with mnemonic.
+ *
+ * Since: 3.0
+ */
+ drawable_button_props[PROP_LABEL] =
+ g_param_spec_string ("label",
+ "Label",
+ "The label to be used next to the button",
+ NULL,
+ PIKA_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY);
+
+ /**
+ * PikaDrawableChooser:drawable:
+ *
+ * The currently selected drawable.
+ *
+ * Since: 3.0
+ */
+ drawable_button_props[PROP_DRAWABLE] =
+ pika_param_spec_drawable ("drawable",
+ "Drawable",
+ "The currently selected drawable",
+ TRUE,
+ PIKA_PARAM_READWRITE |
+ G_PARAM_EXPLICIT_NOTIFY);
+
+ /**
+ * PikaDrawableChooser:drawable-type:
+ *
+ * Allowed drawable types, which must be either PIKA_TYPE_DRAWABLE or a
+ * subtype.
+ *
+ * Since: 3.0
+ */
+ drawable_button_props[PROP_DRAWABLE_TYPE] =
+ g_param_spec_gtype ("drawable-type",
+ "Allowed drawable Type",
+ "The GType of the drawable property",
+ PIKA_TYPE_DRAWABLE,
+ G_PARAM_CONSTRUCT_ONLY |
+ PIKA_PARAM_READWRITE);
+
+ g_object_class_install_properties (object_class,
+ N_PROPS, drawable_button_props);
+}
+
+static void
+pika_drawable_chooser_init (PikaDrawableChooser *chooser)
+{
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (chooser),
+ GTK_ORIENTATION_HORIZONTAL);
+ gtk_box_set_spacing (GTK_BOX (chooser), 6);
+
+ chooser->thumbnail_drawable = NULL;
+ chooser->thumbnail = NULL;
+}
+
+static void
+pika_drawable_chooser_constructed (GObject *object)
+{
+ PikaDrawableChooser *chooser = PIKA_DRAWABLE_CHOOSER (object);
+ GtkWidget *button;
+ GtkWidget *box;
+ gint scale_factor;
+
+ scale_factor = gtk_widget_get_scale_factor (GTK_WIDGET (chooser));
+
+ chooser->label_widget = gtk_label_new (NULL);
+ gtk_box_pack_start (GTK_BOX (chooser), chooser->label_widget, FALSE, FALSE, 0);
+ gtk_label_set_text_with_mnemonic (GTK_LABEL (chooser->label_widget), chooser->label);
+ gtk_widget_show (GTK_WIDGET (chooser->label_widget));
+
+ button = gtk_button_new ();
+ gtk_box_pack_start (GTK_BOX (chooser), button, FALSE, FALSE, 0);
+ gtk_label_set_mnemonic_widget (GTK_LABEL (chooser->label_widget), button);
+ gtk_widget_show (button);
+
+ box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
+ gtk_container_add (GTK_CONTAINER (button), box);
+ gtk_widget_show (box);
+
+ chooser->preview_frame = gtk_aspect_frame_new (NULL, 0.5, 0.5, 1.0, FALSE);
+ gtk_frame_set_shadow_type (GTK_FRAME (chooser->preview_frame), GTK_SHADOW_IN);
+ gtk_box_pack_start (GTK_BOX (box), chooser->preview_frame, FALSE, FALSE, 0);
+ gtk_widget_show (chooser->preview_frame);
+
+ chooser->preview = pika_preview_area_new ();
+ gtk_widget_set_size_request (chooser->preview, scale_factor * CELL_SIZE, scale_factor * CELL_SIZE);
+ gtk_container_add (GTK_CONTAINER (chooser->preview_frame), chooser->preview);
+ gtk_widget_show (chooser->preview);
+
+ chooser->preview_title = gtk_label_new (_("Browse..."));
+ gtk_box_pack_start (GTK_BOX (box), chooser->preview_title, FALSE, FALSE, 0);
+ gtk_widget_show (chooser->preview_title);
+
+ g_signal_connect_swapped (button, "clicked",
+ G_CALLBACK (pika_drawable_chooser_clicked),
+ chooser);
+
+ G_OBJECT_CLASS (pika_drawable_chooser_parent_class)->constructed (object);
+}
+
+static void
+pika_drawable_chooser_dispose (GObject *object)
+{
+ PikaDrawableChooser *chooser = PIKA_DRAWABLE_CHOOSER (object);
+
+ if (chooser->callback)
+ {
+ pika_drawables_close_popup (chooser->callback);
+
+ pika_plug_in_remove_temp_procedure (pika_get_plug_in (), chooser->callback);
+ g_clear_pointer (&chooser->callback, g_free);
+ }
+
+ G_OBJECT_CLASS (pika_drawable_chooser_parent_class)->dispose (object);
+}
+
+static void
+pika_drawable_chooser_finalize (GObject *object)
+{
+ PikaDrawableChooser *chooser = PIKA_DRAWABLE_CHOOSER (object);
+
+ g_clear_pointer (&chooser->title, g_free);
+ g_clear_pointer (&chooser->label, g_free);
+ g_clear_pointer (&chooser->thumbnail, g_bytes_unref);
+
+ G_OBJECT_CLASS (pika_drawable_chooser_parent_class)->finalize (object);
+}
+
+static void
+pika_drawable_chooser_set_property (GObject *object,
+ guint property_id,
+ const GValue *gvalue,
+ GParamSpec *pspec)
+{
+ PikaDrawableChooser *chooser = PIKA_DRAWABLE_CHOOSER (object);
+
+ switch (property_id)
+ {
+ case PROP_TITLE:
+ chooser->title = g_value_dup_string (gvalue);
+ break;
+
+ case PROP_LABEL:
+ chooser->label = g_value_dup_string (gvalue);
+ break;
+
+ case PROP_DRAWABLE:
+ g_return_if_fail (g_value_get_object (gvalue) == NULL ||
+ g_type_is_a (G_TYPE_FROM_INSTANCE (g_value_get_object (gvalue)),
+ chooser->drawable_type));
+ pika_drawable_chooser_set_drawable (chooser, g_value_get_object (gvalue));
+ break;
+
+ case PROP_DRAWABLE_TYPE:
+ chooser->drawable_type = g_value_get_gtype (gvalue);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+pika_drawable_chooser_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PikaDrawableChooser *chooser = PIKA_DRAWABLE_CHOOSER (object);
+
+ switch (property_id)
+ {
+ case PROP_TITLE:
+ g_value_set_string (value, chooser->title);
+ break;
+
+ case PROP_LABEL:
+ g_value_set_string (value, chooser->label);
+ break;
+
+ case PROP_DRAWABLE:
+ g_value_set_object (value, chooser->drawable);
+ break;
+
+ case PROP_DRAWABLE_TYPE:
+ g_value_set_gtype (value, chooser->drawable_type);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+/**
+ * pika_drawable_chooser_new:
+ * @title: (nullable): Title of the dialog to use or %NULL to use the default title.
+ * @label: (nullable): Button label or %NULL for no label.
+ * @drawable_type: the acceptable subtype of choosable drawables.
+ * @drawable: (nullable): Initial drawable.
+ *
+ * Creates a new #GtkWidget that lets a user choose a drawable which must be of
+ * type @drawable_type. @drawable_type of values %G_TYPE_NONE and
+ * %PIKA_TYPE_DRAWABLE are equivalent. Otherwise it must be a subtype of
+ * %PIKA_TYPE_DRAWABLE.
+ *
+ * When @drawable is %NULL, initial choice is from context.
+ *
+ * Returns: A [class@GimpUi.DrawableChooser.
+ *
+ * Since: 3.0
+ */
+GtkWidget *
+pika_drawable_chooser_new (const gchar *title,
+ const gchar *label,
+ GType drawable_type,
+ PikaDrawable *drawable)
+{
+ GtkWidget *chooser;
+
+ if (drawable_type == G_TYPE_NONE)
+ drawable_type = PIKA_TYPE_DRAWABLE;
+
+ g_return_val_if_fail (g_type_is_a (drawable_type, PIKA_TYPE_DRAWABLE), NULL);
+ g_return_val_if_fail (drawable == NULL ||
+ g_type_is_a (G_TYPE_FROM_INSTANCE (drawable), drawable_type),
+ NULL);
+
+ chooser = g_object_new (PIKA_TYPE_DRAWABLE_CHOOSER,
+ "title", title,
+ "label", label,
+ "drawable", drawable,
+ "drawable-type", drawable_type,
+ NULL);
+
+ return chooser;
+}
+
+/**
+ * pika_drawable_chooser_get_drawable:
+ * @chooser: A #PikaDrawableChooser
+ *
+ * Gets the currently selected drawable.
+ *
+ * Returns: (transfer none): an internal copy of the drawable which must not be freed.
+ *
+ * Since: 3.0
+ */
+PikaDrawable *
+pika_drawable_chooser_get_drawable (PikaDrawableChooser *chooser)
+{
+ g_return_val_if_fail (PIKA_IS_DRAWABLE_CHOOSER (chooser), NULL);
+
+ return chooser->drawable;
+}
+
+/**
+ * pika_drawable_chooser_set_drawable:
+ * @chooser: A #PikaDrawableChooser
+ * @drawable: Drawable to set.
+ *
+ * Sets the currently selected drawable.
+ * This will select the drawable in both the button and any chooser popup.
+ *
+ * Since: 3.0
+ */
+void
+pika_drawable_chooser_set_drawable (PikaDrawableChooser *chooser,
+ PikaDrawable *drawable)
+{
+ g_return_if_fail (PIKA_IS_DRAWABLE_CHOOSER (chooser));
+ g_return_if_fail (drawable == NULL || PIKA_IS_DRAWABLE (drawable));
+
+ chooser->drawable = drawable;
+
+ if (chooser->callback)
+ pika_drawables_set_popup (chooser->callback, chooser->drawable);
+
+ g_object_notify_by_pspec (G_OBJECT (chooser), drawable_button_props[PROP_DRAWABLE]);
+
+ pika_drawable_chooser_draw (chooser);
+}
+
+/**
+ * pika_drawable_chooser_get_label:
+ * @widget: A [class@DrawableChooser].
+ *
+ * Returns the label widget.
+ *
+ * Returns: (transfer none): the [class@Gtk.Widget] showing the label text.
+ * Since: 3.0
+ */
+GtkWidget *
+pika_drawable_chooser_get_label (PikaDrawableChooser *chooser)
+{
+ g_return_val_if_fail (PIKA_IS_DRAWABLE_CHOOSER (chooser), NULL);
+
+ return chooser->label_widget;
+}
+
+
+/* private functions */
+
+static PikaValueArray *
+pika_temp_callback_run (PikaProcedure *procedure,
+ PikaProcedureConfig *config,
+ PikaDrawableChooser *chooser)
+{
+ PikaDrawable *drawable;
+ gboolean closing;
+
+ g_object_get (config,
+ "drawable", &drawable,
+ "closing", &closing,
+ NULL);
+
+ g_object_set (chooser, "drawable", drawable, NULL);
+
+ if (closing)
+ {
+ g_idle_add_full (G_PRIORITY_DEFAULT_IDLE,
+ (GSourceFunc) pika_drawable_select_remove_after_run,
+ g_strdup (pika_procedure_get_name (procedure)),
+ g_free);
+ g_clear_pointer (&chooser->callback, g_free);
+ }
+
+ g_clear_object (&drawable);
+
+ return pika_procedure_new_return_values (procedure, PIKA_PDB_SUCCESS, NULL);
+}
+
+static gboolean
+pika_drawable_select_remove_after_run (const gchar *procedure_name)
+{
+ pika_plug_in_remove_temp_procedure (pika_get_plug_in (), procedure_name);
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+pika_drawable_chooser_clicked (PikaDrawableChooser *chooser)
+{
+ if (chooser->callback)
+ {
+ /* Popup already created. Calling setter raises the popup. */
+ pika_drawables_set_popup (chooser->callback, chooser->drawable);
+ }
+ else
+ {
+ PikaPlugIn *plug_in = pika_get_plug_in ();
+ GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (chooser));
+ GBytes *handle = NULL;
+ gchar *callback_name;
+ PikaProcedure *callback_procedure;
+
+ if (PIKA_IS_DIALOG (toplevel))
+ handle = pika_dialog_get_native_handle (PIKA_DIALOG (toplevel));
+
+ callback_name = pika_pdb_temp_procedure_name (pika_get_pdb ());
+ callback_procedure = pika_procedure_new (plug_in,
+ callback_name,
+ PIKA_PDB_PROC_TYPE_TEMPORARY,
+ (PikaRunFunc) pika_temp_callback_run,
+ g_object_ref (chooser),
+ (GDestroyNotify) g_object_unref);
+ PIKA_PROC_ARG_DRAWABLE (callback_procedure, "drawable",
+ "Drawable", "The selected drawable",
+ TRUE, G_PARAM_READWRITE);
+ PIKA_PROC_ARG_BOOLEAN (callback_procedure, "closing",
+ "Closing", "If the dialog was closing",
+ FALSE, G_PARAM_READWRITE);
+
+ pika_plug_in_add_temp_procedure (plug_in, callback_procedure);
+ g_object_unref (callback_procedure);
+ g_free (callback_name);
+
+ if (pika_drawables_popup (pika_procedure_get_name (callback_procedure), chooser->title,
+ g_type_name (chooser->drawable_type), chooser->drawable, handle))
+ {
+ /* Allow callbacks to be watched */
+ pika_plug_in_extension_enable (plug_in);
+
+ chooser->callback = g_strdup (pika_procedure_get_name (callback_procedure));
+ }
+ else
+ {
+ g_warning ("%s: failed to open remote drawable select dialog.", G_STRFUNC);
+ pika_plug_in_remove_temp_procedure (plug_in, pika_procedure_get_name (callback_procedure));
+ return;
+ }
+ pika_drawables_set_popup (chooser->callback, chooser->drawable);
+ }
+}
+
+static void
+pika_drawable_chooser_draw (PikaDrawableChooser *chooser)
+{
+ PikaPreviewArea *area = PIKA_PREVIEW_AREA (chooser->preview);
+ GtkAllocation allocation;
+ gint x = 0;
+ gint y = 0;
+
+ gtk_widget_get_allocation (chooser->preview, &allocation);
+
+ pika_drawable_chooser_get_thumbnail (chooser, allocation.width, allocation.height);
+
+ if (chooser->width < allocation.width ||
+ chooser->height < allocation.height)
+ {
+ x = ((allocation.width - chooser->width) / 2);
+ y = ((allocation.height - chooser->height) / 2);
+ }
+ pika_preview_area_reset (area);
+
+ if (chooser->thumbnail)
+ {
+ PikaImageType type;
+ gint rowstride;
+
+ rowstride = chooser->width * chooser->bpp;
+ switch (chooser->bpp)
+ {
+ case 1:
+ type = PIKA_GRAY_IMAGE;
+ break;
+ case 2:
+ type = PIKA_GRAYA_IMAGE;
+ break;
+ case 3:
+ type = PIKA_RGB_IMAGE;
+ break;
+ case 4:
+ type = PIKA_RGBA_IMAGE;
+ break;
+ default:
+ g_return_if_reached ();
+ }
+
+ pika_preview_area_draw (area, x, y, chooser->width, chooser->height, type,
+ g_bytes_get_data (chooser->thumbnail, NULL), rowstride);
+ }
+}
+
+static void
+pika_drawable_chooser_get_thumbnail (PikaDrawableChooser *chooser,
+ gint width,
+ gint height)
+{
+ if (chooser->drawable == chooser->thumbnail_drawable &&
+ chooser->width == width &&
+ chooser->height == height)
+ /* Let's assume drawable contents is not changing in a single run. */
+ return;
+
+ g_clear_pointer (&chooser->thumbnail, g_bytes_unref);
+ chooser->width = chooser->height = 0;
+
+ chooser->thumbnail_drawable = chooser->drawable;
+ if (chooser->drawable)
+ chooser->thumbnail = pika_drawable_get_thumbnail_data (chooser->drawable,
+ width, height,
+ &chooser->width,
+ &chooser->height,
+ &chooser->bpp);
+}
diff --git a/libpika/pikadrawablechooser.h b/libpika/pikadrawablechooser.h
new file mode 100644
index 0000000..96feaf4
--- /dev/null
+++ b/libpika/pikadrawablechooser.h
@@ -0,0 +1,48 @@
+/* LIBPIKA - The PIKA Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * pikadrawablechooser.c
+ * Copyright (C) 2023 Jehan
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * 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 (__PIKA_UI_H_INSIDE__) && !defined (PIKA_COMPILATION)
+#error "Only can be included directly."
+#endif
+
+#ifndef __PIKA_DRAWABLE_CHOOSER_H__
+#define __PIKA_DRAWABLE_CHOOSER_H__
+
+G_BEGIN_DECLS
+
+#define PIKA_TYPE_DRAWABLE_CHOOSER (pika_drawable_chooser_get_type ())
+G_DECLARE_FINAL_TYPE (PikaDrawableChooser, pika_drawable_chooser, PIKA, DRAWABLE_CHOOSER, GtkBox)
+
+
+GtkWidget * pika_drawable_chooser_new (const gchar *title,
+ const gchar *label,
+ GType drawable_type,
+ PikaDrawable *drawable);
+
+PikaDrawable * pika_drawable_chooser_get_drawable (PikaDrawableChooser *chooser);
+void pika_drawable_chooser_set_drawable (PikaDrawableChooser *chooser,
+ PikaDrawable *drawable);
+GtkWidget * pika_drawable_chooser_get_label (PikaDrawableChooser *widget);
+
+
+G_END_DECLS
+
+#endif /* __PIKA_DRAWABLE_CHOOSER_H__ */
diff --git a/libpika/pikadrawableselect_pdb.c b/libpika/pikadrawableselect_pdb.c
new file mode 100644
index 0000000..1977969
--- /dev/null
+++ b/libpika/pikadrawableselect_pdb.c
@@ -0,0 +1,151 @@
+/* LIBPIKA - The PIKA Library
+ * Copyright (C) 1995-2003 Peter Mattis and Spencer Kimball
+ *
+ * pikadrawableselect_pdb.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
+ * .
+ */
+
+/* NOTE: This file is auto-generated by pdbgen.pl */
+
+#include "config.h"
+
+#include "stamp-pdbgen.h"
+
+#include "pika.h"
+
+
+/**
+ * SECTION: pikadrawableselect
+ * @title: pikadrawableselect
+ * @short_description: Methods of a drawable chooser dialog
+ *
+
+ **/
+
+
+/**
+ * pika_drawables_popup:
+ * @callback: The callback PDB proc to call when user chooses an drawable.
+ * @popup_title: Title of the drawable selection dialog.
+ * @drawable_type: The name of the PIKA_TYPE_DRAWABLE subtype.
+ * @initial_drawable: The drawable to set as the initial choice.
+ * @parent_window: An optional parent window handle for the popup to be set transient to.
+ *
+ * Invokes the drawable selection dialog.
+ *
+ * Opens a dialog letting a user choose an drawable.
+ *
+ * Returns: TRUE on success.
+ **/
+gboolean
+pika_drawables_popup (const gchar *callback,
+ const gchar *popup_title,
+ const gchar *drawable_type,
+ PikaDrawable *initial_drawable,
+ GBytes *parent_window)
+{
+ PikaValueArray *args;
+ PikaValueArray *return_vals;
+ gboolean success = TRUE;
+
+ args = pika_value_array_new_from_types (NULL,
+ G_TYPE_STRING, callback,
+ G_TYPE_STRING, popup_title,
+ G_TYPE_STRING, drawable_type,
+ PIKA_TYPE_DRAWABLE, initial_drawable,
+ G_TYPE_BYTES, parent_window,
+ G_TYPE_NONE);
+
+ return_vals = _pika_pdb_run_procedure_array (pika_get_pdb (),
+ "pika-drawables-popup",
+ args);
+ pika_value_array_unref (args);
+
+ success = PIKA_VALUES_GET_ENUM (return_vals, 0) == PIKA_PDB_SUCCESS;
+
+ pika_value_array_unref (return_vals);
+
+ return success;
+}
+
+/**
+ * pika_drawables_close_popup:
+ * @callback: The name of the callback registered for this pop-up.
+ *
+ * Close the drawable selection dialog.
+ *
+ * Closes an open drawable selection dialog.
+ *
+ * Returns: TRUE on success.
+ **/
+gboolean
+pika_drawables_close_popup (const gchar *callback)
+{
+ PikaValueArray *args;
+ PikaValueArray *return_vals;
+ gboolean success = TRUE;
+
+ args = pika_value_array_new_from_types (NULL,
+ G_TYPE_STRING, callback,
+ G_TYPE_NONE);
+
+ return_vals = _pika_pdb_run_procedure_array (pika_get_pdb (),
+ "pika-drawables-close-popup",
+ args);
+ pika_value_array_unref (args);
+
+ success = PIKA_VALUES_GET_ENUM (return_vals, 0) == PIKA_PDB_SUCCESS;
+
+ pika_value_array_unref (return_vals);
+
+ return success;
+}
+
+/**
+ * pika_drawables_set_popup:
+ * @callback: The name of the callback registered for this pop-up.
+ * @drawable: The drawable to set as selected.
+ *
+ * Sets the selected drawable in a drawable selection dialog.
+ *
+ * Sets the selected drawable in a drawable selection dialog.
+ *
+ * Returns: TRUE on success.
+ **/
+gboolean
+pika_drawables_set_popup (const gchar *callback,
+ PikaDrawable *drawable)
+{
+ PikaValueArray *args;
+ PikaValueArray *return_vals;
+ gboolean success = TRUE;
+
+ args = pika_value_array_new_from_types (NULL,
+ G_TYPE_STRING, callback,
+ PIKA_TYPE_DRAWABLE, drawable,
+ G_TYPE_NONE);
+
+ return_vals = _pika_pdb_run_procedure_array (pika_get_pdb (),
+ "pika-drawables-set-popup",
+ args);
+ pika_value_array_unref (args);
+
+ success = PIKA_VALUES_GET_ENUM (return_vals, 0) == PIKA_PDB_SUCCESS;
+
+ pika_value_array_unref (return_vals);
+
+ return success;
+}
diff --git a/libpika/pikadrawableselect_pdb.h b/libpika/pikadrawableselect_pdb.h
new file mode 100644
index 0000000..9fba8ea
--- /dev/null
+++ b/libpika/pikadrawableselect_pdb.h
@@ -0,0 +1,47 @@
+/* LIBPIKA - The PIKA Library
+ * Copyright (C) 1995-2003 Peter Mattis and Spencer Kimball
+ *
+ * pikadrawableselect_pdb.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
+ * .
+ */
+
+/* NOTE: This file is auto-generated by pdbgen.pl */
+
+#if !defined (__PIKA_H_INSIDE__) && !defined (PIKA_COMPILATION)
+#error "Only can be included directly."
+#endif
+
+#ifndef __PIKA_DRAWABLE_SELECT_PDB_H__
+#define __PIKA_DRAWABLE_SELECT_PDB_H__
+
+G_BEGIN_DECLS
+
+/* For information look into the C source or the html documentation */
+
+
+gboolean pika_drawables_popup (const gchar *callback,
+ const gchar *popup_title,
+ const gchar *drawable_type,
+ PikaDrawable *initial_drawable,
+ GBytes *parent_window);
+gboolean pika_drawables_close_popup (const gchar *callback);
+gboolean pika_drawables_set_popup (const gchar *callback,
+ PikaDrawable *drawable);
+
+
+G_END_DECLS
+
+#endif /* __PIKA_DRAWABLE_SELECT_PDB_H__ */
diff --git a/libpika/pikafont.c b/libpika/pikafont.c
new file mode 100644
index 0000000..214268a
--- /dev/null
+++ b/libpika/pikafont.c
@@ -0,0 +1,86 @@
+/* LIBPIKA - The PIKA Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * pikafont.c
+ * Copyright (C) 2023 Jehan
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * 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
+ * .
+ */
+
+#include "config.h"
+
+#include
+
+#include "pika.h"
+
+#include "pikafont.h"
+
+struct _PikaFont
+{
+ PikaResource parent_instance;
+};
+
+G_DEFINE_TYPE (PikaFont, pika_font, PIKA_TYPE_RESOURCE);
+
+
+static void pika_font_class_init (PikaFontClass *klass)
+{
+}
+
+static void pika_font_init (PikaFont *font)
+{
+}
+
+/**
+ * pika_font_get_pango_font_description:
+ * @font: (transfer none): the [class@Gimp.Font]
+ *
+ * Returns a [class@Pango.Font.Description] representing @font.
+ *
+ * Returns: (transfer full): a %PangoFontDescription representing @font.
+ *
+ * Since: 3.0
+ **/
+PangoFontDescription *
+pika_font_get_pango_font_description (PikaFont *font)
+{
+ PangoFontDescription *desc = NULL;
+ gchar *name = NULL;
+ gchar *collection_id = NULL;
+ gboolean is_internal;
+
+ is_internal = _pika_resource_get_identifiers (PIKA_RESOURCE (font),
+ &name, &collection_id);
+ /* TODO: we can't create fonts from internal fonts right now, but it should
+ * actually be possible because these are in fact alias to non-internal fonts.
+ * See #9985.
+ */
+ if (! is_internal)
+ {
+ gchar *expanded_path;
+
+ expanded_path = pika_config_path_expand (collection_id, FALSE, NULL);
+ if (expanded_path != NULL &&
+ FcConfigAppFontAddFile (FcConfigGetCurrent (), (const FcChar8 *) expanded_path))
+ desc = pango_font_description_from_string (name);
+
+ g_free (expanded_path);
+ }
+
+ g_free (name);
+ g_free (collection_id);
+
+ return desc;
+}
diff --git a/libpika/pikafont.h b/libpika/pikafont.h
new file mode 100644
index 0000000..d3f78b3
--- /dev/null
+++ b/libpika/pikafont.h
@@ -0,0 +1,46 @@
+/* LIBPIKA - The PIKA Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * pikafont.h
+ * Copyright (C) 2023 Jehan
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * 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 (__PIKA_H_INSIDE__) && !defined (PIKA_COMPILATION)
+#error "Only can be included directly."
+#endif
+
+#ifndef __PIKA_FONT_H__
+#define __PIKA_FONT_H__
+
+G_BEGIN_DECLS
+
+/* For information look into the C source or the html documentation */
+
+
+#include
+
+
+#define PIKA_TYPE_FONT (pika_font_get_type ())
+G_DECLARE_FINAL_TYPE (PikaFont, pika_font, PIKA, FONT, PikaResource)
+
+
+PangoFontDescription * pika_font_get_pango_font_description (PikaFont *font);
+
+
+G_END_DECLS
+
+#endif /* __PIKA_FONT_H__ */
diff --git a/libpika/pikafontchooser.c b/libpika/pikafontchooser.c
new file mode 100644
index 0000000..0886520
--- /dev/null
+++ b/libpika/pikafontchooser.c
@@ -0,0 +1,158 @@
+/* LIBPIKA - The PIKA Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * pikafontchooser.c
+ * Copyright (C) 2003 Sven Neumann
+ *
+ * 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 "libpikawidgets/pikawidgets.h"
+
+#include "pika.h"
+
+#include "pikauitypes.h"
+#include "pikafontchooser.h"
+#include "pikauimarshal.h"
+
+#include "libpika-intl.h"
+
+
+/**
+ * SECTION: pikafontchooser
+ * @title: PikaFontChooser
+ * @short_description: A button which pops up a font selection dialog.
+ *
+ * A button which pops up a font selection dialog.
+ **/
+
+struct _PikaFontChooser
+{
+ PikaResourceChooser parent_instance;
+
+ GtkWidget *label;
+};
+
+
+static void pika_font_chooser_draw_interior (PikaResourceChooser *self);
+
+
+static const GtkTargetEntry drag_target = { "application/x-pika-font-name", 0, 0 };
+
+
+G_DEFINE_FINAL_TYPE (PikaFontChooser, pika_font_chooser, PIKA_TYPE_RESOURCE_CHOOSER)
+
+
+static void
+pika_font_chooser_class_init (PikaFontChooserClass *klass)
+{
+ PikaResourceChooserClass *superclass = PIKA_RESOURCE_CHOOSER_CLASS (klass);
+
+ superclass->draw_interior = pika_font_chooser_draw_interior;
+ superclass->resource_type = PIKA_TYPE_FONT;
+}
+
+static void
+pika_font_chooser_init (PikaFontChooser *self)
+{
+ GtkWidget *button;
+ GtkWidget *hbox;
+ GtkWidget *image;
+
+ button = gtk_button_new ();
+ gtk_container_add (GTK_CONTAINER (self), button);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
+ gtk_container_add (GTK_CONTAINER (button), hbox);
+
+ image = gtk_image_new_from_icon_name (PIKA_ICON_FONT,
+ GTK_ICON_SIZE_BUTTON);
+ gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
+
+ self->label = gtk_label_new ("unknown");
+ gtk_box_pack_start (GTK_BOX (hbox), self->label, TRUE, TRUE, 4);
+
+ gtk_widget_show_all (GTK_WIDGET (self));
+
+ pika_resource_chooser_set_drag_target (PIKA_RESOURCE_CHOOSER (self),
+ hbox, &drag_target);
+
+ pika_resource_chooser_set_clickable (PIKA_RESOURCE_CHOOSER (self), button);
+}
+
+static void
+pika_font_chooser_draw_interior (PikaResourceChooser *self)
+{
+ PikaFontChooser *font_select= PIKA_FONT_CHOOSER (self);
+ PikaResource *resource;
+ gchar *name = NULL;
+
+ resource = pika_resource_chooser_get_resource (self);
+
+ if (resource)
+ name = pika_resource_get_name (resource);
+
+ gtk_label_set_text (GTK_LABEL (font_select->label), name);
+}
+
+
+/**
+ * pika_font_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.
+ * @resource: (nullable): Initial font.
+ *
+ * Creates a new #GtkWidget that lets a user choose a font.
+ * You can put this widget in a plug-in dialog.
+ *
+ * When font is NULL, initial choice is from context.
+ *
+ * Returns: A #GtkWidget that you can use in your UI.
+ *
+ * Since: 2.4
+ */
+GtkWidget *
+pika_font_chooser_new (const gchar *title,
+ const gchar *label,
+ PikaResource *resource)
+{
+ GtkWidget *chooser;
+
+ g_return_val_if_fail (resource == NULL || PIKA_IS_FONT (resource), NULL);
+
+ if (resource == NULL)
+ resource = PIKA_RESOURCE (pika_context_get_font ());
+
+ if (title)
+ chooser = g_object_new (PIKA_TYPE_FONT_CHOOSER,
+ "title", title,
+ "label", label,
+ "resource", resource,
+ NULL);
+ else
+ chooser = g_object_new (PIKA_TYPE_FONT_CHOOSER,
+ "label", label,
+ "resource", resource,
+ NULL);
+
+ pika_font_chooser_draw_interior (PIKA_RESOURCE_CHOOSER (chooser));
+
+ return chooser;
+}
diff --git a/libpika/pikafontchooser.h b/libpika/pikafontchooser.h
new file mode 100644
index 0000000..abe485c
--- /dev/null
+++ b/libpika/pikafontchooser.h
@@ -0,0 +1,43 @@
+/* LIBPIKA - The PIKA Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * pikafontchooser.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 (__PIKA_UI_H_INSIDE__) && !defined (PIKA_COMPILATION)
+#error "Only can be included directly."
+#endif
+
+#ifndef __PIKA_FONT_CHOOSER_H__
+#define __PIKA_FONT_CHOOSER_H__
+
+#include
+
+G_BEGIN_DECLS
+
+#define PIKA_TYPE_FONT_CHOOSER (pika_font_chooser_get_type ())
+G_DECLARE_FINAL_TYPE (PikaFontChooser, pika_font_chooser, PIKA, FONT_CHOOSER, PikaResourceChooser)
+
+
+GtkWidget * pika_font_chooser_new (const gchar *title,
+ const gchar *label,
+ PikaResource *resource);
+
+
+G_END_DECLS
+
+#endif /* __PIKA_FONT_CHOOSER_H__ */
diff --git a/libpika/pikagradient.c b/libpika/pikagradient.c
new file mode 100644
index 0000000..c6c1820
--- /dev/null
+++ b/libpika/pikagradient.c
@@ -0,0 +1,42 @@
+/* LIBPIKA - The PIKA Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * pikagradient.c
+ * Copyright (C) 2023 Jehan
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * 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
+ * .
+ */
+
+#include "config.h"
+
+#include "pika.h"
+
+#include "pikagradient.h"
+
+struct _PikaGradient
+{
+ PikaResource parent_instance;
+};
+
+G_DEFINE_TYPE (PikaGradient, pika_gradient, PIKA_TYPE_RESOURCE);
+
+
+static void pika_gradient_class_init (PikaGradientClass *klass)
+{
+}
+
+static void pika_gradient_init (PikaGradient *gradient)
+{
+}
diff --git a/libpika/pikagradient.h b/libpika/pikagradient.h
new file mode 100644
index 0000000..b538fd1
--- /dev/null
+++ b/libpika/pikagradient.h
@@ -0,0 +1,43 @@
+/* LIBPIKA - The PIKA Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * pikagradient.h
+ * Copyright (C) 2023 Jehan
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * 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 (__PIKA_H_INSIDE__) && !defined (PIKA_COMPILATION)
+#error "Only can be included directly."
+#endif
+
+#ifndef __PIKA_GRADIENT_H__
+#define __PIKA_GRADIENT_H__
+
+G_BEGIN_DECLS
+
+/* For information look into the C source or the html documentation */
+
+
+#include
+
+
+#define PIKA_TYPE_GRADIENT (pika_gradient_get_type ())
+G_DECLARE_FINAL_TYPE (PikaGradient, pika_gradient, PIKA, GRADIENT, PikaResource)
+
+
+G_END_DECLS
+
+#endif /* __PIKA_GRADIENT_H__ */
diff --git a/libpika/pikagradientchooser.c b/libpika/pikagradientchooser.c
new file mode 100644
index 0000000..e816ff5
--- /dev/null
+++ b/libpika/pikagradientchooser.c
@@ -0,0 +1,303 @@
+/* LIBPIKA - The PIKA Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * pikagradientchooser.c
+ * Copyright (C) 1998 Andy Thomas
+ *
+ * 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 "libpikawidgets/pikawidgets.h"
+
+#include "pika.h"
+
+#include "pikauitypes.h"
+#include "pikagradientchooser.h"
+#include "pikauimarshal.h"
+
+#include "libpika-intl.h"
+
+
+/**
+ * SECTION: pikagradientchooser
+ * @title: PikaGradientChooser
+ * @short_description: A button which pops up a gradient select dialog.
+ *
+ * A button which pops up a gradient select dialog.
+ **/
+
+
+struct _PikaGradientChooser
+{
+ PikaResourceChooser parent_instance;
+
+ GtkWidget *preview;
+};
+
+
+/* local function prototypes */
+
+static void pika_gradient_chooser_draw_interior (PikaResourceChooser *self);
+
+static void pika_gradient_select_preview_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation,
+ PikaGradientChooser *self);
+static gboolean pika_gradient_select_preview_draw_handler (GtkWidget *preview,
+ cairo_t *cr,
+ PikaGradientChooser *self);
+
+
+static const GtkTargetEntry drag_target = { "application/x-pika-gradient-name", 0 };
+
+G_DEFINE_FINAL_TYPE (PikaGradientChooser,
+ pika_gradient_chooser,
+ PIKA_TYPE_RESOURCE_CHOOSER)
+
+/* Initial dimensions of widget. */
+#define CELL_HEIGHT 18
+#define CELL_WIDTH 84
+
+static void
+pika_gradient_chooser_class_init (PikaGradientChooserClass *klass)
+{
+ PikaResourceChooserClass *superclass = PIKA_RESOURCE_CHOOSER_CLASS (klass);
+
+ superclass->draw_interior = pika_gradient_chooser_draw_interior;
+ superclass->resource_type = PIKA_TYPE_GRADIENT;
+}
+
+static void
+pika_gradient_chooser_init (PikaGradientChooser *self)
+{
+ GtkWidget *button;
+
+ button = gtk_button_new ();
+ gtk_container_add (GTK_CONTAINER (self), button);
+
+ self->preview = gtk_drawing_area_new ();
+ gtk_widget_set_size_request (self->preview, CELL_WIDTH, CELL_HEIGHT);
+ gtk_container_add (GTK_CONTAINER (button), self->preview);
+
+ g_signal_connect (self->preview, "size-allocate",
+ G_CALLBACK (pika_gradient_select_preview_size_allocate),
+ self);
+
+ g_signal_connect (self->preview, "draw",
+ G_CALLBACK (pika_gradient_select_preview_draw_handler),
+ self);
+
+ gtk_widget_show_all (GTK_WIDGET (self));
+
+ pika_resource_chooser_set_drag_target (PIKA_RESOURCE_CHOOSER (self),
+ self->preview, &drag_target);
+
+ pika_resource_chooser_set_clickable (PIKA_RESOURCE_CHOOSER (self), button);
+}
+
+static void
+pika_gradient_chooser_draw_interior (PikaResourceChooser *self)
+{
+ PikaGradientChooser *gradient_select = PIKA_GRADIENT_CHOOSER (self);
+
+ gtk_widget_queue_draw (gradient_select->preview);
+}
+
+
+/**
+ * pika_gradient_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.
+ * @gradient: (nullable): Initial gradient.
+ *
+ * Creates a new #GtkWidget that lets a user choose a gradient.
+ * You can use this widget in a table in a plug-in dialog.
+ *
+ * Returns: A #GtkWidget that you can use in your UI.
+ *
+ * Since: 2.4
+ */
+GtkWidget *
+pika_gradient_chooser_new (const gchar *title,
+ const gchar *label,
+ PikaResource *gradient)
+{
+ GtkWidget *self;
+
+ if (gradient == NULL)
+ gradient = PIKA_RESOURCE (pika_context_get_gradient ());
+
+ if (title)
+ self = g_object_new (PIKA_TYPE_GRADIENT_CHOOSER,
+ "title", title,
+ "label", label,
+ "resource", gradient,
+ NULL);
+ else
+ self = g_object_new (PIKA_TYPE_GRADIENT_CHOOSER,
+ "label", label,
+ "resource", gradient,
+ NULL);
+
+ pika_gradient_chooser_draw_interior (PIKA_RESOURCE_CHOOSER (self));
+
+ return self;
+}
+
+
+/* private functions */
+
+/* Get array of samples from self's gradient.
+ * Return array and size at given handles.
+ * Return success.
+ */
+static gboolean
+get_gradient_data (PikaGradientChooser *self,
+ gint allocation_width,
+ gint *sample_count,
+ gdouble **sample_array)
+{
+ PikaGradient *gradient;
+ gboolean result;
+ gdouble *samples;
+ gint n_samples;
+
+ g_object_get (self, "resource", &gradient, NULL);
+
+ result = pika_gradient_get_uniform_samples (gradient,
+ allocation_width,
+ FALSE, /* not reversed. */
+ &n_samples,
+ &samples);
+
+ if (result)
+ {
+ /* Return array of samples to dereferenced handles. */
+ *sample_array = samples;
+ *sample_count = n_samples;
+ }
+
+ g_object_unref (gradient);
+
+ /* When result is true, caller must free the array. */
+ return result;
+}
+
+
+/* Called on widget resized. */
+static void
+pika_gradient_select_preview_size_allocate (GtkWidget *widget,
+ GtkAllocation *allocation,
+ PikaGradientChooser *self)
+{
+ /* Do nothing.
+ *
+ * In former code, we cached the gradient data in self, on allocate event.
+ * But allocate event always seems to be paired with a draw event,
+ * so there is no point in caching the gradient data.
+ * And caching gradient data is a premature optimization,
+ * without deep knowledge of Gtk and actual performance testing,
+ * you can't know caching helps performance.
+ */
+}
+
+
+
+/* Draw array of samples.
+ * This understands mostly cairo, and little about gradient.
+ */
+static void
+pika_gradient_select_preview_draw (cairo_t *cr,
+ gint src_width,
+ gint dest_width,
+ gdouble *src)
+{
+ cairo_pattern_t *pattern;
+ cairo_surface_t *surface;
+ guchar *dest;
+ gint x;
+
+ pattern = pika_cairo_checkerboard_create (cr, PIKA_CHECK_SIZE_SM, NULL, NULL);
+ cairo_set_source (cr, pattern);
+ cairo_pattern_destroy (pattern);
+
+ cairo_paint (cr);
+
+ surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, src_width, 1);
+
+ for (x = 0, dest = cairo_image_surface_get_data (surface);
+ x < src_width;
+ x++, src += 4, dest += 4)
+ {
+ PikaRGB color;
+ guchar r, g, b, a;
+
+ pika_rgba_set (&color, src[0], src[1], src[2], src[3]);
+ pika_rgba_get_uchar (&color, &r, &g, &b, &a);
+
+ PIKA_CAIRO_ARGB32_SET_PIXEL (dest, r, g, b, a);
+ }
+
+ cairo_surface_mark_dirty (surface);
+
+ pattern = cairo_pattern_create_for_surface (surface);
+ cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REFLECT);
+ cairo_surface_destroy (surface);
+
+ cairo_scale (cr, (gdouble) dest_width / (gdouble) src_width, 1.0);
+
+ cairo_set_source (cr, pattern);
+ cairo_pattern_destroy (pattern);
+
+ cairo_paint (cr);
+}
+
+/* Handles a draw signal.
+ * Draw self, i.e. interior of button.
+ *
+ * Always returns FALSE, but doesn't draw when fail to get gradient data.
+ *
+ * Is passed neither gradient nor attributes of gradient: get them now from self.
+ */
+static gboolean
+pika_gradient_select_preview_draw_handler (GtkWidget *widget,
+ cairo_t *cr,
+ PikaGradientChooser *self)
+{
+ GtkAllocation allocation;
+
+ /* Attributes of the source.*/
+ gdouble *src;
+ gint n_samples;
+ gint src_width;
+
+ gtk_widget_get_allocation (widget, &allocation);
+
+ if (!get_gradient_data (self, allocation.width, &n_samples, &src))
+ return FALSE;
+
+ /* Width in pixels of src, since BPP is 4. */
+ src_width = n_samples / 4;
+
+ pika_gradient_select_preview_draw (cr, src_width, allocation.width, src);
+
+ g_free (src);
+
+ return FALSE;
+}
diff --git a/libpika/pikagradientchooser.h b/libpika/pikagradientchooser.h
new file mode 100644
index 0000000..c868375
--- /dev/null
+++ b/libpika/pikagradientchooser.h
@@ -0,0 +1,43 @@
+/* LIBPIKA - The PIKA Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * pikagradientchooser.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 (__PIKA_UI_H_INSIDE__) && !defined (PIKA_COMPILATION)
+#error "Only can be included directly."
+#endif
+
+#ifndef __PIKA_GRADIENT_CHOOSER_H__
+#define __PIKA_GRADIENT_CHOOSER_H__
+
+#include
+
+G_BEGIN_DECLS
+
+#define PIKA_TYPE_GRADIENT_CHOOSER (pika_gradient_chooser_get_type ())
+G_DECLARE_FINAL_TYPE (PikaGradientChooser, pika_gradient_chooser, PIKA, GRADIENT_CHOOSER, PikaResourceChooser)
+
+
+GtkWidget * pika_gradient_chooser_new (const gchar *title,
+ const gchar *label,
+ PikaResource *gradient);
+
+
+G_END_DECLS
+
+#endif /* __PIKA_GRADIENT_CHOOSER_H__ */
diff --git a/libpika/pikapalette.c b/libpika/pikapalette.c
new file mode 100644
index 0000000..0366cd7
--- /dev/null
+++ b/libpika/pikapalette.c
@@ -0,0 +1,42 @@
+/* LIBPIKA - The PIKA Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * pikapalette.c
+ * Copyright (C) 2023 Jehan
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * 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
+ * .
+ */
+
+#include "config.h"
+
+#include "pika.h"
+
+#include "pikapalette.h"
+
+struct _PikaPalette
+{
+ PikaResource parent_instance;
+};
+
+G_DEFINE_TYPE (PikaPalette, pika_palette, PIKA_TYPE_RESOURCE);
+
+
+static void pika_palette_class_init (PikaPaletteClass *klass)
+{
+}
+
+static void pika_palette_init (PikaPalette *palette)
+{
+}
diff --git a/libpika/pikapalette.h b/libpika/pikapalette.h
new file mode 100644
index 0000000..62eb63a
--- /dev/null
+++ b/libpika/pikapalette.h
@@ -0,0 +1,43 @@
+/* LIBPIKA - The PIKA Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * pikapalette.h
+ * Copyright (C) 2023 Jehan
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * 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 (__PIKA_H_INSIDE__) && !defined (PIKA_COMPILATION)
+#error "Only can be included directly."
+#endif
+
+#ifndef __PIKA_PALETTE_H__
+#define __PIKA_PALETTE_H__
+
+G_BEGIN_DECLS
+
+/* For information look into the C source or the html documentation */
+
+
+#include
+
+
+#define PIKA_TYPE_PALETTE (pika_palette_get_type ())
+G_DECLARE_FINAL_TYPE (PikaPalette, pika_palette, PIKA, PALETTE, PikaResource)
+
+
+G_END_DECLS
+
+#endif /* __PIKA_PALETTE_H__ */
diff --git a/libpika/pikapalettechooser.c b/libpika/pikapalettechooser.c
new file mode 100644
index 0000000..32d5404
--- /dev/null
+++ b/libpika/pikapalettechooser.c
@@ -0,0 +1,154 @@
+/* LIBPIKA - The PIKA Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * pikapalettechooser.c
+ * Copyright (C) 2004 Michael Natterer
+ *
+ * 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 "libpikawidgets/pikawidgets.h"
+
+#include "pika.h"
+
+#include "pikauitypes.h"
+#include "pikapalettechooser.h"
+#include "pikauimarshal.h"
+
+#include "libpika-intl.h"
+
+
+/**
+ * SECTION: pikapalettechooser
+ * @title: PikaPaletteChooser
+ * @short_description: A button which pops up a palette selection dialog.
+ *
+ * A button which pops up a palette selection dialog.
+ **/
+
+struct _PikaPaletteChooser
+{
+ PikaResourceChooser parent_instance;
+
+ GtkWidget *label;
+ GtkWidget *button;
+};
+
+
+static void pika_palette_chooser_draw_interior (PikaResourceChooser *self);
+
+
+static const GtkTargetEntry drag_target = { "application/x-pika-palette-name", 0, 0 };
+
+
+G_DEFINE_FINAL_TYPE (PikaPaletteChooser, pika_palette_chooser, PIKA_TYPE_RESOURCE_CHOOSER)
+
+
+static void
+pika_palette_chooser_class_init (PikaPaletteChooserClass *klass)
+{
+ PikaResourceChooserClass *superclass = PIKA_RESOURCE_CHOOSER_CLASS (klass);
+
+ superclass->draw_interior = pika_palette_chooser_draw_interior;
+ superclass->resource_type = PIKA_TYPE_PALETTE;
+}
+
+static void
+pika_palette_chooser_init (PikaPaletteChooser *self)
+{
+ GtkWidget *hbox;
+ GtkWidget *image;
+
+ self->button = gtk_button_new ();
+ gtk_container_add (GTK_CONTAINER (self), self->button);
+
+ hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4);
+ gtk_container_add (GTK_CONTAINER (self->button), hbox);
+
+ image = gtk_image_new_from_icon_name (PIKA_ICON_PALETTE,
+ GTK_ICON_SIZE_BUTTON);
+ gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
+
+ self->label = gtk_label_new ("unknown");
+ gtk_box_pack_start (GTK_BOX (hbox), self->label, TRUE, TRUE, 4);
+
+ gtk_widget_show_all (GTK_WIDGET (self));
+
+ pika_resource_chooser_set_drag_target (PIKA_RESOURCE_CHOOSER (self),
+ hbox, &drag_target);
+ pika_resource_chooser_set_clickable (PIKA_RESOURCE_CHOOSER (self), self->button);
+}
+
+static void
+pika_palette_chooser_draw_interior (PikaResourceChooser *self)
+{
+ PikaPaletteChooser *palette_select= PIKA_PALETTE_CHOOSER (self);
+ PikaResource *resource;
+ gchar *name = NULL;
+
+ resource = pika_resource_chooser_get_resource (self);
+
+ if (resource)
+ name = pika_resource_get_name (resource);
+
+ gtk_label_set_text (GTK_LABEL (palette_select->label), name);
+}
+
+/**
+ * pika_palette_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.
+ * @resource: (nullable): Initial palette.
+ *
+ * Creates a new #GtkWidget that lets a user choose a palette.
+ * You can put this widget in a table in a plug-in dialog.
+ *
+ * When palette is NULL, initial choice is from context.
+ *
+ * Returns: A #GtkWidget that you can use in your UI.
+ *
+ * Since: 2.4
+ */
+GtkWidget *
+pika_palette_chooser_new (const gchar *title,
+ const gchar *label,
+ PikaResource *resource)
+{
+ GtkWidget *self;
+
+ if (resource == NULL)
+ resource = PIKA_RESOURCE (pika_context_get_palette ());
+
+ if (title)
+ self = g_object_new (PIKA_TYPE_PALETTE_CHOOSER,
+ "title", title,
+ "label", label,
+ "resource", resource,
+ NULL);
+ else
+ self = g_object_new (PIKA_TYPE_PALETTE_CHOOSER,
+ "label", label,
+ "resource", resource,
+ NULL);
+
+ pika_palette_chooser_draw_interior (PIKA_RESOURCE_CHOOSER (self));
+
+ return self;
+}
diff --git a/libpika/pikapalettechooser.h b/libpika/pikapalettechooser.h
new file mode 100644
index 0000000..4eb7797
--- /dev/null
+++ b/libpika/pikapalettechooser.h
@@ -0,0 +1,43 @@
+/* LIBPIKA - The PIKA Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * pikapaletteselectbutton.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 (__PIKA_UI_H_INSIDE__) && !defined (PIKA_COMPILATION)
+#error "Only can be included directly."
+#endif
+
+#ifndef __PIKA_PALETTE_CHOOSER_H__
+#define __PIKA_PALETTE_CHOOSER_H__
+
+#include
+
+G_BEGIN_DECLS
+
+#define PIKA_TYPE_PALETTE_CHOOSER (pika_palette_chooser_get_type ())
+G_DECLARE_FINAL_TYPE (PikaPaletteChooser, pika_palette_chooser, PIKA, PALETTE_CHOOSER, PikaResourceChooser)
+
+
+GtkWidget * pika_palette_chooser_new (const gchar *title,
+ const gchar *label,
+ PikaResource *resource);
+
+
+G_END_DECLS
+
+#endif /* __PIKA_PALETTE_CHOOSER_H__ */
diff --git a/libpika/pikapattern.c b/libpika/pikapattern.c
new file mode 100644
index 0000000..af6e3d4
--- /dev/null
+++ b/libpika/pikapattern.c
@@ -0,0 +1,207 @@
+/* LIBPIKA - The PIKA Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * pikapattern.c
+ * Copyright (C) 2023 Jehan
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * 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
+ * .
+ */
+
+#include "config.h"
+
+#include "pika.h"
+
+#include "pikapattern.h"
+
+struct _PikaPattern
+{
+ PikaResource parent_instance;
+
+ /* Native size buffer of the pattern contents. */
+ GeglBuffer *buffer;
+};
+
+static void pika_pattern_finalize (GObject *object);
+static void pika_pattern_get_data (PikaPattern *pattern);
+static GeglBuffer * pika_pattern_scale (GeglBuffer *buffer,
+ gint max_width,
+ gint max_height);
+
+
+G_DEFINE_TYPE (PikaPattern, pika_pattern, PIKA_TYPE_RESOURCE);
+
+
+static void pika_pattern_class_init (PikaPatternClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = pika_pattern_finalize;
+}
+
+static void pika_pattern_init (PikaPattern *pattern)
+{
+ pattern->buffer = NULL;
+}
+
+static void
+pika_pattern_finalize (GObject *object)
+{
+ PikaPattern *pattern = PIKA_PATTERN (object);
+
+ g_clear_object (&pattern->buffer);
+
+ G_OBJECT_CLASS (pika_pattern_parent_class)->finalize (object);
+}
+
+
+/**
+ * pika_pattern_get_buffer:
+ * @pattern: a [class@Pattern].
+ * @max_width: a maximum width for the returned buffer.
+ * @max_height: a maximum height for the returned buffer.
+ * @format: an optional Babl format.
+ *
+ * Gets pixel data of the pattern within the bounding box specified by @max_width
+ * and @max_height. The data will be scaled down so that it fits within this
+ * size without changing its ratio. If the pattern is smaller than this size to
+ * begin with, it will not be scaled up.
+ *
+ * If @max_width or @max_height are %NULL, the buffer is returned in the pattern's
+ * native size.
+ *
+ * Make sure you called [func@Gegl.init] before calling any function using
+ * `GEGL`.
+ *
+ * Returns: (transfer full): a [class@Gegl.Buffer].
+*/
+GeglBuffer *
+pika_pattern_get_buffer (PikaPattern *pattern,
+ gint max_width,
+ gint max_height,
+ const Babl *format)
+{
+ pika_pattern_get_data (pattern);
+
+ g_return_val_if_fail (pattern->buffer != NULL, NULL);
+
+ if (max_width == 0 || max_height == 0 ||
+ (gegl_buffer_get_width (pattern->buffer) <= max_width &&
+ gegl_buffer_get_height (pattern->buffer) <= max_height))
+ return gegl_buffer_dup (pattern->buffer);
+
+ return pika_pattern_scale (pattern->buffer, max_width, max_height);
+}
+
+static void
+pika_pattern_get_data (PikaPattern *pattern)
+{
+ gint width;
+ gint height;
+ gint bpp;
+ GBytes *bytes;
+ const guchar *pixels;
+ gsize pixels_size;
+ const Babl *format;
+
+ /*
+ * This check assumes that the pattern contents doesn't change, which is not a
+ * perfect assumption. We could maybe add a PDB call which would return
+ * the new pattern data only if it changed since last call (which can be
+ * verified with some kind of internal runtime version to pass from one call
+ * to another). TODO
+ */
+ if (pattern->buffer != NULL)
+ return;
+
+ g_clear_object (&pattern->buffer);
+
+ _pika_pattern_get_pixels (pattern, &width, &height, &bpp, &bytes);
+ pixels = g_bytes_unref_to_data (bytes, &pixels_size);
+
+ /* It's an ugly way to determine the proper format but pika_pattern_get_pixels()
+ * doesn't give more info.
+ */
+ switch (bpp)
+ {
+ case 1:
+ format = babl_format ("Y' u8");
+ break;
+ case 2:
+ format = babl_format ("Y'A u8");
+ break;
+ case 3:
+ format = babl_format ("R'G'B' u8");
+ break;
+ case 4:
+ format = babl_format ("R'G'B'A u8");
+ break;
+ default:
+ g_return_if_reached ();
+ }
+
+ pattern->buffer = gegl_buffer_linear_new_from_data ((const gpointer) pixels, format,
+ GEGL_RECTANGLE (0, 0, width, height),
+ 0, g_free, NULL);
+}
+
+static GeglBuffer *
+pika_pattern_scale (GeglBuffer *buffer,
+ gint max_width,
+ gint max_height)
+{
+ GeglBuffer *scaled = NULL;
+ GeglNode *graph;
+ GeglNode *source;
+ GeglNode *op;
+ GeglNode *sink;
+ gdouble width;
+ gdouble height;
+ gdouble scale;
+
+ height = (gdouble) max_height;
+ width = (gdouble) gegl_buffer_get_width (buffer) / gegl_buffer_get_height (buffer) * height;
+ if (width > (gdouble) max_width)
+ {
+ width = (gdouble) max_width;
+ height = (gdouble) gegl_buffer_get_height (buffer) / gegl_buffer_get_width (buffer) * width;
+ }
+ scale = width / gegl_buffer_get_width (buffer);
+
+ graph = gegl_node_new ();
+ source = gegl_node_new_child (graph,
+ "operation", "gegl:buffer-source",
+ "buffer", buffer,
+ NULL);
+ op = gegl_node_new_child (graph,
+ "operation", "gegl:scale-ratio",
+ "origin-x", 0.0,
+ "origin-y", 0.0,
+ "sampler", PIKA_INTERPOLATION_LINEAR,
+ "abyss-policy", GEGL_ABYSS_CLAMP,
+ "x", scale,
+ "y", scale,
+ NULL);
+ sink = gegl_node_new_child (graph,
+ "operation", "gegl:buffer-sink",
+ "buffer", &scaled,
+ "format", gegl_buffer_get_format (buffer),
+ NULL);
+ gegl_node_link_many (source, op, sink, NULL);
+ gegl_node_process (sink);
+
+ g_object_unref (graph);
+
+ return scaled;
+}
diff --git a/libpika/pikapattern.h b/libpika/pikapattern.h
new file mode 100644
index 0000000..f3bc79b
--- /dev/null
+++ b/libpika/pikapattern.h
@@ -0,0 +1,49 @@
+/* LIBPIKA - The PIKA Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * pikapattern.h
+ * Copyright (C) 2023 Jehan
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * 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 (__PIKA_H_INSIDE__) && !defined (PIKA_COMPILATION)
+#error "Only can be included directly."
+#endif
+
+#ifndef __PIKA_PATTERN_H__
+#define __PIKA_PATTERN_H__
+
+G_BEGIN_DECLS
+
+/* For information look into the C source or the html documentation */
+
+
+#include
+
+
+#define PIKA_TYPE_PATTERN (pika_pattern_get_type ())
+G_DECLARE_FINAL_TYPE (PikaPattern, pika_pattern, PIKA, PATTERN, PikaResource)
+
+
+
+GeglBuffer * pika_pattern_get_buffer (PikaPattern *pattern,
+ gint max_width,
+ gint max_height,
+ const Babl *format) G_GNUC_WARN_UNUSED_RESULT;
+
+G_END_DECLS
+
+#endif /* __PIKA_PATTERN_H__ */
diff --git a/libpika/pikapatternchooser.c b/libpika/pikapatternchooser.c
new file mode 100644
index 0000000..66e5bf8
--- /dev/null
+++ b/libpika/pikapatternchooser.c
@@ -0,0 +1,381 @@
+/* LIBPIKA - The PIKA Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * pikapatternchooser.c
+ * Copyright (C) 1998 Andy Thomas
+ *
+ * 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 "libpikawidgets/pikawidgets.h"
+
+#include "pika.h"
+
+#include "pikauitypes.h"
+#include "pikapatternchooser.h"
+#include "pikauimarshal.h"
+
+#include "libpika-intl.h"
+
+
+/**
+ * SECTION: pikapatternchooser
+ * @title: PikaPatternChooser
+ * @short_description: A button which pops up a pattern selection dialog.
+ *
+ * A button which pops up a pattern selection dialog.
+ *
+ * Note that this widget draws itself using `GEGL` code. You **must** call
+ * [func@Gegl.init] first to be able to use this chooser.
+ **/
+
+#define CELL_SIZE 40
+
+struct _PikaPatternChooser
+{
+ PikaResourceChooser parent_instance;
+
+ GtkWidget *preview;
+ GtkWidget *popup;
+
+ PikaPattern *pattern;
+ GeglBuffer *buffer;
+ gint width;
+ gint height;
+};
+
+/* local */
+
+static gboolean pika_pattern_select_on_preview_events (GtkWidget *widget,
+ GdkEvent *event,
+ PikaPatternChooser *button);
+
+/* local drawing methods. */
+static void pika_pattern_select_preview_fill_draw (PikaPatternChooser *chooser,
+ PikaPreviewArea *area);
+
+static void pika_pattern_chooser_draw (PikaResourceChooser *self);
+static void pika_pattern_chooser_get_pattern_image (PikaPatternChooser *self,
+ gint width,
+ gint height);
+
+/* Popup methods. */
+static void pika_pattern_chooser_open_popup (PikaPatternChooser *button,
+ gint x,
+ gint y);
+static void pika_pattern_chooser_close_popup (PikaPatternChooser *button);
+
+
+/* A GtkTargetEntry has a string and two ints.
+ * This is one, but we treat it as an array.
+ */
+static const GtkTargetEntry drag_target = { "application/x-pika-pattern-name", 0, 0 };
+
+G_DEFINE_FINAL_TYPE (PikaPatternChooser, pika_pattern_chooser, PIKA_TYPE_RESOURCE_CHOOSER)
+
+
+static void
+pika_pattern_chooser_class_init (PikaPatternChooserClass *klass)
+{
+ PikaResourceChooserClass *superclass = PIKA_RESOURCE_CHOOSER_CLASS (klass);
+
+ superclass->draw_interior = pika_pattern_chooser_draw;
+ superclass->resource_type = PIKA_TYPE_PATTERN;
+}
+
+static void
+pika_pattern_chooser_init (PikaPatternChooser *self)
+{
+ GtkWidget *frame;
+ GtkWidget *button;
+
+ frame = gtk_aspect_frame_new (NULL, 0.5, 0.5, 1.0, FALSE);
+ gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN);
+ gtk_box_pack_start (GTK_BOX (self), frame, FALSE, FALSE, 0);
+ gtk_widget_show (frame);
+
+ self->preview = pika_preview_area_new ();
+ gtk_widget_add_events (self->preview,
+ GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK);
+ gtk_widget_set_size_request (self->preview, CELL_SIZE, CELL_SIZE);
+ gtk_container_add (GTK_CONTAINER (frame), self->preview);
+ gtk_widget_show (self->preview);
+
+ g_signal_connect_swapped (self->preview, "size-allocate",
+ G_CALLBACK (pika_pattern_chooser_draw),
+ self);
+
+ g_signal_connect (self->preview, "event",
+ G_CALLBACK (pika_pattern_select_on_preview_events),
+ self);
+
+ button = gtk_button_new_with_mnemonic (_("_Browse..."));
+ gtk_box_pack_start (GTK_BOX (self), button, FALSE, FALSE, 0);
+
+ pika_resource_chooser_set_drag_target (PIKA_RESOURCE_CHOOSER (self),
+ self->preview, &drag_target);
+
+ pika_resource_chooser_set_clickable (PIKA_RESOURCE_CHOOSER (self), button);
+ gtk_widget_show (button);
+}
+
+/**
+ * pika_pattern_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.
+ * @resource: (nullable): Initial pattern.
+ *
+ * Creates a new #GtkWidget that lets a user choose a pattern.
+ * You can put this widget in a table in a plug-in dialog.
+ *
+ * When pattern is NULL, initial choice is from context.
+ *
+ * Returns: A #GtkWidget that you can use in your UI.
+ *
+ * Since: 2.4
+ */
+GtkWidget *
+pika_pattern_chooser_new (const gchar *title,
+ const gchar *label,
+ PikaResource *resource)
+{
+ GtkWidget *self;
+
+ if (resource == NULL)
+ resource = PIKA_RESOURCE (pika_context_get_pattern ());
+ g_return_val_if_fail (PIKA_IS_PATTERN (resource), NULL);
+
+ if (title)
+ self = g_object_new (PIKA_TYPE_PATTERN_CHOOSER,
+ "title", title,
+ "label", label,
+ "resource", resource,
+ NULL);
+ else
+ self = g_object_new (PIKA_TYPE_PATTERN_CHOOSER,
+ "label", label,
+ "resource", resource,
+ NULL);
+
+ pika_pattern_chooser_draw (PIKA_RESOURCE_CHOOSER (self));
+
+ return self;
+}
+
+
+static void
+pika_pattern_chooser_draw (PikaResourceChooser *chooser)
+{
+ PikaPatternChooser *pchooser = PIKA_PATTERN_CHOOSER (chooser);
+ GtkAllocation allocation;
+
+ gtk_widget_get_allocation (pchooser->preview, &allocation);
+
+ pika_pattern_chooser_get_pattern_image (pchooser, allocation.width, allocation.height);
+ pika_pattern_select_preview_fill_draw (pchooser, PIKA_PREVIEW_AREA (pchooser->preview));
+}
+
+static void
+pika_pattern_chooser_get_pattern_image (PikaPatternChooser *chooser,
+ gint width,
+ gint height)
+{
+ PikaPattern *pattern;
+
+ g_object_get (chooser, "resource", &pattern, NULL);
+
+ if (chooser->pattern == pattern &&
+ chooser->width == width &&
+ chooser->height == height)
+ {
+ /* Let's assume pattern contents is not changing in a single run. */
+ g_object_unref (pattern);
+ return;
+ }
+
+ g_clear_object (&chooser->buffer);
+
+ chooser->pattern = pattern;
+ chooser->buffer = pika_pattern_get_buffer (pattern, width, height, NULL);
+ chooser->width = gegl_buffer_get_width (chooser->buffer);
+ chooser->height = gegl_buffer_get_height (chooser->buffer);
+
+ g_object_unref (pattern);
+}
+
+/* On mouse events in self's preview, popup a zoom view of entire pattern */
+static gboolean
+pika_pattern_select_on_preview_events (GtkWidget *widget,
+ GdkEvent *event,
+ PikaPatternChooser *self)
+{
+ GdkEventButton *bevent;
+
+ switch (event->type)
+ {
+ case GDK_BUTTON_PRESS:
+ bevent = (GdkEventButton *) event;
+
+ if (bevent->button == 1)
+ {
+ gtk_grab_add (widget);
+ pika_pattern_chooser_open_popup (self,
+ bevent->x, bevent->y);
+ }
+ break;
+
+ case GDK_BUTTON_RELEASE:
+ bevent = (GdkEventButton *) event;
+
+ if (bevent->button == 1)
+ {
+ gtk_grab_remove (widget);
+ pika_pattern_chooser_close_popup (self);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return FALSE;
+}
+
+/* Fill a PikaPreviewArea with a image then draw. */
+static void
+pika_pattern_select_preview_fill_draw (PikaPatternChooser *chooser,
+ PikaPreviewArea *area)
+{
+ GeglBuffer *src_buffer;
+ const Babl *format;
+ const Babl *model;
+ guchar *src;
+ PikaImageType type;
+ gint rowstride;
+ GtkAllocation allocation;
+ gint x = 0;
+ gint y = 0;
+
+ gtk_widget_get_allocation (GTK_WIDGET (area), &allocation);
+
+ /* Fill with white. */
+ if (chooser->width < allocation.width ||
+ chooser->height < allocation.height)
+ {
+ pika_preview_area_fill (area,
+ 0, 0,
+ allocation.width,
+ allocation.height,
+ 0xFF, 0xFF, 0xFF);
+
+ x = ((allocation.width - chooser->width) / 2);
+ y = ((allocation.height - chooser->height) / 2);
+ }
+
+ /* Draw the pattern. */
+ src_buffer = chooser->buffer;
+ format = gegl_buffer_get_format (src_buffer);
+ rowstride = chooser->width * babl_format_get_bytes_per_pixel (format);
+ model = babl_format_get_model (format);
+
+ if (model == babl_model ("R'G'B'"))
+ type = PIKA_RGB_IMAGE;
+ else if (model == babl_model ("R'G'B'A"))
+ type = PIKA_RGBA_IMAGE;
+ else if (model == babl_model ("Y'"))
+ type = PIKA_GRAY_IMAGE;
+ else if (model == babl_model ("Y'A"))
+ type = PIKA_GRAYA_IMAGE;
+ else
+ /* I just know that we can't have other formats because I set it up this way
+ * in pika_pattern_get_buffer(). If we make the latter more generic, able to
+ * return more types of pixel data, this should be reviewed. XXX
+ */
+ g_return_if_reached ();
+
+ src = g_try_malloc (sizeof (guchar) * rowstride * chooser->height);
+
+ gegl_buffer_get (chooser->buffer, NULL, 1.0, format, src, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+ pika_preview_area_draw (area, x, y, chooser->width, chooser->height, type, src, rowstride);
+
+ g_free (src);
+}
+
+/* popup methods. */
+
+static void
+pika_pattern_chooser_open_popup (PikaPatternChooser *chooser,
+ gint x,
+ gint y)
+{
+ GtkWidget *frame;
+ GtkWidget *preview;
+ GdkMonitor *monitor;
+ GdkRectangle workarea;
+ gint x_org;
+ gint y_org;
+
+ if (chooser->popup)
+ pika_pattern_chooser_close_popup (chooser);
+
+ if (chooser->width <= CELL_SIZE && chooser->height <= CELL_SIZE)
+ return;
+
+ chooser->popup = gtk_window_new (GTK_WINDOW_POPUP);
+ gtk_window_set_type_hint (GTK_WINDOW (chooser->popup), GDK_WINDOW_TYPE_HINT_DND);
+ gtk_window_set_screen (GTK_WINDOW (chooser->popup),
+ gtk_widget_get_screen (GTK_WIDGET (chooser)));
+
+ frame = gtk_frame_new (NULL);
+ gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
+ gtk_container_add (GTK_CONTAINER (chooser->popup), frame);
+ gtk_widget_show (frame);
+
+ preview = pika_preview_area_new ();
+ gtk_widget_set_size_request (preview, chooser->width, chooser->height);
+ gtk_container_add (GTK_CONTAINER (frame), preview);
+ gtk_widget_show (preview);
+
+ /* decide where to put the popup: near the preview i.e. at mousedown coords */
+ gdk_window_get_origin (gtk_widget_get_window (chooser->preview),
+ &x_org, &y_org);
+
+ monitor = pika_widget_get_monitor (GTK_WIDGET (chooser));
+ gdk_monitor_get_workarea (monitor, &workarea);
+
+ x = x_org + x - (chooser->width / 2);
+ y = y_org + y - (chooser->height / 2);
+
+ x = CLAMP (x, workarea.x, workarea.x + workarea.width - chooser->width);
+ y = CLAMP (y, workarea.y, workarea.y + workarea.height - chooser->height);
+
+ gtk_window_move (GTK_WINDOW (chooser->popup), x, y);
+
+ gtk_widget_show (chooser->popup);
+
+ /* Draw popup now. Usual events do not cause a draw. */
+ pika_pattern_select_preview_fill_draw (chooser, PIKA_PREVIEW_AREA (preview));
+}
+
+static void
+pika_pattern_chooser_close_popup (PikaPatternChooser *self)
+{
+ g_clear_pointer (&self->popup, gtk_widget_destroy);
+}
diff --git a/libpika/pikapatternchooser.h b/libpika/pikapatternchooser.h
new file mode 100644
index 0000000..d7f6189
--- /dev/null
+++ b/libpika/pikapatternchooser.h
@@ -0,0 +1,43 @@
+/* LIBPIKA - The PIKA Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * pikapatternchooser.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 (__PIKA_UI_H_INSIDE__) && !defined (PIKA_COMPILATION)
+#error "Only can be included directly."
+#endif
+
+#ifndef __PIKA_PATTERN_CHOOSER_H__
+#define __PIKA_PATTERN_CHOOSER_H__
+
+#include
+
+G_BEGIN_DECLS
+
+#define PIKA_TYPE_PATTERN_CHOOSER (pika_pattern_chooser_get_type ())
+G_DECLARE_FINAL_TYPE (PikaPatternChooser, pika_pattern_chooser, PIKA, PATTERN_CHOOSER, PikaResourceChooser)
+
+
+GtkWidget * pika_pattern_chooser_new (const gchar *title,
+ const gchar *label,
+ PikaResource *resource);
+
+
+G_END_DECLS
+
+#endif /* __PIKA_PATTERN_CHOOSER_H__ */
diff --git a/libpika/pikapropwidgets.c b/libpika/pikapropwidgets.c
new file mode 100644
index 0000000..1a61eb3
--- /dev/null
+++ b/libpika/pikapropwidgets.c
@@ -0,0 +1,273 @@
+/* LIBPIKA - The PIKA Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * pikapropwidgets.c
+ * Copyright (C) 2023 Jehan
+ *
+ * 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
+ * .
+ */
+
+#include "config.h"
+
+#include "libpika/pikaui.h"
+
+#include "libpika-intl.h"
+
+/*
+ * This is a complement of libpikawidgets/pikapropwidgets.c
+ * These are property functions for types from libpika, such as
+ * [class@Gimp.Resource] or [class@Gimp.Item] subclasses.
+ */
+
+
+typedef GtkWidget* (*PikaResourceWidgetCreator) (const gchar *title,
+ const gchar *label,
+ PikaResource *initial_resource);
+
+
+/* utility function prototypes */
+
+static GtkWidget * pika_prop_resource_chooser_factory (PikaResourceWidgetCreator widget_creator_func,
+ GObject *config,
+ const gchar *property_name,
+ const gchar *chooser_title);
+static gchar * pika_utils_make_canonical_menu_label (const gchar *path);
+
+
+/**
+ * pika_prop_brush_chooser_new:
+ * @config: Object to which property is attached.
+ * @property_name: Name of a [class@Gimp.Brush] property.
+ * @chooser_title: (nullable): title for the poppable dialog.
+ *
+ * Creates a [class@Gimp.Brush.Chooser] controlled by the specified property.
+ *
+ * Returns: (transfer full): A new [class@GimpUi.BrushChooser].
+ *
+ * Since: 3.0
+ */
+GtkWidget *
+pika_prop_brush_chooser_new (GObject *config,
+ const gchar *property_name,
+ const gchar *chooser_title)
+{
+ return pika_prop_resource_chooser_factory (pika_brush_chooser_new,
+ config, property_name, chooser_title);
+}
+
+/**
+ * pika_prop_font_chooser_new:
+ * @config: Object to which property is attached.
+ * @property_name: Name of a [class@Gimp.Font] property.
+ * @chooser_title: (nullable): title for the poppable dialog.
+ *
+ * Creates a [class@GimpUi.FontChooser] controlled by the specified property.
+ *
+ * Returns: (transfer full): A new [class@GimpUi.FontChooser].
+ *
+ * Since: 3.0
+ */
+GtkWidget *
+pika_prop_font_chooser_new (GObject *config,
+ const gchar *property_name,
+ const gchar *chooser_title)
+{
+ return pika_prop_resource_chooser_factory (pika_font_chooser_new,
+ config, property_name, chooser_title);
+}
+
+/**
+ * pika_prop_gradient_chooser_new:
+ * @config: Object to which property is attached.
+ * @property_name: Name of a [class@Gimp.Gradient] property.
+ * @chooser_title: (nullable): title for the poppable dialog.
+ *
+ * Creates a [class@GimpUi.GradientChooser] controlled by the specified property.
+ *
+ * Returns: (transfer full): A new [class@GimpUi.GradientChooser].
+ *
+ * Since: 3.0
+ */
+GtkWidget *
+pika_prop_gradient_chooser_new (GObject *config,
+ const gchar *property_name,
+ const gchar *chooser_title)
+{
+ return pika_prop_resource_chooser_factory (pika_gradient_chooser_new,
+ config, property_name, chooser_title);
+}
+
+/**
+ * pika_prop_palette_chooser_new:
+ * @config: Object to which property is attached.
+ * @property_name: Name of a [class@Gimp.Palette] property.
+ * @chooser_title: (nullable): title for the poppable dialog.
+ *
+ * Creates a [class@GimpUi.PaletteChooser] controlled by the specified property.
+ *
+ * Returns: (transfer full): A new [class@GimpUi.PaletteChooser].
+ *
+ * Since: 3.0
+ */
+GtkWidget *
+pika_prop_palette_chooser_new (GObject *config,
+ const gchar *property_name,
+ const gchar *chooser_title)
+{
+ return pika_prop_resource_chooser_factory (pika_palette_chooser_new,
+ config, property_name, chooser_title);
+}
+
+/**
+ * pika_prop_pattern_chooser_new:
+ * @config: Object to which property is attached.
+ * @property_name: Name of a [class@Gimp.Pattern] property.
+ * @chooser_title: (nullable): title for the poppable dialog.
+ *
+ * Creates a [class@GimpUi.PatternChooser] controlled by the specified property.
+ *
+ * Returns: (transfer full): A new [class@GimpUi.PatternChooser].
+ *
+ * Since: 3.0
+ */
+GtkWidget *
+pika_prop_pattern_chooser_new (GObject *config,
+ const gchar *property_name,
+ const gchar *chooser_title)
+{
+ return pika_prop_resource_chooser_factory (pika_pattern_chooser_new,
+ config, property_name, chooser_title);
+}
+
+/**
+ * pika_prop_drawable_chooser_new:
+ * @config: Object to which property is attached.
+ * @property_name: Name of a [class@Gimp.Drawable] property.
+ * @chooser_title: (nullable): title for the poppable dialog.
+ *
+ * Creates a [class@GimpUi.DrawableChooser] controlled by the specified property.
+ *
+ * Returns: (transfer full): A new [class@GimpUi.DrawableChooser].
+ *
+ * Since: 3.0
+ */
+GtkWidget *
+pika_prop_drawable_chooser_new (GObject *config,
+ const gchar *property_name,
+ const gchar *chooser_title)
+{
+ GParamSpec *param_spec;
+ GtkWidget *prop_chooser;
+ PikaDrawable *initial_drawable = 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, PIKA_TYPE_DRAWABLE), NULL);
+
+ g_object_get (config,
+ property_name, &initial_drawable,
+ NULL);
+
+ label = g_param_spec_get_nick (param_spec);
+
+ if (chooser_title == NULL)
+ {
+ gchar *canonical;
+
+ canonical = pika_utils_make_canonical_menu_label (label);
+ if (g_type_is_a (param_spec->value_type, PIKA_TYPE_LAYER))
+ title = g_strdup_printf (_("Choose layer: %s"), canonical);
+ if (g_type_is_a (param_spec->value_type, PIKA_TYPE_CHANNEL))
+ title = g_strdup_printf (_("Choose channel: %s"), canonical);
+ else
+ title = g_strdup_printf (_("Choose drawable: %s"), canonical);
+ g_free (canonical);
+ }
+ else
+ {
+ title = g_strdup (chooser_title);
+ }
+
+ prop_chooser = pika_drawable_chooser_new (title, label, param_spec->value_type, initial_drawable);
+ g_clear_object (&initial_drawable);
+ g_free (title);
+
+ g_object_bind_property (prop_chooser, "drawable",
+ config, property_name,
+ G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+
+ return prop_chooser;
+}
+
+/*******************************/
+/* private utility functions */
+/*******************************/
+
+static GtkWidget *
+pika_prop_resource_chooser_factory (PikaResourceWidgetCreator widget_creator_func,
+ GObject *config,
+ const gchar *property_name,
+ const gchar *chooser_title)
+{
+ GParamSpec *param_spec;
+ GtkWidget *prop_chooser;
+ PikaResource *initial_resource;
+ 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, PIKA_TYPE_RESOURCE), NULL);
+
+ g_object_get (config,
+ property_name, &initial_resource,
+ NULL);
+
+ label = g_param_spec_get_nick (param_spec);
+
+ /* Create the wrapped widget. For example, call pika_font_chooser_new.
+ * When initial_resource is NULL, the widget creator will set it's resource
+ * property from context.
+ */
+ prop_chooser = widget_creator_func (chooser_title, label, initial_resource);
+ g_clear_object (&initial_resource);
+
+ g_object_bind_property (prop_chooser, "resource",
+ config, property_name,
+ G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
+
+ return prop_chooser;
+}
+
+/* This is a copy of the similarly-named function in app/widgets/pikawidgets-utils.c
+ * I hesitated to put this maybe in libpikawidgets/pikawidgetsutils.h but for
+ * now, let's not. If it's useful to more people, it's always easier to move the
+ * function in rather than deprecating it.
+ */
+static gchar *
+pika_utils_make_canonical_menu_label (const gchar *path)
+{
+ gchar **split_path;
+ gchar *canon_path;
+
+ /* The first underscore of each path item is a mnemonic. */
+ split_path = g_strsplit (path, "_", 2);
+ canon_path = g_strjoinv ("", split_path);
+ g_strfreev (split_path);
+
+ return canon_path;
+}
diff --git a/libpika/pikapropwidgets.h b/libpika/pikapropwidgets.h
new file mode 100644
index 0000000..428824c
--- /dev/null
+++ b/libpika/pikapropwidgets.h
@@ -0,0 +1,58 @@
+/* LIBPIKA - The PIKA Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * pikapropwidgets.h
+ * Copyright (C) 2023 Jehan
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * 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 (__PIKA_UI_H_INSIDE__) && !defined (PIKA_COMPILATION)
+#error "Only can be included directly."
+#endif
+
+
+#ifndef __PIKA_PROP_WIDGETS_H__
+#define __PIKA_PROP_WIDGETS_H__
+
+G_BEGIN_DECLS
+
+
+/* PikaParamResource */
+
+GtkWidget * pika_prop_brush_chooser_new (GObject *config,
+ const gchar *property_name,
+ const gchar *chooser_title);
+GtkWidget * pika_prop_font_chooser_new (GObject *config,
+ const gchar *property_name,
+ const gchar *chooser_title);
+GtkWidget * pika_prop_gradient_chooser_new (GObject *config,
+ const gchar *property_name,
+ const gchar *chooser_title);
+GtkWidget * pika_prop_palette_chooser_new (GObject *config,
+ const gchar *property_name,
+ const gchar *chooser_title);
+GtkWidget * pika_prop_pattern_chooser_new (GObject *config,
+ const gchar *property_name,
+ const gchar *chooser_title);
+
+GtkWidget * pika_prop_drawable_chooser_new (GObject *config,
+ const gchar *property_name,
+ const gchar *chooser_title);
+
+
+G_END_DECLS
+
+#endif /* __PIKA_PROP_WIDGETS_H__ */
diff --git a/libpika/pikaresourcechooser.c b/libpika/pikaresourcechooser.c
new file mode 100644
index 0000000..f90ba2a
--- /dev/null
+++ b/libpika/pikaresourcechooser.c
@@ -0,0 +1,622 @@
+/* LIBPIKA - The PIKA Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * 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 "libpikawidgets/pikawidgets.h"
+
+#include "pika.h"
+
+#include "pikauitypes.h"
+#include "pikaresourcechooser.h"
+#include "pikauimarshal.h"
+
+#include "libpika-intl.h"
+
+
+/**
+ * SECTION: pikaresourcechooser
+ * @title: PikaResourceChooser
+ * @short_description: Base class for buttons that popup a resource
+ * selection dialog.
+ *
+ * A button which pops up a resource selection dialog.
+ *
+ * Responsibilities:
+ *
+ * - implementing outer container widget,
+ * - managing clicks and popping up a remote chooser,
+ * - having a resource property,
+ * - signaling when user selects resource
+ * - receiving drag,
+ * - triggering draws of the button interior (by subclass) and draws of remote popup chooser.
+ *
+ * Collaborations:
+ *
+ * - owned by PikaProcedureDialog via PikaPropWidget
+ * - resource property usually bound to a PikaConfig for a PikaPluginProcedure.
+ * - communicates using PikaResourceSelect with remote PikaPDBDialog,
+ * to choose an installed PikaResource owned by core.
+ *
+ * Subclass responsibilities:
+ *
+ * - creating interior widgets
+ * - drawing the interior (a preview of the chosen resource)
+ * - declaring which interior widgets are drag destinations
+ * - declaring which interior widgets are clickable (generate "clicked" signal)
+ * - generate "clicked" (delegating to GtkButton or implementing from mouse events)
+ *
+ * Since: 3.0
+ **/
+
+
+enum
+{
+ RESOURCE_SET,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_TITLE,
+ PROP_LABEL,
+ PROP_RESOURCE,
+ N_PROPS
+};
+
+
+typedef struct
+{
+ PikaResource *resource;
+ gchar *title;
+ gchar *label;
+ gchar *callback;
+
+ GtkWidget *label_widget;
+} PikaResourceChooserPrivate;
+
+
+/* local function prototypes */
+
+static void pika_resource_chooser_constructed (GObject *object);
+static void pika_resource_chooser_dispose (GObject *object);
+static void pika_resource_chooser_finalize (GObject *object);
+
+static void pika_resource_chooser_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void pika_resource_chooser_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static void pika_resource_chooser_clicked (PikaResourceChooser *self);
+
+static void pika_resource_chooser_callback (PikaResource *resource,
+ gboolean dialog_closing,
+ gpointer user_data);
+
+static void pika_resource_select_drag_data_received (PikaResourceChooser *self,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ GtkSelectionData *selection,
+ guint info,
+ guint time);
+
+static void pika_resource_chooser_set_remote_dialog (PikaResourceChooser *self,
+ PikaResource *resource);
+
+
+static guint resource_button_signals[LAST_SIGNAL] = { 0 };
+static GParamSpec *resource_button_props[N_PROPS] = { NULL, };
+
+G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (PikaResourceChooser, pika_resource_chooser, GTK_TYPE_BOX)
+
+
+static void
+pika_resource_chooser_class_init (PikaResourceChooserClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = pika_resource_chooser_constructed;
+ object_class->dispose = pika_resource_chooser_dispose;
+ object_class->finalize = pika_resource_chooser_finalize;
+ object_class->set_property = pika_resource_chooser_set_property;
+ object_class->get_property = pika_resource_chooser_get_property;
+
+ klass->resource_set = NULL;
+
+ /**
+ * PikaResourceChooser:title:
+ *
+ * The title to be used for the resource selection popup dialog.
+ *
+ * Since: 3.0
+ */
+ resource_button_props[PROP_TITLE] =
+ g_param_spec_string ("title",
+ "Title",
+ "The title to be used for the resource selection popup dialog",
+ "Resource Selection",
+ PIKA_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY);
+
+ /**
+ * PikaResourceChooser:label:
+ *
+ * Label text with mnemonic.
+ *
+ * Since: 3.0
+ */
+ resource_button_props[PROP_LABEL] =
+ g_param_spec_string ("label",
+ "Label",
+ "The label to be used next to the button",
+ NULL,
+ PIKA_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY);
+
+ /**
+ * PikaResourceChooser:resource:
+ *
+ * The currently selected resource.
+ *
+ * Since: 3.0
+ */
+ resource_button_props[PROP_RESOURCE] =
+ pika_param_spec_resource ("resource",
+ "Resource",
+ "The currently selected resource",
+ TRUE, /* none_ok */
+ PIKA_PARAM_READWRITE);
+
+ g_object_class_install_properties (object_class,
+ N_PROPS, resource_button_props);
+
+ /**
+ * PikaResourceChooser::resource-set:
+ * @widget: the object which received the signal.
+ * @resource: the currently selected resource.
+ * @dialog_closing: whether the dialog was closed or not.
+ *
+ * The ::resource-set signal is emitted when the user selects a resource.
+ *
+ * Since: 3.0
+ */
+ resource_button_signals[RESOURCE_SET] =
+ g_signal_new ("resource-set",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (PikaResourceChooserClass, resource_set),
+ NULL, NULL,
+ _pikaui_marshal_VOID__POINTER_BOOLEAN,
+ G_TYPE_NONE, 2,
+ G_TYPE_OBJECT,
+ G_TYPE_BOOLEAN);
+}
+
+static void
+pika_resource_chooser_init (PikaResourceChooser *self)
+{
+ PikaResourceChooserPrivate *priv;
+
+ priv = pika_resource_chooser_get_instance_private (PIKA_RESOURCE_CHOOSER (self));
+
+ gtk_orientable_set_orientation (GTK_ORIENTABLE (self),
+ GTK_ORIENTATION_HORIZONTAL);
+ gtk_box_set_spacing (GTK_BOX (self), 6);
+
+ priv->label_widget = gtk_label_new (NULL);
+ gtk_box_pack_start (GTK_BOX (self), priv->label_widget, FALSE, FALSE, 0);
+}
+
+static void
+pika_resource_chooser_constructed (GObject *object)
+{
+ PikaResourceChooserPrivate *priv;
+
+ priv = pika_resource_chooser_get_instance_private (PIKA_RESOURCE_CHOOSER (object));
+ gtk_label_set_text_with_mnemonic (GTK_LABEL (priv->label_widget), priv->label);
+ gtk_widget_show (GTK_WIDGET (priv->label_widget));
+
+ G_OBJECT_CLASS (pika_resource_chooser_parent_class)->constructed (object);
+}
+
+static void
+pika_resource_chooser_dispose (GObject *self)
+{
+ PikaResourceChooserPrivate *priv;
+ PikaResourceChooserClass *klass;
+
+ priv = pika_resource_chooser_get_instance_private (PIKA_RESOURCE_CHOOSER (self));
+ klass = PIKA_RESOURCE_CHOOSER_GET_CLASS (self);
+
+ if (priv->callback)
+ {
+ GType resource_type = klass->resource_type;
+
+ if (resource_type == PIKA_TYPE_FONT)
+ pika_fonts_close_popup (priv->callback);
+ else if (resource_type == PIKA_TYPE_GRADIENT)
+ pika_gradients_close_popup (priv->callback);
+ else if (resource_type == PIKA_TYPE_BRUSH)
+ pika_brushes_close_popup (priv->callback);
+ else if (resource_type == PIKA_TYPE_PALETTE)
+ pika_palettes_close_popup (priv->callback);
+ else if (resource_type == PIKA_TYPE_PATTERN)
+ pika_patterns_close_popup (priv->callback);
+ else
+ g_warning ("%s: unhandled resource type", G_STRFUNC);
+
+ pika_plug_in_remove_temp_procedure (pika_get_plug_in (), priv->callback);
+ g_clear_pointer (&priv->callback, g_free);
+ }
+
+ G_OBJECT_CLASS (pika_resource_chooser_parent_class)->dispose (self);
+}
+
+static void
+pika_resource_chooser_finalize (GObject *object)
+{
+ PikaResourceChooser *self = PIKA_RESOURCE_CHOOSER (object);
+ PikaResourceChooserPrivate *priv = pika_resource_chooser_get_instance_private (self);
+
+ g_clear_pointer (&priv->title, g_free);
+ g_clear_pointer (&priv->label, g_free);
+
+ G_OBJECT_CLASS (pika_resource_chooser_parent_class)->finalize (object);
+}
+
+/**
+ * pika_resource_chooser_set_drag_target:
+ * @chooser: A [class@ResourceChooser]
+ * @drag_region_widget: An interior widget to be a droppable region
+ * and emit "drag-data-received" signal
+ * @drag_target: The drag target to accept
+ *
+ * Called by a subclass init to specialize the instance.
+ *
+ * Subclass knows its interior widget whose region is a drop zone.
+ * Subclass knows what things can be dropped (target.)
+ * Self (super) handles the drop.
+ *
+ * Since: 3.0
+ **/
+void
+pika_resource_chooser_set_drag_target (PikaResourceChooser *chooser,
+ GtkWidget *drag_region_widget,
+ const GtkTargetEntry *drag_target)
+{
+ g_return_if_fail (PIKA_IS_RESOURCE_CHOOSER (chooser));
+ g_return_if_fail (drag_target != NULL);
+ g_return_if_fail (drag_region_widget != NULL);
+
+ gtk_drag_dest_set (drag_region_widget,
+ GTK_DEST_DEFAULT_HIGHLIGHT |
+ GTK_DEST_DEFAULT_MOTION |
+ GTK_DEST_DEFAULT_DROP,
+ drag_target, 1, /* Pass array of size 1 */
+ GDK_ACTION_COPY);
+
+ /* connect drag_region_widget's drag_received signal to chooser's callback. */
+ g_signal_connect_swapped (drag_region_widget, "drag-data-received",
+ G_CALLBACK (pika_resource_select_drag_data_received),
+ chooser);
+}
+
+/**
+ * pika_resource_chooser_set_clickable:
+ * @chooser: A [class@ResourceChooser]
+ * @widget: An interior widget that emits "clicked" signal
+ *
+ * Called by a subclass init to specialize the instance.
+ *
+ * Subclass knows its interior widget whose region when clicked
+ * should popup remote chooser.
+ * Self handles the click event.
+ *
+ * Since: 3.0
+ **/
+void
+pika_resource_chooser_set_clickable (PikaResourceChooser *chooser,
+ GtkWidget *widget)
+{
+ g_return_if_fail (PIKA_IS_RESOURCE_CHOOSER (chooser));
+ g_return_if_fail (widget != NULL);
+ g_return_if_fail (GTK_IS_WIDGET (widget));
+
+ /* Require the widget have a signal "clicked", usually a button. */
+ g_signal_connect_swapped (widget, "clicked",
+ G_CALLBACK (pika_resource_chooser_clicked),
+ chooser);
+}
+
+/**
+ * pika_resource_chooser_get_resource:
+ * @chooser: A #PikaResourceChooser
+ *
+ * Gets the currently selected resource.
+ *
+ * Returns: (transfer none): an internal copy of the resource which must not be freed.
+ *
+ * Since: 3.0
+ */
+PikaResource *
+pika_resource_chooser_get_resource (PikaResourceChooser *chooser)
+{
+ PikaResourceChooserPrivate *priv;
+
+ g_return_val_if_fail (PIKA_IS_RESOURCE_CHOOSER (chooser), NULL);
+
+ priv = pika_resource_chooser_get_instance_private (chooser);
+
+ return priv->resource;
+}
+
+/**
+ * pika_resource_chooser_set_resource:
+ * @chooser: A #PikaResourceChooser
+ * @resource: Resource to set.
+ *
+ * Sets the currently selected resource.
+ * This will select the resource in both the button and any chooser popup.
+ *
+ * Since: 3.0
+ */
+void
+pika_resource_chooser_set_resource (PikaResourceChooser *chooser,
+ PikaResource *resource)
+{
+ PikaResourceChooserPrivate *priv;
+
+ g_return_if_fail (PIKA_IS_RESOURCE_CHOOSER (chooser));
+ g_return_if_fail (resource != NULL);
+
+ priv = pika_resource_chooser_get_instance_private (chooser);
+
+ if (priv->callback)
+ {
+ /* A popup chooser dialog is already shown.
+ * Call its setter to change the selection there
+ * (since all views of the resource must be consistent.)
+ * That will call back, which will change our own view of the resource.
+ */
+ pika_resource_chooser_set_remote_dialog (chooser, resource);
+ }
+ else
+ {
+ /* Call our own setter. */
+ pika_resource_chooser_callback (resource, FALSE, chooser);
+ }
+}
+
+/**
+ * pika_resource_chooser_get_label:
+ * @widget: A [class@ResourceChooser].
+ *
+ * Returns the label widget.
+ *
+ * Returns: (transfer none): the [class@Gtk.Widget] showing the label text.
+ * Since: 3.0
+ */
+GtkWidget *
+pika_resource_chooser_get_label (PikaResourceChooser *widget)
+{
+ PikaResourceChooserPrivate *priv;
+
+ g_return_val_if_fail (PIKA_IS_RESOURCE_CHOOSER (widget), NULL);
+
+ priv = pika_resource_chooser_get_instance_private (widget);
+ return priv->label_widget;
+}
+
+
+/* private functions */
+
+static void
+pika_resource_chooser_set_remote_dialog (PikaResourceChooser *self,
+ PikaResource *resource)
+{
+ PikaResourceChooserPrivate *priv;
+ PikaResourceChooserClass *klass;
+
+ g_return_if_fail (PIKA_IS_RESOURCE_CHOOSER (self));
+ g_return_if_fail (resource != NULL);
+
+ priv = pika_resource_chooser_get_instance_private (self);
+ klass = PIKA_RESOURCE_CHOOSER_GET_CLASS (self);
+
+ g_return_if_fail (klass->resource_type != G_TYPE_INVALID);
+ g_return_if_fail (klass->resource_type == G_TYPE_FROM_INSTANCE (resource));
+
+ pika_resource_select_set (priv->callback, resource);
+}
+
+static void
+pika_resource_chooser_set_property (GObject *object,
+ guint property_id,
+ const GValue *gvalue,
+ GParamSpec *pspec)
+{
+ PikaResourceChooser *self = PIKA_RESOURCE_CHOOSER (object);
+ PikaResourceChooserPrivate *priv = pika_resource_chooser_get_instance_private (self);
+
+ switch (property_id)
+ {
+ case PROP_TITLE:
+ priv->title = g_value_dup_string (gvalue);
+ break;
+
+ case PROP_LABEL:
+ priv->label = g_value_dup_string (gvalue);
+ break;
+
+ case PROP_RESOURCE:
+ priv->resource = g_value_get_object (gvalue);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+pika_resource_chooser_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PikaResourceChooser *self = PIKA_RESOURCE_CHOOSER (object);
+ PikaResourceChooserPrivate *priv = pika_resource_chooser_get_instance_private (self);
+
+ switch (property_id)
+ {
+ case PROP_TITLE:
+ g_value_set_string (value, priv->title);
+ break;
+
+ case PROP_LABEL:
+ g_value_set_string (value, priv->label);
+ break;
+
+ case PROP_RESOURCE:
+ g_value_set_object (value, priv->resource);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+/* A callback from the remote resource select popup.
+ * When user chooses a resource.
+ * Via a temporary PDB procedure.
+ *
+ * Set self's model (priv->resource)
+ * Notify any parent widget subscribed on the "resource" property
+ * typically a prop widget.
+ * Update the view, since model changed.
+ */
+static void
+pika_resource_chooser_callback (PikaResource *resource,
+ gboolean dialog_closing,
+ gpointer user_data)
+{
+ PikaResourceChooser *self = PIKA_RESOURCE_CHOOSER (user_data);
+ PikaResourceChooserPrivate *priv = pika_resource_chooser_get_instance_private (self);
+
+ priv->resource = resource;
+
+ PIKA_RESOURCE_CHOOSER_GET_CLASS (self)->draw_interior (self);
+
+ if (dialog_closing)
+ g_clear_pointer (&priv->callback, g_free);
+
+ g_signal_emit (self, resource_button_signals[RESOURCE_SET], 0, resource, dialog_closing);
+ g_object_notify_by_pspec (G_OBJECT (self), resource_button_props[PROP_RESOURCE]);
+}
+
+static void
+pika_resource_chooser_clicked (PikaResourceChooser *self)
+{
+ PikaResourceChooserPrivate *priv = pika_resource_chooser_get_instance_private (self);
+ PikaResourceChooserClass *klass = PIKA_RESOURCE_CHOOSER_GET_CLASS (self);
+
+ if (priv->callback)
+ {
+ /* Popup already created. Calling setter raises the popup. */
+ pika_resource_chooser_set_remote_dialog (self, priv->resource);
+ }
+ else
+ {
+ GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (self));
+ GBytes *handle = NULL;
+
+ if (PIKA_IS_DIALOG (toplevel))
+ handle = pika_dialog_get_native_handle (PIKA_DIALOG (toplevel));
+
+ priv->callback = g_strdup (pika_resource_select_new (priv->title,
+ handle,
+ priv->resource,
+ klass->resource_type,
+ pika_resource_chooser_callback,
+ self,
+ NULL));
+ pika_resource_chooser_set_remote_dialog (self, priv->resource);
+ }
+}
+
+
+/* Drag methods. */
+
+static void
+pika_resource_select_drag_data_received (PikaResourceChooser *self,
+ GdkDragContext *context,
+ gint x,
+ gint y,
+ GtkSelectionData *selection,
+ guint info,
+ guint time)
+{
+ gint length = gtk_selection_data_get_length (selection);
+ gchar *str;
+
+ PikaResourceChooserClass *klass;
+
+ klass = PIKA_RESOURCE_CHOOSER_GET_CLASS (self);
+ /* Require class resource_type was initialized. */
+ g_assert (klass->resource_type != 0);
+
+ /* Drag data is a string that is the ID of the resource. */
+
+ if (gtk_selection_data_get_format (selection) != 8 || length < 1)
+ {
+ g_warning ("%s: received invalid resource data", G_STRFUNC);
+ return;
+ }
+
+ str = g_strndup ((const gchar *) gtk_selection_data_get_data (selection),
+ length);
+
+ if (g_utf8_validate (str, -1, NULL))
+ {
+ gint pid;
+ gpointer unused;
+ gint name_offset = 0;
+
+ if (sscanf (str, "%i:%p:%n", &pid, &unused, &name_offset) >= 2 &&
+ pid == pika_getpid () && name_offset > 0)
+ {
+ gchar *name = str + name_offset;
+ PikaResource *resource;
+
+ resource = pika_resource_get_by_name (klass->resource_type, name);
+ pika_resource_chooser_set_resource (self, resource);
+ }
+ }
+
+ g_free (str);
+}
diff --git a/libpika/pikaresourcechooser.h b/libpika/pikaresourcechooser.h
new file mode 100644
index 0000000..cd62d67
--- /dev/null
+++ b/libpika/pikaresourcechooser.h
@@ -0,0 +1,65 @@
+/* LIBPIKA - The PIKA Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * 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 (__PIKA_UI_H_INSIDE__) && !defined (PIKA_COMPILATION)
+#error "Only can be included directly."
+#endif
+
+#ifndef __PIKA_RESOURCE_CHOOSER_H__
+#define __PIKA_RESOURCE_CHOOSER_H__
+
+G_BEGIN_DECLS
+
+#define PIKA_TYPE_RESOURCE_CHOOSER (pika_resource_chooser_get_type ())
+G_DECLARE_DERIVABLE_TYPE (PikaResourceChooser, pika_resource_chooser, PIKA, RESOURCE_CHOOSER, GtkBox)
+
+struct _PikaResourceChooserClass
+{
+ GtkBoxClass parent_class;
+
+ /* Signals */
+ void (* resource_set) (PikaResourceChooser *chooser,
+ PikaResource *resource,
+ gboolean dialog_closing);
+
+ /* Abstract methods and class variables */
+ void (*draw_interior) (PikaResourceChooser *chooser);
+
+ GType resource_type;
+
+ /* Padding for future expansion */
+ gpointer padding[8];
+};
+
+PikaResource * pika_resource_chooser_get_resource (PikaResourceChooser *chooser);
+void pika_resource_chooser_set_resource (PikaResourceChooser *chooser,
+ PikaResource *resource);
+GtkWidget * pika_resource_chooser_get_label (PikaResourceChooser *widget);
+
+
+/* API from below, used by subclasses e.g. PikaBrushChooser */
+
+void pika_resource_chooser_set_drag_target (PikaResourceChooser *chooser,
+ GtkWidget *drag_region_widget,
+ const GtkTargetEntry *drag_target);
+void pika_resource_chooser_set_clickable (PikaResourceChooser *chooser,
+ GtkWidget *widget);
+
+G_END_DECLS
+
+#endif /* __PIKA_RESOURCE_CHOOSER_H__ */
diff --git a/libpikabase/pikachoice.c b/libpikabase/pikachoice.c
new file mode 100644
index 0000000..d28e7c6
--- /dev/null
+++ b/libpikabase/pikachoice.c
@@ -0,0 +1,398 @@
+/* LIBPIKA - The PIKA Library
+ * Copyright (C) 1995-2000 Peter Mattis and Spencer Kimball
+ *
+ * pikachoice.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
+ * .
+ */
+
+#include "config.h"
+
+#include
+
+#include "pikabasetypes.h"
+
+#include "pikachoice.h"
+#include "pikaparamspecs.h"
+
+
+typedef struct _PikaChoiceDesc
+{
+ gchar *label;
+ gchar *help;
+ gint id;
+ gboolean sensitive;
+} PikaChoiceDesc;
+
+enum
+{
+ SENSITIVITY_CHANGED,
+ LAST_SIGNAL
+};
+
+struct _PikaChoice
+{
+ GObject parent_instance;
+
+ GHashTable *choices;
+ GList *keys;
+};
+
+
+static void pika_choice_finalize (GObject *object);
+
+static void pika_choice_desc_free (PikaChoiceDesc *desc);
+
+
+G_DEFINE_TYPE (PikaChoice, pika_choice, G_TYPE_OBJECT)
+
+#define parent_class pika_choice_parent_class
+
+static guint pika_choice_signals[LAST_SIGNAL] = { 0 };
+
+static void
+pika_choice_class_init (PikaChoiceClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ pika_choice_signals[SENSITIVITY_CHANGED] =
+ g_signal_new ("sensitivity-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ 0, /*G_STRUCT_OFFSET (PikaChoiceClass, sensitivity_changed),*/
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 1,
+ G_TYPE_STRING);
+
+ object_class->finalize = pika_choice_finalize;
+}
+
+static void
+pika_choice_init (PikaChoice *choice)
+{
+ choice->choices = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
+ (GDestroyNotify) pika_choice_desc_free);
+}
+
+static void
+pika_choice_finalize (GObject *object)
+{
+ PikaChoice *choice = PIKA_CHOICE (object);
+
+ g_hash_table_unref (choice->choices);
+ g_list_free_full (choice->keys, (GDestroyNotify) g_free);
+}
+
+/* Public API */
+
+/**
+ * pika_choice_new:
+ *
+ * Returns: (transfer full): a #PikaChoice.
+ *
+ * Since: 3.0
+ **/
+PikaChoice *
+pika_choice_new (void)
+{
+ PikaChoice *choice;
+
+ choice = g_object_new (PIKA_TYPE_CHOICE, NULL);
+
+ return choice;
+}
+
+/**
+ * pika_choice_new_with_values:
+ * @nick: the first value.
+ * @id: integer ID for @nick.
+ * @label: the label of @nick.
+ * @help: longer help text for @nick.
+ * ...: more triplets of string to pre-fill the created %PikaChoice.
+ *
+ * Returns: (transfer full): a #PikaChoice.
+ *
+ * Since: 3.0
+ **/
+PikaChoice *
+pika_choice_new_with_values (const gchar *nick,
+ gint id,
+ const gchar *label,
+ const gchar *help,
+ ...)
+{
+ PikaChoice *choice;
+ va_list va_args;
+
+ g_return_val_if_fail (nick != NULL, NULL);
+ g_return_val_if_fail (label != NULL, NULL);
+
+ choice = pika_choice_new ();
+
+ va_start (va_args, help);
+
+ do
+ {
+ pika_choice_add (choice, nick, id, label, help);
+ nick = va_arg (va_args, const gchar *);
+ if (nick == NULL)
+ break;
+ id = va_arg (va_args, gint);
+ label = va_arg (va_args, const gchar *);
+ if (label == NULL)
+ {
+ g_critical ("%s: nick '%s' cannot have a NULL label.", G_STRFUNC, nick);
+ break;
+ }
+ help = va_arg (va_args, const gchar *);
+ }
+ while (TRUE);
+
+ va_end (va_args);
+
+ return choice;
+}
+
+/**
+ * pika_choice_add:
+ * @choice: the %PikaChoice.
+ * @nick: the nick of @choice.
+ * @id: optional integer ID for @nick.
+ * @label: the label of @choice.
+ * @help: optional longer help text for @nick.
+ *
+ * This procedure adds a new possible value to @choice list of values.
+ * The @id is an optional integer identifier. This can be useful for instance
+ * when you want to work with different enum values mapped to each @nick.
+ *
+ * Since: 3.0
+ **/
+void
+pika_choice_add (PikaChoice *choice,
+ const gchar *nick,
+ gint id,
+ const gchar *label,
+ const gchar *help)
+{
+ PikaChoiceDesc *desc;
+ GList *duplicate;
+
+ g_return_if_fail (label != NULL);
+
+ desc = g_new0 (PikaChoiceDesc, 1);
+ desc->id = id;
+ desc->label = g_strdup (label);
+ desc->help = help != NULL ? g_strdup (help) : NULL;
+ desc->sensitive = TRUE;
+ g_hash_table_insert (choice->choices, g_strdup (nick), desc);
+
+ duplicate = g_list_find_custom (choice->keys, nick, (GCompareFunc) g_strcmp0);
+ if (duplicate != NULL)
+ {
+ choice->keys = g_list_remove_link (choice->keys, duplicate);
+ pika_choice_desc_free (duplicate->data);
+ g_list_free (duplicate);
+ }
+ choice->keys = g_list_append (choice->keys, g_strdup (nick));
+}
+
+/**
+ * pika_choice_is_valid:
+ * @choice: a %PikaChoice.
+ * @nick: the nick to check.
+ *
+ * This procedure checks if the given @nick is valid and refers to
+ * an existing choice.
+ *
+ * Returns: Whether the choice is valid.
+ *
+ * Since: 3.0
+ **/
+gboolean
+pika_choice_is_valid (PikaChoice *choice,
+ const gchar *nick)
+{
+ PikaChoiceDesc *desc;
+
+ g_return_val_if_fail (PIKA_IS_CHOICE (choice), FALSE);
+ g_return_val_if_fail (nick != NULL, FALSE);
+
+ desc = g_hash_table_lookup (choice->choices, nick);
+ return (desc != NULL && desc->sensitive);
+}
+
+/**
+ * pika_choice_list_nicks:
+ * @choice: a %PikaChoice.
+ * @nick: the nick to check.
+ *
+ * This procedure returns the list of nicks allowed for @choice.
+ *
+ * Returns: (element-type gchar*) (transfer none): The list of @choice's nicks.
+ *
+ * Since: 3.0
+ **/
+GList *
+pika_choice_list_nicks (PikaChoice *choice)
+{
+ /* I don't use g_hash_table_get_keys() on purpose, because I want to retain
+ * the adding-time order.
+ */
+ return choice->keys;
+}
+
+/**
+ * pika_choice_get_id:
+ * @choice: a %PikaChoice.
+ * @nick: the nick to lookup.
+ *
+ * Returns: the ID of @nick.
+ *
+ * Since: 3.0
+ **/
+gint
+pika_choice_get_id (PikaChoice *choice,
+ const gchar *nick)
+{
+ PikaChoiceDesc *desc;
+
+ desc = g_hash_table_lookup (choice->choices, nick);
+ if (desc)
+ return desc->id;
+ else
+ return 0;
+}
+
+/**
+ * pika_choice_get_label:
+ * @choice: a %PikaChoice.
+ * @nick: the nick to lookup.
+ *
+ * Returns: (transfer none): the label of @nick.
+ *
+ * Since: 3.0
+ **/
+const gchar *
+pika_choice_get_label (PikaChoice *choice,
+ const gchar *nick)
+{
+ PikaChoiceDesc *desc;
+
+ desc = g_hash_table_lookup (choice->choices, nick);
+ if (desc)
+ return desc->label;
+ else
+ return NULL;
+}
+
+/**
+ * pika_choice_get_help:
+ * @choice: a %PikaChoice.
+ * @nick: the nick to lookup.
+ *
+ * Returns the longer documentation for @nick.
+ *
+ * Returns: (transfer none): the help text of @nick.
+ *
+ * Since: 3.0
+ **/
+const gchar *
+pika_choice_get_help (PikaChoice *choice,
+ const gchar *nick)
+{
+ PikaChoiceDesc *desc;
+
+ desc = g_hash_table_lookup (choice->choices, nick);
+ if (desc)
+ return desc->help;
+ else
+ return NULL;
+}
+
+/**
+ * pika_choice_get_documentation:
+ * @choice: the %PikaChoice.
+ * @nick: the possible value's nick you need documentation for.
+ * @label: (transfer none): the label of @nick.
+ * @help: (transfer none): the help text of @nick.
+ *
+ * Returns the documentation strings for @nick.
+ *
+ * Returns: %TRUE if @nick is found, %FALSE otherwise.
+ *
+ * Since: 3.0
+ **/
+gboolean
+pika_choice_get_documentation (PikaChoice *choice,
+ const gchar *nick,
+ const gchar **label,
+ const gchar **help)
+{
+ PikaChoiceDesc *desc;
+
+ desc = g_hash_table_lookup (choice->choices, nick);
+ if (desc)
+ {
+ *label = desc->label;
+ *help = desc->help;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * pika_choice_set_sensitive:
+ * @choice: the %PikaChoice.
+ * @nick: the nick to lookup.
+ *
+ * Change the sensitivity of a possible @nick. Technically a non-sensitive @nick
+ * means it cannot be chosen anymore (so [method@Gimp.Choice.is_valid] will
+ * return %FALSE; nevertheless [method@Gimp.Choice.list_nicks] and other
+ * functions to get information about a choice will still function).
+ *
+ * Returns: %TRUE if @nick is found, %FALSE otherwise.
+ *
+ * Since: 3.0
+ **/
+void
+pika_choice_set_sensitive (PikaChoice *choice,
+ const gchar *nick,
+ gboolean sensitive)
+{
+ PikaChoiceDesc *desc;
+
+ g_return_if_fail (PIKA_IS_CHOICE (choice));
+ g_return_if_fail (nick != NULL);
+
+ desc = g_hash_table_lookup (choice->choices, nick);
+ g_return_if_fail (desc != NULL);
+ if (desc->sensitive != sensitive)
+ {
+ desc->sensitive = sensitive;
+ g_signal_emit (choice, pika_choice_signals[SENSITIVITY_CHANGED], 0, nick);
+ }
+}
+
+
+/* Private functions */
+
+static void
+pika_choice_desc_free (PikaChoiceDesc *desc)
+{
+ g_free (desc->label);
+ g_free (desc->help);
+ g_free (desc);
+}
diff --git a/libpikabase/pikachoice.h b/libpikabase/pikachoice.h
new file mode 100644
index 0000000..86750d6
--- /dev/null
+++ b/libpikabase/pikachoice.h
@@ -0,0 +1,73 @@
+/* LIBPIKA - The PIKA Library
+ * Copyright (C) 1995-2000 Peter Mattis and Spencer Kimball
+ *
+ * pikachoice.h
+ * Copyright (C) 2023 Jehan
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * 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 (__PIKA_BASE_H_INSIDE__) && !defined (PIKA_BASE_COMPILATION)
+#error "Only can be included directly."
+#endif
+
+
+#ifndef __PIKA_CHOICE_H__
+#define __PIKA_CHOICE_H__
+
+G_BEGIN_DECLS
+
+
+/* For information look into the C source or the html documentation */
+
+
+#define PIKA_TYPE_CHOICE (pika_choice_get_type ())
+G_DECLARE_FINAL_TYPE (PikaChoice, pika_choice, PIKA, CHOICE, GObject)
+
+
+PikaChoice * pika_choice_new (void);
+PikaChoice * pika_choice_new_with_values (const gchar *nick,
+ gint id,
+ const gchar *label,
+ const gchar *help,
+ ...) G_GNUC_NULL_TERMINATED;
+void pika_choice_add (PikaChoice *choice,
+ const gchar *nick,
+ gint id,
+ const gchar *label,
+ const gchar *help);
+
+gboolean pika_choice_is_valid (PikaChoice *choice,
+ const gchar *nick);
+GList * pika_choice_list_nicks (PikaChoice *choice);
+gint pika_choice_get_id (PikaChoice *choice,
+ const gchar *nick);
+const gchar * pika_choice_get_label (PikaChoice *choice,
+ const gchar *nick);
+const gchar * pika_choice_get_help (PikaChoice *choice,
+ const gchar *nick);
+gboolean pika_choice_get_documentation (PikaChoice *choice,
+ const gchar *nick,
+ const gchar **label,
+ const gchar **help);
+
+void pika_choice_set_sensitive (PikaChoice *choice,
+ const gchar *nick,
+ gboolean sensitive);
+
+
+G_END_DECLS
+
+#endif /* __PIKA_CHOICE_H__ */
diff --git a/libpikawidgets/pikalabelstringwidget.c b/libpikawidgets/pikalabelstringwidget.c
new file mode 100644
index 0000000..9949392
--- /dev/null
+++ b/libpikawidgets/pikalabelstringwidget.c
@@ -0,0 +1,291 @@
+/* LIBPIKA - The PIKA Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * pikalabelstringwidget.c
+ * Copyright (C) 2023 Jehan
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library. If not, see
+ * .
+ */
+
+#include "config.h"
+
+#include
+#include
+
+#include "libpikacolor/pikacolor.h"
+#include "libpikamath/pikamath.h"
+#include "libpikabase/pikabase.h"
+
+#include "pikalabelstringwidget.h"
+
+
+/**
+ * SECTION: pikalabelstringwidget
+ * @title: PikaLabelStringWidget
+ * @short_description: Widget containing a label and a widget with a
+ * string "value" property.
+ *
+ * This widget is a subclass of #PikaLabeled.
+ **/
+
+enum
+{
+ VALUE_CHANGED,
+ LAST_SIGNAL
+};
+
+enum
+{
+ PROP_0,
+ PROP_VALUE,
+ PROP_WIDGET,
+};
+
+typedef struct _PikaLabelStringWidgetPrivate
+{
+ PikaLabeled parent_instance;
+
+ GtkWidget *widget;
+ gchar *value;
+} PikaLabelStringWidgetPrivate;
+
+static void pika_label_string_widget_constructed (GObject *object);
+static void pika_label_string_widget_finalize (GObject *object);
+static void pika_label_string_widget_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec);
+static void pika_label_string_widget_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec);
+
+static GtkWidget * pika_label_string_widget_populate (PikaLabeled *widget,
+ gint *x,
+ gint *y,
+ gint *width,
+ gint *height);
+
+G_DEFINE_TYPE_WITH_PRIVATE (PikaLabelStringWidget, pika_label_string_widget, PIKA_TYPE_LABELED)
+
+#define parent_class pika_label_string_widget_parent_class
+
+static guint pika_label_string_widget_signals[LAST_SIGNAL] = { 0 };
+
+static void
+pika_label_string_widget_class_init (PikaLabelStringWidgetClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ PikaLabeledClass *labeled_class = PIKA_LABELED_CLASS (klass);
+
+ pika_label_string_widget_signals[VALUE_CHANGED] =
+ g_signal_new ("value-changed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (PikaLabelStringWidgetClass, value_changed),
+ NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+
+ object_class->constructed = pika_label_string_widget_constructed;
+ object_class->finalize = pika_label_string_widget_finalize;
+ object_class->set_property = pika_label_string_widget_set_property;
+ object_class->get_property = pika_label_string_widget_get_property;
+
+ labeled_class->populate = pika_label_string_widget_populate;
+
+ /**
+ * PikaLabelStringWidget:value:
+ *
+ * The currently set value.
+ *
+ * Since: 3.0
+ **/
+ g_object_class_install_property (object_class, PROP_VALUE,
+ g_param_spec_string ("value", NULL,
+ "Current value",
+ NULL,
+ PIKA_PARAM_READWRITE));
+
+ /**
+ * PikaLabelStringWidget:widget:
+ *
+ * The widget holding a string property named "value".
+ *
+ * Since: 3.0
+ **/
+ g_object_class_install_property (object_class, PROP_WIDGET,
+ g_param_spec_object ("widget", NULL,
+ "String widget",
+ GTK_TYPE_WIDGET,
+ PIKA_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+pika_label_string_widget_init (PikaLabelStringWidget *widget)
+{
+}
+
+static void
+pika_label_string_widget_constructed (GObject *object)
+{
+ PikaLabelStringWidget *widget = PIKA_LABEL_STRING_WIDGET (object);
+ PikaLabelStringWidgetPrivate *priv = pika_label_string_widget_get_instance_private (widget);
+
+ G_OBJECT_CLASS (parent_class)->constructed (object);
+
+ gtk_grid_set_column_spacing (GTK_GRID (widget), 6);
+ gtk_grid_set_row_spacing (GTK_GRID (widget), 6);
+
+ /* This is important to make this object into a property widget. It
+ * will allow config object to bind the "value" property of this
+ * widget, and therefore be updated automatically.
+ */
+ g_object_bind_property (G_OBJECT (priv->widget), "value",
+ object, "value",
+ G_BINDING_BIDIRECTIONAL |
+ G_BINDING_SYNC_CREATE);
+}
+
+static void
+pika_label_string_widget_finalize (GObject *object)
+{
+ PikaLabelStringWidget *widget = PIKA_LABEL_STRING_WIDGET (object);
+ PikaLabelStringWidgetPrivate *priv = pika_label_string_widget_get_instance_private (widget);
+
+ g_free (priv->value);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+pika_label_string_widget_set_property (GObject *object,
+ guint property_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ PikaLabelStringWidget *widget = PIKA_LABEL_STRING_WIDGET (object);
+ PikaLabelStringWidgetPrivate *priv = pika_label_string_widget_get_instance_private (widget);
+
+ switch (property_id)
+ {
+ case PROP_VALUE:
+ if (g_strcmp0 (priv->value, g_value_get_string (value)) != 0)
+ {
+ g_free (priv->value);
+ priv->value = g_value_dup_string (value);
+ g_signal_emit (object, pika_label_string_widget_signals[VALUE_CHANGED], 0);
+ }
+ break;
+ case PROP_WIDGET:
+ priv->widget = g_value_get_object (value);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+pika_label_string_widget_get_property (GObject *object,
+ guint property_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ PikaLabelStringWidget *widget = PIKA_LABEL_STRING_WIDGET (object);
+ PikaLabelStringWidgetPrivate *priv = pika_label_string_widget_get_instance_private (widget);
+
+ switch (property_id)
+ {
+ case PROP_VALUE:
+ g_value_set_string (value, priv->value);
+ break;
+ case PROP_WIDGET:
+ g_value_set_object (value, priv->widget);
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static GtkWidget *
+pika_label_string_widget_populate (PikaLabeled *labeled,
+ gint *x,
+ gint *y,
+ gint *width,
+ gint *height)
+{
+ PikaLabelStringWidget *widget = PIKA_LABEL_STRING_WIDGET (labeled);
+ PikaLabelStringWidgetPrivate *priv = pika_label_string_widget_get_instance_private (widget);
+
+ gtk_grid_attach (GTK_GRID (widget), priv->widget, 1, 0, 1, 1);
+ gtk_widget_show (priv->widget);
+
+ return priv->widget;
+}
+
+/* Public Functions */
+
+/**
+ * pika_label_string_widget_new:
+ * @text: The text for the #GtkLabel.
+ * @widget: (transfer full): The #GtkWidget to use.
+ *
+ * Creates a new #PikaLabelStringWidget whose "value" property is bound to
+ * that of @widget (which must therefore have such a string property).
+ *
+ * Returns: (transfer full): The new #PikaLabelStringWidget widget.
+ **/
+GtkWidget *
+pika_label_string_widget_new (const gchar *text,
+ GtkWidget *widget)
+{
+ GtkWidget *string_widget;
+ GParamSpec *pspec;
+
+ g_return_val_if_fail ((pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (widget),
+ "value")) &&
+ (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_STRING ||
+ G_PARAM_SPEC_TYPE (pspec) == PIKA_TYPE_PARAM_CHOICE),
+ NULL);
+
+ string_widget = g_object_new (PIKA_TYPE_LABEL_STRING_WIDGET,
+ "label", text,
+ "widget", widget,
+ NULL);
+
+ return string_widget;
+}
+
+/**
+ * pika_label_string_widget_get_widget:
+ * @widget: the #PikaLabelStringWidget.
+ *
+ * Returns: (transfer none): The new #GtkWidget packed next to the label.
+ **/
+GtkWidget *
+pika_label_string_widget_get_widget (PikaLabelStringWidget *widget)
+{
+ PikaLabelStringWidgetPrivate *priv;
+
+ g_return_val_if_fail (PIKA_IS_LABEL_STRING_WIDGET (widget), NULL);
+
+ priv = pika_label_string_widget_get_instance_private (widget);
+
+ return priv->widget;
+}
diff --git a/libpikawidgets/pikalabelstringwidget.h b/libpikawidgets/pikalabelstringwidget.h
new file mode 100644
index 0000000..d17c281
--- /dev/null
+++ b/libpikawidgets/pikalabelstringwidget.h
@@ -0,0 +1,62 @@
+/* LIBPIKA - The PIKA Library
+ * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
+ *
+ * pikalabelstringwidget.h
+ * Copyright (C) 2023 Jehan
+ *
+ * This library is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * 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 (__PIKA_WIDGETS_H_INSIDE__) && !defined (PIKA_WIDGETS_COMPILATION)
+#error "Only can be included directly."
+#endif
+
+#ifndef __PIKA_LABEL_STRING_WIDGET_H__
+#define __PIKA_LABEL_STRING_WIDGET_H__
+
+#include
+
+G_BEGIN_DECLS
+
+#define PIKA_TYPE_LABEL_STRING_WIDGET (pika_label_string_widget_get_type ())
+G_DECLARE_DERIVABLE_TYPE (PikaLabelStringWidget, pika_label_string_widget, PIKA, LABEL_STRING_WIDGET, PikaLabeled)
+
+struct _PikaLabelStringWidgetClass
+{
+ PikaLabeledClass parent_class;
+
+ /* Signals */
+ void (* value_changed) (GtkWidget *string_widget);
+
+ /* Padding for future expansion */
+ void (* _pika_reserved1) (void);
+ void (* _pika_reserved2) (void);
+ void (* _pika_reserved3) (void);
+ void (* _pika_reserved4) (void);
+ void (* _pika_reserved5) (void);
+ void (* _pika_reserved6) (void);
+ void (* _pika_reserved7) (void);
+ void (* _pika_reserved8) (void);
+};
+
+GtkWidget * pika_label_string_widget_new (const gchar *text,
+ GtkWidget *widget);
+
+GtkWidget * pika_label_string_widget_get_widget (PikaLabelStringWidget *widget);
+
+
+G_END_DECLS
+
+#endif /* __PIKA_LABEL_STRING_WIDGET_H__ */
diff --git a/pdb/groups/drawable_select.pdb b/pdb/groups/drawable_select.pdb
new file mode 100644
index 0000000..bed8b9f
--- /dev/null
+++ b/pdb/groups/drawable_select.pdb
@@ -0,0 +1,113 @@
+# PIKA - Photo and Image Kooker Application
+# Copyright (C) 1995 Spencer Kimball and Peter Mattis
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+sub drawables_popup {
+ $blurb = 'Invokes the drawable selection dialog.';
+ $help = 'Opens a dialog letting a user choose an drawable.';
+
+ &jehan_pdb_misc('2023');
+
+ @inargs = (
+ { name => 'callback', type => 'string', non_empty => 1,
+ desc => 'The callback PDB proc to call when user chooses an drawable' },
+ { name => 'popup_title', type => 'string',
+ desc => 'Title of the drawable selection dialog' },
+ { name => 'drawable_type', type => 'string', non_empty => 1,
+ desc => 'The name of the PIKA_TYPE_DRAWABLE subtype' },
+ { name => 'initial_drawable', type => 'drawable', null_ok => 1, no_validate => 1,
+ desc => 'The drawable to set as the initial choice' },
+ { name => 'parent_window', type => 'bytes', null_ok => 1,
+ desc => 'An optional parent window handle for the popup to be set transient to' }
+ );
+
+ %invoke = (
+ code => <<'CODE'
+{
+ if (pika->no_interface ||
+ ! pika_pdb_lookup_procedure (pika->pdb, callback) ||
+ ! pika_pdb_dialog_new (pika, context, progress,
+ g_type_from_name (drawable_type),
+ parent_window, popup_title, callback,
+ PIKA_OBJECT (initial_drawable),
+ NULL))
+ success = FALSE;
+}
+CODE
+ );
+}
+
+sub drawables_close_popup {
+ $blurb = 'Close the drawable selection dialog.';
+ $help = 'Closes an open drawable selection dialog.';
+
+ &jehan_pdb_misc('2023');
+
+ @inargs = (
+ { name => 'callback', type => 'string', non_empty => 1,
+ desc => 'The name of the callback registered for this pop-up' }
+ );
+
+ %invoke = (
+ code => <<'CODE'
+{
+ if (pika->no_interface ||
+ ! pika_pdb_lookup_procedure (pika->pdb, callback) ||
+ ! pika_pdb_dialog_close (pika, PIKA_TYPE_DRAWABLE, callback))
+ success = FALSE;
+}
+CODE
+ );
+}
+
+sub drawables_set_popup {
+ $blurb = 'Sets the selected drawable in a drawable selection dialog.';
+ $help = $blurb;
+
+ &jehan_pdb_misc('2023');
+
+ @inargs = (
+ { name => 'callback', type => 'string', non_empty => 1,
+ desc => 'The name of the callback registered for this pop-up' },
+ { name => 'drawable', type => 'drawable', no_validate => 1,
+ desc => 'The drawable to set as selected' }
+ );
+
+ %invoke = (
+ code => <<'CODE'
+{
+ if (pika->no_interface ||
+ ! pika_pdb_lookup_procedure (pika->pdb, callback) ||
+ ! pika_pdb_dialog_set (pika, PIKA_TYPE_DRAWABLE, callback, PIKA_OBJECT (drawable), NULL))
+ success = FALSE;
+}
+CODE
+ );
+}
+
+@headers = qw("core/pika.h"
+ "core/pikadatafactory.h");
+
+@procs = qw(drawables_popup
+ drawables_close_popup
+ drawables_set_popup);
+
+%exports = (app => [@procs], lib => [@procs]);
+
+$desc = 'Drawables UI';
+$doc_title = 'pikadrawableselect';
+$doc_short_desc = 'Methods of a drawable chooser dialog';
+
+1;
diff --git a/pdb/meson-enumcode.sh b/pdb/meson-enumcode.sh
new file mode 100644
index 0000000..1cee6ed
--- /dev/null
+++ b/pdb/meson-enumcode.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+PERL="$1"
+top_srcdir="$2"
+top_builddir="$3"
+
+# Environment for the pdbgen.pl file.
+destdir=`cd "$top_srcdir" && pwd`
+export destdir
+builddir=`cd "$top_builddir" && pwd`
+export BUILD builddir
+
+cd "$top_srcdir"/
+$PERL -I "$top_builddir/pdb" -I "$top_srcdir/pdb" pdb/enumcode.pl
+RET=$?
+if [ $RET -eq 0 ]; then
+ echo "/* Generated on `date`. */" > $top_builddir/pdb/stamp-enumcode.h
+fi
+exit $RET
diff --git a/pdb/meson-enumgen.sh b/pdb/meson-enumgen.sh
new file mode 100644
index 0000000..200a55f
--- /dev/null
+++ b/pdb/meson-enumgen.sh
@@ -0,0 +1,20 @@
+#!/bin/sh
+
+PERL="$1"
+top_srcdir="$2"
+top_builddir="$3"
+
+# Environment for the pdbgen.pl file.
+destdir=`cd "$top_srcdir" && pwd`
+export destdir
+builddir=`cd "$top_builddir" && pwd`
+export BUILD builddir
+
+cd "$top_srcdir"/pdb
+shift 3
+$PERL enumgen.pl "${@}"
+RET=$?
+if [ $RET -eq 0 ]; then
+ echo "/* Generated on `date`. */" > $top_builddir/pdb/stamp-enumgen.h
+fi
+exit $RET
diff --git a/plug-ins/script-fu/scripts/test/register-fail/register-fail.scm b/plug-ins/script-fu/scripts/test/register-fail/register-fail.scm
new file mode 100644
index 0000000..cced59d
--- /dev/null
+++ b/plug-ins/script-fu/scripts/test/register-fail/register-fail.scm
@@ -0,0 +1,126 @@
+#!/usr/bin/env pika-script-fu-interpreter-3.0
+
+; A script that fails at query/registration time: ill-formed args in registration data
+
+; Tests the fix for #6157
+
+; Incomplete: FUTURE add more test cases.
+
+; One script can't test all cases.
+; Several cases are tested here, but you must edit the script,
+; putting each case to the top: the failing top case stops interpretation.
+
+; Install this script.
+; Start Gimp
+; Expect:
+; - an error in the stderr console, errors from SF re args
+; e.g. "adjustment default must be list"
+; - plugin should not install to the menus
+; - the interpreter must not crash !!!
+; - the procedure is not in ProcedureBrowser
+
+; Note errors come before Pika Error Console is ready
+
+; moot run func
+(define (script-fu-test-registration-fail ) ())
+
+
+; Case SF-ENUM not a list of two strings
+(script-fu-register "script-fu-test-registration-fail"
+ "Moot" "Moot" "lkk" "lkk" "2023" "" ; requires no image
+
+ ; ill-formed arg spec: 1 is not a string
+ SF-ENUM "foo" '("InterpolationType" 1)
+)
+
+; This should be moot (since it should fail earlier.)
+; But leave this here so when it does succeed,
+; you know earlier test failed to fail.
+(script-fu-menu-register "script-fu-test-registration-fail"
+ "/Test")
+
+
+
+; More tests below.
+; Copy them to above, and retest.
+; Can't test them all at once.
+
+
+; Case SF-ADJUSTMENT default not a list
+(script-fu-register "script-fu-test-registration-fail"
+ "Moot" "Moot" "lkk" "lkk" "2023" "" ; requires no image
+
+ ; ill-formed : 0 is not a list
+ SF-ADJUSTMENT "foo" 0
+)
+
+
+; Case: SF-ADJUSTMENT wrong list length
+; At query time, SF throws error.
+; At query time, a critical from Pika re arg default not in range
+(script-fu-register "script-fu-test-registration-fail"
+ "Moot" "Moot" "lkk" "lkk" "2023" "" ; requires no image
+
+ ; ill-formed arg spec: a list, but not proper length
+ ; Expect SF error
+ SF-ADJUSTMENT "foo" '(0 )
+)
+
+
+
+; Case SF-OPTIONS not a list
+(script-fu-register "script-fu-test-registration-fail"
+ "Moot" "Moot" "lkk" "lkk" "2023" "" ; requires no image
+
+ ; ill-formed arg spec: a list, but not proper length
+ SF-OPTION "foo" "bar"
+)
+
+
+; Case SF-OPTIONS default an empty list
+(script-fu-register "script-fu-test-registration-fail"
+ "Moot" "Moot" "lkk" "lkk" "2023" "" ; requires no image
+
+ ; semantics : empty list makes no sense
+ SF-OPTION "foo" '()
+)
+
+
+; Case SF-OPTIONS list contains non-string
+(script-fu-register "script-fu-test-registration-fail"
+ "Moot" "Moot" "lkk" "lkk" "2023" "" ; requires no image
+
+ ; 1 is not a string
+ SF-OPTION "foo" '("foo" 1)
+)
+
+
+
+; Case SF-COLOR not a list or a string
+(script-fu-register "script-fu-test-registration-fail"
+ "Moot" "Moot" "lkk" "lkk" "2023" "" ; requires no image
+
+ ; ill-formed arg spec: 1 is not a list or a string
+ SF-COLOR "foo" 1 4 5
+)
+
+; FUTURE more test cases for SF-COLOR
+
+
+; Case SF-ENUM not a list
+(script-fu-register "script-fu-test-registration-fail"
+ "Moot" "Moot" "lkk" "lkk" "2023" "" ; requires no image
+
+ ; ill-formed arg spec: "bar" is not a list
+ SF-ENUM "foo" "bar"
+)
+
+; Case SF-ENUM not a list of two strings
+(script-fu-register "script-fu-test-registration-fail"
+ "Moot" "Moot" "lkk" "lkk" "2023" "" ; requires no image
+
+ ; ill-formed arg spec: 1 is not a string
+ SF-ENUM "foo" '("InterpolationType" 1)
+)
+
+; FUTURE more test cases for SF-ENUM
\ No newline at end of file
diff --git a/po-windows-installer/be.po b/po-windows-installer/be.po
new file mode 100644
index 0000000..7c990f1
--- /dev/null
+++ b/po-windows-installer/be.po
@@ -0,0 +1,398 @@
+# Belarusian translation for pika.
+# Copyright (C) 2023 pika's COPYRIGHT HOLDER
+# This file is distributed under the same license as the pika package.
+# Yuras Shumovich , 2023.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: pika master\n"
+"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/pika/issues\n"
+"POT-Creation-Date: 2023-09-26 14:33+0000\n"
+"PO-Revision-Date: 2023-09-26 17:54+0300\n"
+"Last-Translator: Yuras Shumovich \n"
+"Language-Team: Belarusian \n"
+"Language: be\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
+"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Generator: Poedit 3.3.2\n"
+
+#: build/windows/installer/lang/setup.isl.xml.in:7
+msgid "License Agreement"
+msgstr "Ліцэнзійнае пагадненне"
+
+#: build/windows/installer/lang/setup.isl.xml.in:10
+msgid "Setup built by Jernej Simonèiè, jernej-pika@ena.si"
+msgstr ""
+"Праграму ўсталявання сабраў Ерней Сімончыч (Jernej Simončič), jernej-"
+"pika@ena.si"
+
+#: build/windows/installer/lang/setup.isl.xml.in:13
+#: build/windows/installer/lang/setup.isl.xml.in:16
+msgid "This version of PIKA requires Windows 7, or a newer version of Windows."
+msgstr ""
+"Гэтай версіі PIKA патрабуецца Windows 7 або любая пазнейшая версія Windows."
+
+#: build/windows/installer/lang/setup.isl.xml.in:23
+msgid "Development version"
+msgstr "Версія для распрацоўкі"
+
+#: build/windows/installer/lang/setup.isl.xml.in:28
+msgid ""
+"This is a development version of PIKA where some features may not be "
+"finished, or it may be unstable.%nThis version of PIKA is not intended for "
+"day-to-day work as it may be unstable, and you could lose your work.%nIf you "
+"encounter any problems, first verify that they haven't already been fixed in "
+"GIT before you contact the developers or report it in PIKA gitlab:%n_https://"
+"gitlab.gnome.org/GNOME/pika/issues%n%nDo you wish to continue with "
+"installation anyway?"
+msgstr ""
+"Гэта версія для распрацоўкі PIKA, некаторыя функцыі могуць быць "
+"незавершанымі або нестабільнымі.%nГэтая версія PIKA не прызначана для "
+"штодзённай працы, бо з-за нестабільнасці, вынікі працы могуць быць страчаны."
+"%nКалі вы сутыкнуліся з нейкімі праблемамі, спачатку трэба ўпэўніцца, што іх "
+"не выправілі на GIT і толькі пасля паведамляць аб праблемах распрацоўшчыкам "
+"PIKA на gitlab:%n_https://gitlab.gnome.org/GNOME/pika/issues%n%nУсё роўна "
+"працягваць усталяванне?"
+
+#: build/windows/installer/lang/setup.isl.xml.in:31
+#: build/windows/installer/lang/setup.isl.xml.in:54
+msgid "&Continue"
+msgstr "&Працягнуць"
+
+#: build/windows/installer/lang/setup.isl.xml.in:34
+msgid "Exit"
+msgstr "Выйсці"
+
+#: build/windows/installer/lang/setup.isl.xml.in:44
+msgid ""
+"This version of PIKA requires a processor that supports SSE instructions."
+msgstr "Гэтай версіі PIKA патрабуецца працэсар з падтрымкай інструкцый SSE."
+
+#: build/windows/installer/lang/setup.isl.xml.in:48
+msgid "Display settings problem"
+msgstr "Праблема з наладамі дысплэя"
+
+#: build/windows/installer/lang/setup.isl.xml.in:51
+msgid ""
+"Setup has detected that your Windows is not running in 32 bits-per-pixel "
+"display mode. This has been known to cause stability problems with PIKA, so "
+"it's recommended to change the display colour depth to 32BPP before "
+"continuing."
+msgstr ""
+"Праграма ўсталявання выявіла, што ваша сістэма Windows запушчана не ў рэжыме "
+"адлюстравання 32bpp. вядома, што гэта можа выклікаць нестабільную працу "
+"PIKA, таму, перш чым працягваць, рэкамендуецца змяніць глыбіню колеру на "
+"32bpp."
+
+#: build/windows/installer/lang/setup.isl.xml.in:57
+msgid "E&xit"
+msgstr "&Выхад"
+
+#: build/windows/installer/lang/setup.isl.xml.in:61
+msgid ""
+"PIKA is now ready to be installed. Click the Install now button to install "
+"using the default settings, or click the Customize button if you'd like to "
+"have more control over what gets installed."
+msgstr ""
+"Цяпер PIKA гатовы да ўсталявання. Каб усталяваць праграму з прадвызначанымі "
+"наладамі, націсніце кнопку «Усталяваць», або націсніце «Наладзіць», каб "
+"выбраць пэўныя кампаненты."
+
+#: build/windows/installer/lang/setup.isl.xml.in:64
+msgid "&Install"
+msgstr "&Усталяваць"
+
+#: build/windows/installer/lang/setup.isl.xml.in:67
+msgid "&Customize"
+msgstr "&Наладзіць"
+
+#: build/windows/installer/lang/setup.isl.xml.in:72
+msgid "Compact installation"
+msgstr "Кампактнае ўсталяванне"
+
+#: build/windows/installer/lang/setup.isl.xml.in:76
+msgid "Custom installation"
+msgstr "Выбарачнае ўсталяванне"
+
+#: build/windows/installer/lang/setup.isl.xml.in:80
+msgid "Full installation"
+msgstr "Поўнае ўсталяванне"
+
+#: build/windows/installer/lang/setup.isl.xml.in:85
+msgid "Description"
+msgstr "Апісанне"
+
+#: build/windows/installer/lang/setup.isl.xml.in:89
+msgid "PIKA"
+msgstr "PIKA"
+
+#: build/windows/installer/lang/setup.isl.xml.in:93
+msgid "PIKA and all default plug-ins"
+msgstr "PIKA і ўсе прадвызначаныя ўбудовы"
+
+#: build/windows/installer/lang/setup.isl.xml.in:97
+msgid "Run-time libraries"
+msgstr "Бібліятэкі праграмы"
+
+#: build/windows/installer/lang/setup.isl.xml.in:101
+msgid "Run-time libraries used by PIKA, including GTK Run-time Environment"
+msgstr ""
+"Бібліятэкі, якія выкарыстоўвае PIKA, у тым ліку асяроддзе выканання GTK"
+
+#: build/windows/installer/lang/setup.isl.xml.in:105
+msgid "Debug symbols"
+msgstr "Адладачныя сімвалы"
+
+#: build/windows/installer/lang/setup.isl.xml.in:109
+msgid "Include information to help with debugging PIKA"
+msgstr "Уключыць інфармацыю, якая дапамагае з адладкай PIKA"
+
+#: build/windows/installer/lang/setup.isl.xml.in:113
+msgid "MS-Windows engine for GTK"
+msgstr "Рухавік MS Windows для GTK"
+
+#: build/windows/installer/lang/setup.isl.xml.in:117
+msgid "Native Windows look for PIKA"
+msgstr "Уласнасістэмны выгляд Windows для PIKA"
+
+#: build/windows/installer/lang/setup.isl.xml.in:121
+msgid "Support for old plug-ins"
+msgstr "Падтрымка старых убудоў"
+
+#: build/windows/installer/lang/setup.isl.xml.in:125
+msgid "Install libraries needed by old third-party plug-ins"
+msgstr "Усталяваць бібліятэкі, патрэбныя старым убудовам з пабочных крыніц"
+
+#: build/windows/installer/lang/setup.isl.xml.in:129
+#: build/windows/installer/lang/setup.isl.xml.in:133
+msgid "Translations"
+msgstr "Пераклады"
+
+#: build/windows/installer/lang/setup.isl.xml.in:137
+msgid "Python scripting"
+msgstr "Скрыпты Python"
+
+#: build/windows/installer/lang/setup.isl.xml.in:141
+msgid "Allows you to use PIKA plugins written in Python scripting language."
+msgstr "Дазваляе выкарыстоўваць убудовы напісаныя на мове скрыптоў Python."
+
+#: build/windows/installer/lang/setup.isl.xml.in:145
+msgid "Lua scripting"
+msgstr "Скрыпты Lua"
+
+#: build/windows/installer/lang/setup.isl.xml.in:149
+msgid "Allows you to use PIKA plugins written in Lua scripting language."
+msgstr "Дазваляе выкарыстоўваць убудовы напісаныя на мове скрыптоў Lua."
+
+#: build/windows/installer/lang/setup.isl.xml.in:153
+msgid "MyPaint brushes"
+msgstr "Пэндзлі MyPaint"
+
+#: build/windows/installer/lang/setup.isl.xml.in:157
+msgid "Install the default set of MyPaint brushes"
+msgstr "Усталяваць прадвызначаны набор пэндзляў MyPaint"
+
+#: build/windows/installer/lang/setup.isl.xml.in:161
+msgid "PostScript support"
+msgstr "Падтрымка PostScript"
+
+#: build/windows/installer/lang/setup.isl.xml.in:165
+msgid "Allow PIKA to load PostScript files"
+msgstr "Дазваляе PIKA загружаць файлы PostScript"
+
+#: build/windows/installer/lang/setup.isl.xml.in:169
+msgid "Support for 32-bit plug-ins"
+msgstr "Падтрымка 32-бітных убудоў"
+
+#: build/windows/installer/lang/setup.isl.xml.in:173
+msgid ""
+"Include files necessary for using 32-bit plug-ins.%nRequired for Python "
+"support."
+msgstr ""
+"Уключыць файлы, неабходныя для выкарыстання 32-бітных плагінаў.%nПатрабуюцца "
+"для падтрымкі Python."
+
+#: build/windows/installer/lang/setup.isl.xml.in:178
+msgid "Additional icons:"
+msgstr "Дадатковыя значкі:"
+
+#: build/windows/installer/lang/setup.isl.xml.in:182
+msgid "Create a &desktop icon"
+msgstr "Стварыць значок на працоўным &стале"
+
+#: build/windows/installer/lang/setup.isl.xml.in:186
+msgid "Create a &Quick Launch icon"
+msgstr "Стварыць значок на &панэлі хуткага запуску"
+
+#: build/windows/installer/lang/setup.isl.xml.in:190
+msgid "Remove previous PIKA version"
+msgstr "Выдаліць папярэднюю версію PIKA"
+
+#: build/windows/installer/lang/setup.isl.xml.in:195
+msgid ""
+"There was a problem updating PIKA's environment in %1. If you get any errors "
+"loading the plug-ins, try uninstalling and re-installing PIKA."
+msgstr ""
+"Узнікла праблема падчас абнаўлення асяроддзя PIKA у %1. Калі вы атрымаеце "
+"памылкі падчас загрузкі ўбудоў, паспрабуйце выдаліць PIKA і потым зноў "
+"ўсталяваць."
+
+#: build/windows/installer/lang/setup.isl.xml.in:199
+msgid "Error extracting temporary data."
+msgstr "Памылка вымання часовых даных."
+
+#: build/windows/installer/lang/setup.isl.xml.in:203
+msgid "Error updating Python interpreter info."
+msgstr "Памылка абнаўлення інфармацыі пра інтэрпрэтатар Python."
+
+#: build/windows/installer/lang/setup.isl.xml.in:207
+msgid "Error updating MyPaint brushes info."
+msgstr "Памылка абнаўлення інфармацыі пра пэндзлі MyPaint."
+
+#: build/windows/installer/lang/setup.isl.xml.in:211
+msgid "There was an error updating %1."
+msgstr "Узнікла памылка падчас абнаўлення %1."
+
+#: build/windows/installer/lang/setup.isl.xml.in:215
+msgid "There was an error updating PIKA's configuration file %1."
+msgstr "Узнікла памылка падчас абнаўлення файла канфігурацыі PIKA %1."
+
+#: build/windows/installer/lang/setup.isl.xml.in:220
+msgid "Edit with PIKA"
+msgstr "Змяніць праз PIKA"
+
+#: build/windows/installer/lang/setup.isl.xml.in:225
+msgid "Select file associations"
+msgstr "Суаднясенне з файламі"
+
+#: build/windows/installer/lang/setup.isl.xml.in:229
+msgid "Extensions:"
+msgstr "Пашырэнні:"
+
+#: build/windows/installer/lang/setup.isl.xml.in:233
+msgid "Select the file types you wish to associate with PIKA"
+msgstr "Выберыце тыпы файлаў, якія вы хочаце суаднесці з PIKA"
+
+#: build/windows/installer/lang/setup.isl.xml.in:237
+msgid ""
+"This will make selected files open in PIKA when you double-click them in "
+"Explorer."
+msgstr ""
+"Такім чынам выбраныя файлы, пры падвойным націсканні ў Правадніку, будуць "
+"адкрывацца праз PIKA."
+
+#: build/windows/installer/lang/setup.isl.xml.in:241
+msgid "Select &All"
+msgstr "Вылучыць &усе"
+
+#: build/windows/installer/lang/setup.isl.xml.in:245
+msgid "Unselect &All"
+msgstr "Зняць вылучэнне з &усіх"
+
+#: build/windows/installer/lang/setup.isl.xml.in:249
+msgid "Select &Unused"
+msgstr "Вылучыць &нявыкарыстаныя"
+
+#: build/windows/installer/lang/setup.isl.xml.in:254
+msgid "File types to associate with PIKA:"
+msgstr "Тыпы файлаў, суаднесеныя PIKA:"
+
+#: build/windows/installer/lang/setup.isl.xml.in:258
+msgid "Removing previous version of PIKA:"
+msgstr "Выдаленне папярэдняй версіі PIKA:"
+
+#: build/windows/installer/lang/setup.isl.xml.in:262
+msgid ""
+"PIKA %1 cannot be installed over your currently installed PIKA version, and "
+"the automatic uninstall of old version has failed.%n%nPlease remove the "
+"previous version of PIKA yourself before installing this version in %2, or "
+"choose a Custom install, and select a different installation folder.%n%nThe "
+"Setup will now exit."
+msgstr ""
+"Немагчыма ўсталяваць PIKA %1 на ўсталяваную ў цяперашні час версію, і не "
+"ўдалося выдаліць старую версію аўтаматычна.%n%nПерш чым усталяваць у %2 "
+"гэтую версію PIKA, выдаліце ўручную папярэднюю, або націсніце кнопку "
+"«Наладзіць» і выберыце іншую папку ўсталявання.%n%nЗараз праграма "
+"ўсталявання завершыць працу."
+
+#: build/windows/installer/lang/setup.isl.xml.in:266
+msgid ""
+"PIKA %1 cannot be installed over your currently installed PIKA version, and "
+"Setup couldn't determine how to remove the old version automatically."
+"%n%nPlease remove the previous version of PIKA and any add-ons yourself "
+"before installing this version in %2, or choose a Custom install, and select "
+"a different installation folder.%n%nThe Setup will now exit."
+msgstr ""
+"Немагчыма ўсталяваць PIKA %1 на ўсталяваную ў цяперашні час версію, і "
+"праграма ўсталявання не можа вызначыць як выдаліць старую версію аўтаматычна."
+"%n%nПерш чым усталяваць у %2 гэтую версію PIKA, выдаліце ўручную папярэднюю "
+"версію і ўсе ўбудовы,, або націсніце кнопку «Наладзіць» і выберыце іншую "
+"папку ўсталявання.%n%nЗараз праграма ўсталявання завершыць працу."
+
+#: build/windows/installer/lang/setup.isl.xml.in:270
+msgid ""
+"Previous PIKA version was removed successfully, but Windows has to be "
+"restarted before the Setup can continue.%n%nAfter restarting your computer, "
+"Setup will continue next time an administrator logs in."
+msgstr ""
+"Папярэдняя версія PIKA паспяхова выдалена, але патрабуецца перазапусціць "
+"Windows, каб працягнуць ўсталяванне.%n%nПасля перазапуску камп'ютара, "
+"ўсталяванне працягнецца, калі адміністратар увойдзе ў сістэму."
+
+#: build/windows/installer/lang/setup.isl.xml.in:275
+msgid "There was an error restarting the Setup. (%1)"
+msgstr "Узнікла памылка падчас паўторнага запуску праграмы ўсталявання (%1)"
+
+#: build/windows/installer/lang/setup.isl.xml.in:279
+msgid "Cleaning up old files..."
+msgstr "Ачыстка старых файлаў..."
+
+#: build/windows/installer/lang/setup.isl.xml.in:284
+msgid "Remember: PIKA is Free Software.%n%nPlease visit"
+msgstr "Помніце, PIKA – свабоднае праграмнае забеспячэнне.%n%nНаведайце сайт"
+
+#: build/windows/installer/lang/setup.isl.xml.in:288
+msgid "for free updates."
+msgstr "дзеля бясплатных абнаўленняў."
+
+#: build/windows/installer/lang/setup.isl.xml.in:292
+msgid "Setting up file associations..."
+msgstr "Наладжваецца суаднясенне з тыпамі файлаў..."
+
+#: build/windows/installer/lang/setup.isl.xml.in:295
+msgid "Setting up environment for PIKA Python extension..."
+msgstr "Наладжваецца асяроддзе для ўбудоў PIKA, напісаных на Python..."
+
+#: build/windows/installer/lang/setup.isl.xml.in:298
+msgid "Setting up MyPaint brushes..."
+msgstr "Наладжваюцца пэндзлі MyPaint."
+
+#: build/windows/installer/lang/setup.isl.xml.in:301
+msgid "Setting up PIKA environment..."
+msgstr "Наладжваецца асяроддзе PIKA..."
+
+#: build/windows/installer/lang/setup.isl.xml.in:304
+msgid "Setting up PIKA configuration for 32-bit plug-in support..."
+msgstr "Наладжваецца канфігурацыя PIKA для падтрымкі 32-бітных убудоў..."
+
+#: build/windows/installer/lang/setup.isl.xml.in:309
+msgid "Launch PIKA"
+msgstr "Запусціць PIKA"
+
+#: build/windows/installer/lang/setup.isl.xml.in:314
+msgid "Removing add-on"
+msgstr "Выдаленне дадатковых кампанентаў"
+
+#: build/windows/installer/lang/setup.isl.xml.in:318
+msgid "Internal error (%1)."
+msgstr "Унутраная памылка (%1)."
+
+#: build/windows/installer/lang/setup.isl.xml.in:323
+msgid ""
+"PIKA does not appear to be installed in the selected directory. Continue "
+"anyway?"
+msgstr ""
+"Падобна, што PIKA не ўсталяваны ў выбраным каталогу. Усё роўна працягнуць?"