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 не ўсталяваны ў выбраным каталогу. Усё роўна працягнуць?"