2019-09-21 01:24:40 +08:00
|
|
|
/* GIMP - The GNU Image Manipulation Program
|
|
|
|
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
|
|
|
*
|
|
|
|
* gimpproceduredialog.c
|
|
|
|
* Copyright (C) 2019 Michael Natterer <mitch@gimp.org>
|
2020-11-03 05:42:17 +08:00
|
|
|
* Copyright (C) 2020 Jehan
|
2019-09-21 01:24:40 +08:00
|
|
|
*
|
|
|
|
* 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 <https://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
|
|
|
#include <gegl.h>
|
|
|
|
#include <gtk/gtk.h>
|
|
|
|
|
|
|
|
#include "libgimpwidgets/gimpwidgets.h"
|
|
|
|
|
|
|
|
#include "gimp.h"
|
|
|
|
#include "gimpui.h"
|
|
|
|
|
2019-09-27 01:06:17 +08:00
|
|
|
#include "gimpprocedureconfig-private.h"
|
|
|
|
|
2019-09-21 01:24:40 +08:00
|
|
|
#include "libgimp-intl.h"
|
|
|
|
|
|
|
|
|
|
|
|
enum
|
|
|
|
{
|
|
|
|
PROP_0,
|
|
|
|
PROP_PROCEDURE,
|
|
|
|
PROP_CONFIG,
|
|
|
|
N_PROPS
|
|
|
|
};
|
|
|
|
|
|
|
|
#define RESPONSE_RESET 1
|
|
|
|
|
|
|
|
|
|
|
|
struct _GimpProcedureDialogPrivate
|
|
|
|
{
|
|
|
|
GimpProcedure *procedure;
|
|
|
|
GimpProcedureConfig *config;
|
2019-09-27 01:06:17 +08:00
|
|
|
GimpProcedureConfig *initial_config;
|
|
|
|
|
|
|
|
GtkWidget *reset_popover;
|
2021-01-08 04:59:59 +08:00
|
|
|
GtkWidget *load_settings_button;
|
2020-11-03 05:42:17 +08:00
|
|
|
|
|
|
|
GHashTable *widgets;
|
2020-11-20 08:29:08 +08:00
|
|
|
GHashTable *mnemonics;
|
|
|
|
GHashTable *core_mnemonics;
|
2020-11-06 00:46:02 +08:00
|
|
|
GtkSizeGroup *label_group;
|
2021-04-07 02:10:46 +08:00
|
|
|
|
|
|
|
GHashTable *sensitive_data;
|
2019-09-21 01:24:40 +08:00
|
|
|
};
|
|
|
|
|
2021-04-07 02:10:46 +08:00
|
|
|
typedef struct GimpProcedureDialogSensitiveData
|
|
|
|
{
|
|
|
|
gboolean sensitive;
|
|
|
|
|
|
|
|
GObject *config;
|
|
|
|
gchar *config_property;
|
|
|
|
gboolean config_invert;
|
|
|
|
} GimpProcedureDialogSensitiveData;
|
|
|
|
|
2019-09-21 01:24:40 +08:00
|
|
|
|
2020-11-21 08:15:15 +08:00
|
|
|
static void gimp_procedure_dialog_constructed (GObject *object);
|
2019-09-27 01:06:17 +08:00
|
|
|
static void gimp_procedure_dialog_dispose (GObject *object);
|
|
|
|
static void gimp_procedure_dialog_set_property (GObject *object,
|
|
|
|
guint property_id,
|
|
|
|
const GValue *value,
|
|
|
|
GParamSpec *pspec);
|
|
|
|
static void gimp_procedure_dialog_get_property (GObject *object,
|
|
|
|
guint property_id,
|
|
|
|
GValue *value,
|
|
|
|
GParamSpec *pspec);
|
|
|
|
|
2020-11-21 08:15:15 +08:00
|
|
|
static void gimp_procedure_dialog_real_fill_list (GimpProcedureDialog *dialog,
|
|
|
|
GimpProcedure *procedure,
|
|
|
|
GimpProcedureConfig *config,
|
|
|
|
GList *properties);
|
|
|
|
|
2019-09-27 01:06:17 +08:00
|
|
|
static void gimp_procedure_dialog_reset_initial (GtkWidget *button,
|
|
|
|
GimpProcedureDialog *dialog);
|
|
|
|
static void gimp_procedure_dialog_reset_factory (GtkWidget *button,
|
|
|
|
GimpProcedureDialog *dialog);
|
|
|
|
static void gimp_procedure_dialog_load_defaults (GtkWidget *button,
|
|
|
|
GimpProcedureDialog *dialog);
|
|
|
|
static void gimp_procedure_dialog_save_defaults (GtkWidget *button,
|
|
|
|
GimpProcedureDialog *dialog);
|
2019-09-21 01:24:40 +08:00
|
|
|
|
2020-11-20 08:29:08 +08:00
|
|
|
static gboolean gimp_procedure_dialog_check_mnemonic (GimpProcedureDialog *dialog,
|
|
|
|
GtkWidget *widget,
|
|
|
|
const gchar *id,
|
|
|
|
const gchar *core_id);
|
2020-11-24 22:00:34 +08:00
|
|
|
static GtkWidget *
|
|
|
|
gimp_procedure_dialog_fill_container_list (GimpProcedureDialog *dialog,
|
|
|
|
const gchar *container_id,
|
|
|
|
GtkContainer *container,
|
|
|
|
GList *properties);
|
2019-09-21 01:24:40 +08:00
|
|
|
|
2021-04-07 02:10:46 +08:00
|
|
|
static void gimp_procedure_dialog_sensitive_data_free (GimpProcedureDialogSensitiveData *data);
|
|
|
|
|
|
|
|
|
2019-09-21 01:24:40 +08:00
|
|
|
G_DEFINE_TYPE_WITH_PRIVATE (GimpProcedureDialog, gimp_procedure_dialog,
|
|
|
|
GIMP_TYPE_DIALOG)
|
|
|
|
|
|
|
|
#define parent_class gimp_procedure_dialog_parent_class
|
|
|
|
|
|
|
|
static GParamSpec *props[N_PROPS] = { NULL, };
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
gimp_procedure_dialog_class_init (GimpProcedureDialogClass *klass)
|
|
|
|
{
|
|
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
|
2020-11-21 08:15:15 +08:00
|
|
|
object_class->constructed = gimp_procedure_dialog_constructed;
|
2019-09-21 01:24:40 +08:00
|
|
|
object_class->dispose = gimp_procedure_dialog_dispose;
|
|
|
|
object_class->get_property = gimp_procedure_dialog_get_property;
|
|
|
|
object_class->set_property = gimp_procedure_dialog_set_property;
|
|
|
|
|
2020-11-21 08:15:15 +08:00
|
|
|
klass->fill_list = gimp_procedure_dialog_real_fill_list;
|
|
|
|
|
2019-09-21 01:24:40 +08:00
|
|
|
props[PROP_PROCEDURE] =
|
2020-01-06 01:37:58 +08:00
|
|
|
g_param_spec_object ("procedure",
|
|
|
|
"Procedure",
|
|
|
|
"The GimpProcedure this dialog is used with",
|
2019-09-21 01:24:40 +08:00
|
|
|
GIMP_TYPE_PROCEDURE,
|
2020-11-21 08:15:15 +08:00
|
|
|
GIMP_PARAM_READWRITE |
|
|
|
|
G_PARAM_CONSTRUCT);
|
2019-09-21 01:24:40 +08:00
|
|
|
|
|
|
|
props[PROP_CONFIG] =
|
2020-01-06 01:37:58 +08:00
|
|
|
g_param_spec_object ("config",
|
|
|
|
"Config",
|
2020-01-06 01:57:17 +08:00
|
|
|
"The GimpProcedureConfig this dialog is editing",
|
2019-09-21 01:24:40 +08:00
|
|
|
GIMP_TYPE_PROCEDURE_CONFIG,
|
|
|
|
GIMP_PARAM_READWRITE |
|
|
|
|
G_PARAM_CONSTRUCT);
|
|
|
|
|
|
|
|
g_object_class_install_properties (object_class, N_PROPS, props);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gimp_procedure_dialog_init (GimpProcedureDialog *dialog)
|
|
|
|
{
|
|
|
|
dialog->priv = gimp_procedure_dialog_get_instance_private (dialog);
|
2020-11-03 05:42:17 +08:00
|
|
|
|
2021-04-07 02:10:46 +08:00
|
|
|
dialog->priv->widgets = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
|
|
|
|
dialog->priv->mnemonics = g_hash_table_new_full (g_direct_hash, NULL, NULL, g_free);
|
2020-11-20 08:29:08 +08:00
|
|
|
dialog->priv->core_mnemonics = g_hash_table_new_full (g_direct_hash, NULL, NULL, g_free);
|
2021-04-07 02:10:46 +08:00
|
|
|
dialog->priv->label_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
|
|
|
|
dialog->priv->sensitive_data = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
|
|
|
|
(GDestroyNotify) gimp_procedure_dialog_sensitive_data_free);
|
2019-09-21 01:24:40 +08:00
|
|
|
}
|
|
|
|
|
2020-11-21 08:15:15 +08:00
|
|
|
static void
|
|
|
|
gimp_procedure_dialog_constructed (GObject *object)
|
|
|
|
{
|
|
|
|
GimpProcedureDialog *dialog;
|
|
|
|
GimpProcedure *procedure;
|
|
|
|
const gchar *ok_label;
|
|
|
|
GtkWidget *hbox;
|
|
|
|
GtkWidget *button;
|
2021-01-07 01:16:34 +08:00
|
|
|
GtkWidget *widget;
|
|
|
|
GtkWidget *box;
|
2020-11-21 08:15:15 +08:00
|
|
|
GtkWidget *content_area;
|
|
|
|
gchar *role;
|
|
|
|
|
|
|
|
G_OBJECT_CLASS (parent_class)->constructed (object);
|
|
|
|
|
|
|
|
dialog = GIMP_PROCEDURE_DIALOG (object);
|
|
|
|
procedure = dialog->priv->procedure;
|
|
|
|
|
|
|
|
role = g_strdup_printf ("gimp-%s", gimp_procedure_get_name (procedure));
|
|
|
|
g_object_set (object,
|
|
|
|
"role", role,
|
|
|
|
NULL);
|
|
|
|
g_free (role);
|
|
|
|
|
|
|
|
if (GIMP_IS_LOAD_PROCEDURE (procedure))
|
|
|
|
ok_label = _("_Open");
|
|
|
|
else if (GIMP_IS_SAVE_PROCEDURE (procedure))
|
|
|
|
ok_label = _("_Export");
|
|
|
|
else
|
|
|
|
ok_label = _("_OK");
|
|
|
|
|
2021-01-07 01:16:34 +08:00
|
|
|
/* Reset button packaged with a down-arrow icon to show it pops up
|
|
|
|
* more choices.
|
|
|
|
*/
|
|
|
|
button = gtk_button_new ();
|
|
|
|
box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2);
|
|
|
|
|
|
|
|
widget = gtk_label_new_with_mnemonic (_("_Reset"));
|
|
|
|
gtk_label_set_mnemonic_widget (GTK_LABEL (widget), button);
|
|
|
|
gtk_box_pack_start (GTK_BOX (box), widget, FALSE, FALSE, 1);
|
|
|
|
gtk_widget_show (widget);
|
|
|
|
|
|
|
|
widget = gtk_image_new_from_icon_name (GIMP_ICON_GO_DOWN, GTK_ICON_SIZE_MENU);
|
|
|
|
gtk_box_pack_start (GTK_BOX (box), widget, FALSE, FALSE, 1);
|
|
|
|
gtk_widget_show (widget);
|
|
|
|
|
|
|
|
gtk_container_add (GTK_CONTAINER (button), box);
|
|
|
|
gtk_widget_show (box);
|
|
|
|
|
|
|
|
gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, RESPONSE_RESET);
|
|
|
|
gtk_widget_show (button);
|
2020-11-21 08:15:15 +08:00
|
|
|
gimp_procedure_dialog_check_mnemonic (GIMP_PROCEDURE_DIALOG (dialog), button, NULL, "reset");
|
2021-01-07 01:16:34 +08:00
|
|
|
|
|
|
|
/* Cancel and OK buttons. */
|
2020-11-21 08:15:15 +08:00
|
|
|
button = gimp_dialog_add_button (GIMP_DIALOG (dialog),
|
|
|
|
_("_Cancel"), GTK_RESPONSE_CANCEL);
|
|
|
|
gimp_procedure_dialog_check_mnemonic (GIMP_PROCEDURE_DIALOG (dialog), button, NULL, "cancel");
|
|
|
|
button = gimp_dialog_add_button (GIMP_DIALOG (dialog),
|
|
|
|
ok_label, GTK_RESPONSE_OK);
|
|
|
|
gimp_procedure_dialog_check_mnemonic (GIMP_PROCEDURE_DIALOG (dialog), button, NULL, "ok");
|
2020-11-26 08:57:53 +08:00
|
|
|
/* OK button is the default action and has focus from start.
|
|
|
|
* This allows to just accept quickly whatever default values.
|
|
|
|
*/
|
|
|
|
gtk_widget_set_can_default (button, TRUE);
|
|
|
|
gtk_widget_grab_focus (button);
|
|
|
|
gtk_widget_grab_default (button);
|
2020-11-21 08:15:15 +08:00
|
|
|
|
|
|
|
gimp_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
|
|
|
|
GTK_RESPONSE_OK,
|
|
|
|
RESPONSE_RESET,
|
|
|
|
GTK_RESPONSE_CANCEL,
|
|
|
|
-1);
|
|
|
|
|
|
|
|
gimp_window_set_transient (GTK_WINDOW (dialog));
|
|
|
|
|
|
|
|
/* Main content area. */
|
|
|
|
content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
|
|
|
|
gtk_container_set_border_width (GTK_CONTAINER (content_area), 12);
|
|
|
|
gtk_box_set_spacing (GTK_BOX (content_area), 3);
|
|
|
|
|
|
|
|
/* Bottom box buttons with small additional padding. */
|
|
|
|
hbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL);
|
|
|
|
gtk_box_set_spacing (GTK_BOX (hbox), 6);
|
|
|
|
gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_START);
|
|
|
|
gtk_box_pack_end (GTK_BOX (content_area), hbox, FALSE, FALSE, 0);
|
|
|
|
gtk_container_child_set (GTK_CONTAINER (content_area), hbox,
|
|
|
|
"padding", 3, NULL);
|
|
|
|
gtk_widget_show (hbox);
|
|
|
|
|
2021-01-08 04:59:59 +08:00
|
|
|
button = gtk_button_new_with_mnemonic (_("_Load Saved Settings"));
|
|
|
|
gtk_widget_set_tooltip_text (button, _("Load settings saved with \"Save Settings\" button"));
|
2020-11-21 08:15:15 +08:00
|
|
|
gimp_procedure_dialog_check_mnemonic (GIMP_PROCEDURE_DIALOG (dialog), button, NULL, "load-defaults");
|
|
|
|
gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
|
|
|
|
gtk_widget_show (button);
|
|
|
|
|
|
|
|
g_signal_connect (button, "clicked",
|
|
|
|
G_CALLBACK (gimp_procedure_dialog_load_defaults),
|
|
|
|
dialog);
|
2021-01-08 04:59:59 +08:00
|
|
|
gtk_widget_set_sensitive (button,
|
|
|
|
gimp_procedure_config_has_default (dialog->priv->config));
|
|
|
|
dialog->priv->load_settings_button = button;
|
2020-11-21 08:15:15 +08:00
|
|
|
|
2021-01-08 04:59:59 +08:00
|
|
|
button = gtk_button_new_with_mnemonic (_("_Save Settings"));
|
|
|
|
gtk_widget_set_tooltip_text (button, _("Store current settings for later reuse"));
|
2020-11-21 08:15:15 +08:00
|
|
|
gimp_procedure_dialog_check_mnemonic (GIMP_PROCEDURE_DIALOG (dialog), button, NULL, "save-defaults");
|
|
|
|
gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0);
|
|
|
|
gtk_widget_show (button);
|
|
|
|
|
|
|
|
g_signal_connect (button, "clicked",
|
|
|
|
G_CALLBACK (gimp_procedure_dialog_save_defaults),
|
|
|
|
dialog);
|
|
|
|
}
|
|
|
|
|
2019-09-21 01:24:40 +08:00
|
|
|
static void
|
|
|
|
gimp_procedure_dialog_dispose (GObject *object)
|
|
|
|
{
|
|
|
|
GimpProcedureDialog *dialog = GIMP_PROCEDURE_DIALOG (object);
|
|
|
|
|
|
|
|
g_clear_object (&dialog->priv->procedure);
|
|
|
|
g_clear_object (&dialog->priv->config);
|
2019-09-27 01:06:17 +08:00
|
|
|
g_clear_object (&dialog->priv->initial_config);
|
|
|
|
|
|
|
|
g_clear_pointer (&dialog->priv->reset_popover, gtk_widget_destroy);
|
2020-11-20 08:29:08 +08:00
|
|
|
|
|
|
|
g_clear_pointer (&dialog->priv->widgets, g_hash_table_destroy);
|
|
|
|
g_clear_pointer (&dialog->priv->mnemonics, g_hash_table_destroy);
|
|
|
|
g_clear_pointer (&dialog->priv->core_mnemonics, g_hash_table_destroy);
|
2020-11-03 05:42:17 +08:00
|
|
|
|
2021-04-07 02:10:46 +08:00
|
|
|
g_clear_pointer (&dialog->priv->sensitive_data, g_hash_table_destroy);
|
|
|
|
|
2020-11-06 00:46:02 +08:00
|
|
|
g_clear_object (&dialog->priv->label_group);
|
2019-09-21 01:24:40 +08:00
|
|
|
|
|
|
|
G_OBJECT_CLASS (parent_class)->dispose (object);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gimp_procedure_dialog_set_property (GObject *object,
|
|
|
|
guint property_id,
|
|
|
|
const GValue *value,
|
|
|
|
GParamSpec *pspec)
|
|
|
|
{
|
|
|
|
GimpProcedureDialog *dialog = GIMP_PROCEDURE_DIALOG (object);
|
|
|
|
|
|
|
|
switch (property_id)
|
|
|
|
{
|
|
|
|
case PROP_PROCEDURE:
|
|
|
|
dialog->priv->procedure = g_value_dup_object (value);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PROP_CONFIG:
|
|
|
|
dialog->priv->config = g_value_dup_object (value);
|
2019-09-27 01:06:17 +08:00
|
|
|
|
|
|
|
if (dialog->priv->config)
|
|
|
|
dialog->priv->initial_config =
|
|
|
|
gimp_config_duplicate (GIMP_CONFIG (dialog->priv->config));
|
2019-09-21 01:24:40 +08:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gimp_procedure_dialog_get_property (GObject *object,
|
|
|
|
guint property_id,
|
|
|
|
GValue *value,
|
|
|
|
GParamSpec *pspec)
|
|
|
|
{
|
|
|
|
GimpProcedureDialog *dialog = GIMP_PROCEDURE_DIALOG (object);
|
|
|
|
|
|
|
|
switch (property_id)
|
|
|
|
{
|
|
|
|
case PROP_PROCEDURE:
|
|
|
|
g_value_set_object (value, dialog->priv->procedure);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PROP_CONFIG:
|
|
|
|
g_value_set_object (value, dialog->priv->config);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-21 08:15:15 +08:00
|
|
|
static void
|
|
|
|
gimp_procedure_dialog_real_fill_list (GimpProcedureDialog *dialog,
|
|
|
|
GimpProcedure *procedure,
|
|
|
|
GimpProcedureConfig *config,
|
|
|
|
GList *properties)
|
|
|
|
{
|
|
|
|
GtkWidget *content_area;
|
|
|
|
GList *iter;
|
|
|
|
|
|
|
|
content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
|
|
|
|
|
|
|
|
for (iter = properties; iter; iter = iter->next)
|
|
|
|
{
|
|
|
|
GtkWidget *widget;
|
|
|
|
|
|
|
|
widget = gimp_procedure_dialog_get_widget (dialog, iter->data, G_TYPE_NONE);
|
|
|
|
if (widget)
|
|
|
|
{
|
|
|
|
/* Reference the widget because the hash table will
|
|
|
|
* unreference it anyway when getting destroyed so we don't
|
|
|
|
* want to give the only reference to the parent widget.
|
|
|
|
*/
|
|
|
|
g_object_ref (widget);
|
|
|
|
gtk_box_pack_start (GTK_BOX (content_area), widget, TRUE, TRUE, 0);
|
|
|
|
gtk_widget_show (widget);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-02-02 22:53:34 +08:00
|
|
|
/**
|
|
|
|
* gimp_procedure_dialog_new:
|
|
|
|
* @procedure: the associated #GimpProcedure.
|
|
|
|
* @config: a #GimpProcedureConfig from which properties will be
|
|
|
|
* turned into widgets.
|
2021-02-03 00:14:43 +08:00
|
|
|
* @title: (nullable): a dialog title.
|
2021-02-02 22:53:34 +08:00
|
|
|
*
|
|
|
|
* Creates a new dialog for @procedure using widgets generated from
|
|
|
|
* properties of @config.
|
2021-02-03 00:14:43 +08:00
|
|
|
* A %NULL title will only be accepted if a menu label was set with
|
|
|
|
* gimp_procedure_set_menu_label() (this menu label will then be used as
|
|
|
|
* dialog title instead). If neither an explicit label nor a @procedure
|
|
|
|
* menu label was set, the call will fail.
|
2021-02-02 22:53:34 +08:00
|
|
|
*
|
|
|
|
* Returns: (transfer full): the newly created #GimpProcedureDialog.
|
|
|
|
*/
|
2019-09-21 01:24:40 +08:00
|
|
|
GtkWidget *
|
|
|
|
gimp_procedure_dialog_new (GimpProcedure *procedure,
|
|
|
|
GimpProcedureConfig *config,
|
|
|
|
const gchar *title)
|
|
|
|
{
|
|
|
|
GtkWidget *dialog;
|
2021-02-03 00:14:43 +08:00
|
|
|
GtkWidget *bogus = NULL;
|
2019-09-21 01:24:40 +08:00
|
|
|
const gchar *help_id;
|
|
|
|
gboolean use_header_bar;
|
|
|
|
|
|
|
|
g_return_val_if_fail (GIMP_IS_PROCEDURE (procedure), NULL);
|
|
|
|
g_return_val_if_fail (GIMP_IS_PROCEDURE_CONFIG (config), NULL);
|
2019-09-21 01:33:50 +08:00
|
|
|
g_return_val_if_fail (gimp_procedure_config_get_procedure (config) ==
|
|
|
|
procedure, NULL);
|
2021-02-03 00:14:43 +08:00
|
|
|
g_return_val_if_fail (title != NULL || gimp_procedure_get_menu_label (procedure), NULL);
|
2019-09-21 01:24:40 +08:00
|
|
|
|
|
|
|
help_id = gimp_procedure_get_help_id (procedure);
|
2021-02-03 00:14:43 +08:00
|
|
|
if (title == NULL)
|
|
|
|
{
|
|
|
|
/* Remove mnemonic underscore. Ugly but must reliable way as GTK
|
|
|
|
* does not expose a function to do this from a string (and better
|
|
|
|
* not to copy-paste the internal function from GTK code).
|
|
|
|
*/
|
|
|
|
bogus = gtk_label_new (NULL);
|
|
|
|
gtk_label_set_markup_with_mnemonic (GTK_LABEL (g_object_ref_sink (bogus)),
|
|
|
|
gimp_procedure_get_menu_label (procedure));
|
|
|
|
title = gtk_label_get_text (GTK_LABEL (bogus));
|
|
|
|
}
|
2019-09-21 01:24:40 +08:00
|
|
|
|
|
|
|
g_object_get (gtk_settings_get_default (),
|
|
|
|
"gtk-dialogs-use-header", &use_header_bar,
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
dialog = g_object_new (GIMP_TYPE_PROCEDURE_DIALOG,
|
|
|
|
"procedure", procedure,
|
|
|
|
"config", config,
|
|
|
|
"title", title,
|
|
|
|
"help-func", gimp_standard_help_func,
|
|
|
|
"help-id", help_id,
|
|
|
|
"use-header-bar", use_header_bar,
|
|
|
|
NULL);
|
|
|
|
|
2021-02-03 00:14:43 +08:00
|
|
|
g_clear_object (&bogus);
|
|
|
|
|
2019-09-21 01:24:40 +08:00
|
|
|
return GTK_WIDGET (dialog);
|
|
|
|
}
|
|
|
|
|
2020-11-03 05:42:17 +08:00
|
|
|
/**
|
|
|
|
* gimp_procedure_dialog_get_widget:
|
2020-11-14 04:26:14 +08:00
|
|
|
* @dialog: the associated #GimpProcedureDialog.
|
|
|
|
* @property: name of the property to build a widget for. It must be
|
|
|
|
* a property of the #GimpProcedure @dialog has been
|
|
|
|
* created for.
|
2020-11-03 05:42:17 +08:00
|
|
|
* @widget_type: alternative widget type. %G_TYPE_NONE will create the
|
|
|
|
* default type of widget for the associated property
|
|
|
|
* type.
|
|
|
|
*
|
2020-11-14 04:26:14 +08:00
|
|
|
* Creates a new #GtkWidget for @property according to the property
|
|
|
|
* type. The following types are possible:
|
|
|
|
*
|
|
|
|
* - %G_TYPE_PARAM_BOOLEAN: %GTK_TYPE_CHECK_BUTTON (default) or
|
|
|
|
* %GTK_TYPE_SWITCH
|
|
|
|
* - %G_TYPE_PARAM_INT: %GIMP_TYPE_LABEL_SPIN (default) or
|
|
|
|
* %GIMP_TYPE_SCALE_ENTRY or %GIMP_TYPE_SPIN_BUTTON (no label).
|
|
|
|
* - %G_TYPE_PARAM_STRING: %GTK_TYPE_ENTRY (default).
|
|
|
|
*
|
|
|
|
* If the @widget_type is not supported for the actual type of
|
|
|
|
* @property, the function will fail. To keep the default, set to
|
|
|
|
* %G_TYPE_NONE.
|
|
|
|
*
|
2020-11-03 05:42:17 +08:00
|
|
|
* If a widget has already been created for this procedure, it will be
|
|
|
|
* returned instead (even if with a different @widget_type).
|
|
|
|
*
|
|
|
|
* Returns: (transfer none): the #GtkWidget representing @property. The
|
|
|
|
* object belongs to @dialog and must not be
|
|
|
|
* freed.
|
|
|
|
*/
|
|
|
|
GtkWidget *
|
|
|
|
gimp_procedure_dialog_get_widget (GimpProcedureDialog *dialog,
|
|
|
|
const gchar *property,
|
|
|
|
GType widget_type)
|
|
|
|
{
|
2021-04-07 02:10:46 +08:00
|
|
|
GtkWidget *widget = NULL;
|
|
|
|
GtkWidget *label = NULL;
|
|
|
|
GimpProcedureDialogSensitiveData *binding;
|
|
|
|
GParamSpec *pspec;
|
2020-11-03 05:42:17 +08:00
|
|
|
|
|
|
|
g_return_val_if_fail (property != NULL, NULL);
|
|
|
|
|
|
|
|
/* First check if it already exists. */
|
|
|
|
widget = g_hash_table_lookup (dialog->priv->widgets, property);
|
|
|
|
|
|
|
|
if (widget)
|
|
|
|
return widget;
|
|
|
|
|
|
|
|
pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (dialog->priv->config),
|
|
|
|
property);
|
|
|
|
if (! pspec)
|
|
|
|
{
|
|
|
|
g_warning ("%s: parameter %s does not exist.",
|
|
|
|
G_STRFUNC, property);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_BOOLEAN)
|
|
|
|
{
|
|
|
|
if (widget_type == G_TYPE_NONE || widget_type == GTK_TYPE_CHECK_BUTTON)
|
|
|
|
widget = gimp_prop_check_button_new (G_OBJECT (dialog->priv->config),
|
|
|
|
property,
|
|
|
|
_(g_param_spec_get_nick (pspec)));
|
|
|
|
else if (widget_type == GTK_TYPE_SWITCH)
|
2020-11-14 03:24:08 +08:00
|
|
|
widget = gimp_prop_switch_new (G_OBJECT (dialog->priv->config),
|
|
|
|
property,
|
|
|
|
_(g_param_spec_get_nick (pspec)),
|
|
|
|
&label, NULL);
|
2020-11-03 05:42:17 +08:00
|
|
|
}
|
2020-11-24 22:00:34 +08:00
|
|
|
else if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_INT ||
|
|
|
|
G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_DOUBLE)
|
2020-11-03 05:42:17 +08:00
|
|
|
{
|
2020-11-24 22:00:34 +08:00
|
|
|
gdouble minimum;
|
|
|
|
gdouble maximum;
|
|
|
|
gdouble step = 0.0;
|
|
|
|
gdouble page = 0.0;
|
|
|
|
gint digits = 0;
|
|
|
|
|
|
|
|
if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_INT)
|
|
|
|
{
|
|
|
|
GParamSpecInt *pspecint = (GParamSpecInt *) pspec;
|
|
|
|
|
|
|
|
minimum = (gdouble) pspecint->minimum;
|
|
|
|
maximum = (gdouble) pspecint->maximum;
|
|
|
|
}
|
|
|
|
else /* G_TYPE_PARAM_DOUBLE */
|
|
|
|
{
|
|
|
|
GParamSpecDouble *pspecdouble = (GParamSpecDouble *) pspec;
|
|
|
|
|
|
|
|
minimum = pspecdouble->minimum;
|
|
|
|
maximum = pspecdouble->maximum;
|
|
|
|
}
|
2020-11-25 08:16:34 +08:00
|
|
|
gimp_range_estimate_settings (minimum, maximum, &step, &page, &digits);
|
2020-11-24 22:00:34 +08:00
|
|
|
|
2020-11-06 00:46:02 +08:00
|
|
|
if (widget_type == G_TYPE_NONE || widget_type == GIMP_TYPE_LABEL_SPIN)
|
|
|
|
{
|
|
|
|
widget = gimp_prop_label_spin_new (G_OBJECT (dialog->priv->config),
|
2020-11-24 22:00:34 +08:00
|
|
|
property, digits);
|
2020-11-06 00:46:02 +08:00
|
|
|
}
|
|
|
|
else if (widget_type == GIMP_TYPE_SCALE_ENTRY)
|
2020-11-03 05:42:17 +08:00
|
|
|
{
|
|
|
|
widget = gimp_prop_scale_entry_new (G_OBJECT (dialog->priv->config),
|
|
|
|
property,
|
|
|
|
_(g_param_spec_get_nick (pspec)),
|
2020-11-26 07:27:43 +08:00
|
|
|
1.0, FALSE, 0.0, 0.0);
|
2020-11-03 05:42:17 +08:00
|
|
|
}
|
|
|
|
else if (widget_type == GIMP_TYPE_SPIN_BUTTON)
|
|
|
|
{
|
2020-11-06 00:46:02 +08:00
|
|
|
/* Just some spin button without label. */
|
2020-11-03 05:42:17 +08:00
|
|
|
widget = gimp_prop_spin_button_new (G_OBJECT (dialog->priv->config),
|
2020-11-24 22:00:34 +08:00
|
|
|
property, step, page, digits);
|
2020-11-03 05:42:17 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_STRING)
|
|
|
|
{
|
2020-11-14 07:21:46 +08:00
|
|
|
if (widget_type == G_TYPE_NONE || widget_type == GTK_TYPE_TEXT_VIEW)
|
|
|
|
{
|
|
|
|
GtkTextBuffer *buffer;
|
2020-11-20 08:12:22 +08:00
|
|
|
const gchar *tooltip;
|
2020-11-14 07:21:46 +08:00
|
|
|
|
|
|
|
buffer = gimp_prop_text_buffer_new (G_OBJECT (dialog->priv->config),
|
|
|
|
property, -1);
|
|
|
|
widget = gtk_text_view_new_with_buffer (buffer);
|
2020-11-20 07:06:27 +08:00
|
|
|
gtk_text_view_set_top_margin (GTK_TEXT_VIEW (widget), 3);
|
|
|
|
gtk_text_view_set_bottom_margin (GTK_TEXT_VIEW (widget), 3);
|
|
|
|
gtk_text_view_set_left_margin (GTK_TEXT_VIEW (widget), 3);
|
|
|
|
gtk_text_view_set_right_margin (GTK_TEXT_VIEW (widget), 3);
|
2020-11-14 07:21:46 +08:00
|
|
|
g_object_unref (buffer);
|
2020-11-20 08:12:22 +08:00
|
|
|
|
|
|
|
tooltip = g_param_spec_get_blurb (pspec);
|
|
|
|
if (tooltip)
|
|
|
|
gimp_help_set_help_data (widget, tooltip, NULL);
|
2020-11-14 07:21:46 +08:00
|
|
|
}
|
|
|
|
else if (widget_type == GTK_TYPE_ENTRY)
|
|
|
|
{
|
|
|
|
widget = gimp_prop_entry_new (G_OBJECT (dialog->priv->config),
|
|
|
|
property, -1);
|
|
|
|
}
|
2020-11-03 05:42:17 +08:00
|
|
|
}
|
2021-04-20 22:40:18 +08:00
|
|
|
else if (G_PARAM_SPEC_TYPE (pspec) == GIMP_TYPE_PARAM_RGB)
|
|
|
|
{
|
|
|
|
widget = gimp_prop_color_area_new (G_OBJECT (dialog->priv->config),
|
|
|
|
property, 20, 20,
|
|
|
|
GIMP_COLOR_AREA_SMALL_CHECKS);
|
|
|
|
}
|
2020-11-03 05:42:17 +08:00
|
|
|
else
|
|
|
|
{
|
|
|
|
g_warning ("%s: parameter %s has non supported type %s",
|
|
|
|
G_STRFUNC, property, G_PARAM_SPEC_TYPE_NAME (pspec));
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (! widget)
|
|
|
|
{
|
|
|
|
g_warning ("%s: widget type %s not supported for parameter '%s' of type %s",
|
2020-11-20 08:29:08 +08:00
|
|
|
G_STRFUNC, g_type_name (widget_type),
|
2020-11-03 05:42:17 +08:00
|
|
|
property, G_PARAM_SPEC_TYPE_NAME (pspec));
|
|
|
|
return NULL;
|
|
|
|
}
|
2020-11-14 03:24:08 +08:00
|
|
|
else if (GIMP_IS_LABELED (widget) || label)
|
2020-11-06 00:46:02 +08:00
|
|
|
{
|
2020-11-14 03:24:08 +08:00
|
|
|
if (! label)
|
|
|
|
label = gimp_labeled_get_label (GIMP_LABELED (widget));
|
2020-11-06 00:46:02 +08:00
|
|
|
|
|
|
|
gtk_size_group_add_widget (dialog->priv->label_group, label);
|
|
|
|
}
|
2020-11-03 05:42:17 +08:00
|
|
|
|
2021-04-07 02:10:46 +08:00
|
|
|
if ((binding = g_hash_table_lookup (dialog->priv->sensitive_data, property)))
|
|
|
|
{
|
|
|
|
if (binding->config)
|
|
|
|
{
|
|
|
|
g_object_bind_property (binding->config, binding->config_property,
|
|
|
|
widget, "sensitive",
|
|
|
|
G_BINDING_SYNC_CREATE |
|
|
|
|
(binding->config_invert ? G_BINDING_INVERT_BOOLEAN : 0));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
gtk_widget_set_sensitive (widget, binding->sensitive);
|
|
|
|
}
|
|
|
|
|
|
|
|
g_hash_table_remove (dialog->priv->sensitive_data, property);
|
|
|
|
}
|
|
|
|
|
2020-11-20 08:29:08 +08:00
|
|
|
gimp_procedure_dialog_check_mnemonic (dialog, widget, property, NULL);
|
2020-11-03 05:42:17 +08:00
|
|
|
g_hash_table_insert (dialog->priv->widgets, g_strdup (property), widget);
|
libgimp: sink the floating references after adding them to hash table.
Though the previous implementation worked fine on C plug-ins, I realized
it was problematic on bindings. In particular, the Python binding at
least was somehow freeing returned floating objects, unless assigned to
a variable.
For instance, the widget returned by the following code:
> dialog.get_color_widget('color', True, GimpUi.ColorAreaType.FLAT)
… was freed by the PyGObject binding when it was floating, even though
(transfer none) was set (hence telling the binding it should not free
the returned object). The workaround was to assign it to some variable,
even though I was not planning to use it.
Making sure all references are full fixes it.
GObject docs also notes:
> **Note**: Floating references are a C convenience API and should not
> be used in modern GObject code. Language bindings in particular find
> the concept highly problematic, as floating references are not
> identifiable through annotations, and neither are deviations from the
> floating reference behavior, like types that inherit from
> GInitiallyUnowned and still return a full reference from
> g_object_new().
2021-04-20 23:08:31 +08:00
|
|
|
if (g_object_is_floating (widget))
|
|
|
|
g_object_ref_sink (widget);
|
2020-11-03 05:42:17 +08:00
|
|
|
|
|
|
|
return widget;
|
|
|
|
}
|
|
|
|
|
2021-04-20 22:40:18 +08:00
|
|
|
/**
|
|
|
|
* gimp_procedure_dialog_get_color_widget:
|
|
|
|
* @dialog: the associated #GimpProcedureDialog.
|
|
|
|
* @property: name of the #GimpRGB property to build a widget for. It
|
|
|
|
* must be a property of the #GimpProcedure @dialog has been
|
|
|
|
* created for.
|
|
|
|
* @editable: whether the color can be edited or is only for display.
|
|
|
|
* @type: the #GimpColorAreaType.
|
|
|
|
*
|
|
|
|
* Creates a new widget for @property which must necessarily be a
|
|
|
|
* #GimpRGB property.
|
|
|
|
* This must be used instead of gimp_procedure_dialog_get_widget() when
|
|
|
|
* you want more customizability for an RGB property.
|
|
|
|
*
|
|
|
|
* If a widget has already been created for this procedure, it will be
|
|
|
|
* returned instead (whatever its actual widget type).
|
|
|
|
*
|
|
|
|
* Returns: (transfer none): a #GimpColorButton representing @property
|
|
|
|
* if @editable is %TRUE, a #GimpColorArea otherwise.
|
|
|
|
* The object belongs to @dialog and must not
|
|
|
|
* be freed.
|
|
|
|
*/
|
|
|
|
GtkWidget *
|
|
|
|
gimp_procedure_dialog_get_color_widget (GimpProcedureDialog *dialog,
|
|
|
|
const gchar *property,
|
|
|
|
gboolean editable,
|
|
|
|
GimpColorAreaType type)
|
|
|
|
{
|
|
|
|
GtkWidget *widget = NULL;
|
|
|
|
GParamSpec *pspec;
|
|
|
|
|
|
|
|
g_return_val_if_fail (property != NULL, NULL);
|
|
|
|
|
|
|
|
/* First check if it already exists. */
|
|
|
|
widget = g_hash_table_lookup (dialog->priv->widgets, property);
|
|
|
|
|
|
|
|
if (widget)
|
|
|
|
return widget;
|
|
|
|
|
|
|
|
pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (dialog->priv->config),
|
|
|
|
property);
|
|
|
|
if (! pspec)
|
|
|
|
{
|
|
|
|
g_warning ("%s: parameter %s does not exist.",
|
|
|
|
G_STRFUNC, property);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (G_PARAM_SPEC_TYPE (pspec) == GIMP_TYPE_PARAM_RGB)
|
|
|
|
{
|
|
|
|
if (editable)
|
|
|
|
widget = gimp_prop_color_select_new (G_OBJECT (dialog->priv->config),
|
|
|
|
property, 20, 20, type);
|
|
|
|
else
|
|
|
|
widget = gimp_prop_color_area_new (G_OBJECT (dialog->priv->config),
|
|
|
|
property, 20, 20, type);
|
|
|
|
|
|
|
|
gtk_widget_set_vexpand (widget, FALSE);
|
|
|
|
gtk_widget_set_hexpand (widget, TRUE);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (! widget)
|
|
|
|
{
|
|
|
|
g_warning ("%s: parameter '%s' of type %s not suitable as color widget",
|
|
|
|
G_STRFUNC, property, G_PARAM_SPEC_TYPE_NAME (pspec));
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
else if (GIMP_IS_LABELED (widget))
|
|
|
|
{
|
|
|
|
GtkWidget *label = gimp_labeled_get_label (GIMP_LABELED (widget));
|
|
|
|
|
|
|
|
gtk_size_group_add_widget (dialog->priv->label_group, label);
|
|
|
|
}
|
|
|
|
|
|
|
|
gimp_procedure_dialog_check_mnemonic (dialog, widget, property, NULL);
|
|
|
|
g_hash_table_insert (dialog->priv->widgets, g_strdup (property), widget);
|
libgimp: sink the floating references after adding them to hash table.
Though the previous implementation worked fine on C plug-ins, I realized
it was problematic on bindings. In particular, the Python binding at
least was somehow freeing returned floating objects, unless assigned to
a variable.
For instance, the widget returned by the following code:
> dialog.get_color_widget('color', True, GimpUi.ColorAreaType.FLAT)
… was freed by the PyGObject binding when it was floating, even though
(transfer none) was set (hence telling the binding it should not free
the returned object). The workaround was to assign it to some variable,
even though I was not planning to use it.
Making sure all references are full fixes it.
GObject docs also notes:
> **Note**: Floating references are a C convenience API and should not
> be used in modern GObject code. Language bindings in particular find
> the concept highly problematic, as floating references are not
> identifiable through annotations, and neither are deviations from the
> floating reference behavior, like types that inherit from
> GInitiallyUnowned and still return a full reference from
> g_object_new().
2021-04-20 23:08:31 +08:00
|
|
|
if (g_object_is_floating (widget))
|
|
|
|
g_object_ref_sink (widget);
|
2021-04-20 22:40:18 +08:00
|
|
|
|
|
|
|
return widget;
|
|
|
|
}
|
|
|
|
|
2020-11-03 05:42:17 +08:00
|
|
|
/**
|
2020-11-14 04:26:14 +08:00
|
|
|
* gimp_procedure_dialog_get_int_combo:
|
|
|
|
* @dialog: the associated #GimpProcedureDialog.
|
|
|
|
* @property: name of the int property to build a combo for. It must be
|
|
|
|
* a property of the #GimpProcedure @dialog has been created
|
|
|
|
* for.
|
|
|
|
* @store: the #GimpIntStore which will be used by the combo box.
|
|
|
|
*
|
|
|
|
* Creates a new #GimpLabelIntWidget for @property which must
|
|
|
|
* necessarily be an integer or boolean property.
|
|
|
|
* This must be used instead of gimp_procedure_dialog_get_widget() when
|
|
|
|
* you want to create a combo box from an integer property.
|
|
|
|
*
|
|
|
|
* If a widget has already been created for this procedure, it will be
|
|
|
|
* returned instead (whatever its actual widget type).
|
|
|
|
*
|
|
|
|
* Returns: (transfer none): the #GtkWidget representing @property. The
|
|
|
|
* object belongs to @dialog and must not be
|
|
|
|
* freed.
|
|
|
|
*/
|
|
|
|
GtkWidget *
|
|
|
|
gimp_procedure_dialog_get_int_combo (GimpProcedureDialog *dialog,
|
|
|
|
const gchar *property,
|
|
|
|
GimpIntStore *store)
|
|
|
|
{
|
|
|
|
GtkWidget *widget = NULL;
|
|
|
|
GParamSpec *pspec;
|
|
|
|
|
|
|
|
g_return_val_if_fail (property != NULL, NULL);
|
|
|
|
|
|
|
|
/* First check if it already exists. */
|
|
|
|
widget = g_hash_table_lookup (dialog->priv->widgets, property);
|
|
|
|
|
|
|
|
if (widget)
|
|
|
|
return widget;
|
|
|
|
|
|
|
|
pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (dialog->priv->config),
|
|
|
|
property);
|
|
|
|
if (! pspec)
|
|
|
|
{
|
|
|
|
g_warning ("%s: parameter %s does not exist.",
|
|
|
|
G_STRFUNC, property);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_BOOLEAN ||
|
|
|
|
G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_INT)
|
|
|
|
{
|
|
|
|
widget = gimp_prop_int_combo_box_new (G_OBJECT (dialog->priv->config),
|
|
|
|
property, store);
|
|
|
|
gtk_widget_set_vexpand (widget, FALSE);
|
|
|
|
gtk_widget_set_hexpand (widget, TRUE);
|
|
|
|
widget = gimp_label_int_widget_new (_(g_param_spec_get_nick (pspec)),
|
|
|
|
widget);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (! widget)
|
|
|
|
{
|
|
|
|
g_warning ("%s: parameter '%s' of type %s not suitable as GimpIntComboBox",
|
|
|
|
G_STRFUNC, property, G_PARAM_SPEC_TYPE_NAME (pspec));
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
else if (GIMP_IS_LABELED (widget))
|
|
|
|
{
|
|
|
|
GtkWidget *label = gimp_labeled_get_label (GIMP_LABELED (widget));
|
|
|
|
|
|
|
|
gtk_size_group_add_widget (dialog->priv->label_group, label);
|
|
|
|
}
|
|
|
|
|
2020-11-20 08:29:08 +08:00
|
|
|
gimp_procedure_dialog_check_mnemonic (dialog, widget, property, NULL);
|
2020-11-25 19:25:59 +08:00
|
|
|
g_hash_table_insert (dialog->priv->widgets, g_strdup (property), widget);
|
libgimp: sink the floating references after adding them to hash table.
Though the previous implementation worked fine on C plug-ins, I realized
it was problematic on bindings. In particular, the Python binding at
least was somehow freeing returned floating objects, unless assigned to
a variable.
For instance, the widget returned by the following code:
> dialog.get_color_widget('color', True, GimpUi.ColorAreaType.FLAT)
… was freed by the PyGObject binding when it was floating, even though
(transfer none) was set (hence telling the binding it should not free
the returned object). The workaround was to assign it to some variable,
even though I was not planning to use it.
Making sure all references are full fixes it.
GObject docs also notes:
> **Note**: Floating references are a C convenience API and should not
> be used in modern GObject code. Language bindings in particular find
> the concept highly problematic, as floating references are not
> identifiable through annotations, and neither are deviations from the
> floating reference behavior, like types that inherit from
> GInitiallyUnowned and still return a full reference from
> g_object_new().
2021-04-20 23:08:31 +08:00
|
|
|
if (g_object_is_floating (widget))
|
|
|
|
g_object_ref_sink (widget);
|
2020-11-25 19:25:59 +08:00
|
|
|
|
|
|
|
return widget;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* gimp_procedure_dialog_get_scale_entry:
|
|
|
|
* @dialog: the associated #GimpProcedureDialog.
|
|
|
|
* @property: name of the int property to build a combo for. It must be
|
|
|
|
* a property of the #GimpProcedure @dialog has been created
|
|
|
|
* for.
|
|
|
|
* @factor: a display factor for the range shown by the widget.
|
|
|
|
*
|
|
|
|
* Creates a new #GimpScaleEntry for @property which must necessarily be
|
|
|
|
* an integer or double property.
|
|
|
|
* This can be used instead of gimp_procedure_dialog_get_widget() in
|
|
|
|
* particular if you want to tweak the display factor. A typical example
|
|
|
|
* is showing a [0.0, 1.0] range as [0.0, 100.0] instead (@factor = 100.0).
|
|
|
|
*
|
|
|
|
* If a widget has already been created for this procedure, it will be
|
|
|
|
* returned instead (whatever its actual widget type).
|
|
|
|
*
|
|
|
|
* Returns: (transfer none): the #GtkWidget representing @property. The
|
|
|
|
* object belongs to @dialog and must not be
|
|
|
|
* freed.
|
|
|
|
*/
|
|
|
|
GtkWidget *
|
|
|
|
gimp_procedure_dialog_get_scale_entry (GimpProcedureDialog *dialog,
|
|
|
|
const gchar *property,
|
|
|
|
gdouble factor)
|
|
|
|
{
|
|
|
|
GtkWidget *widget = NULL;
|
|
|
|
GParamSpec *pspec;
|
|
|
|
|
|
|
|
g_return_val_if_fail (GIMP_IS_PROCEDURE_DIALOG (dialog), NULL);
|
|
|
|
g_return_val_if_fail (property != NULL, NULL);
|
|
|
|
|
|
|
|
pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (dialog->priv->config),
|
|
|
|
property);
|
|
|
|
|
|
|
|
if (! pspec)
|
|
|
|
{
|
|
|
|
g_warning ("%s: parameter %s does not exist.",
|
|
|
|
G_STRFUNC, property);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
g_return_val_if_fail (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_INT ||
|
|
|
|
G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_DOUBLE, NULL);
|
|
|
|
|
|
|
|
/* First check if it already exists. */
|
|
|
|
widget = g_hash_table_lookup (dialog->priv->widgets, property);
|
|
|
|
|
|
|
|
if (widget)
|
|
|
|
return widget;
|
|
|
|
|
|
|
|
widget = gimp_prop_scale_entry_new (G_OBJECT (dialog->priv->config),
|
|
|
|
property,
|
|
|
|
_(g_param_spec_get_nick (pspec)),
|
|
|
|
factor, FALSE, 0.0, 0.0);
|
|
|
|
|
|
|
|
gtk_size_group_add_widget (dialog->priv->label_group,
|
|
|
|
gimp_labeled_get_label (GIMP_LABELED (widget)));
|
|
|
|
|
|
|
|
gimp_procedure_dialog_check_mnemonic (dialog, widget, property, NULL);
|
2020-11-14 04:26:14 +08:00
|
|
|
g_hash_table_insert (dialog->priv->widgets, g_strdup (property), widget);
|
libgimp: sink the floating references after adding them to hash table.
Though the previous implementation worked fine on C plug-ins, I realized
it was problematic on bindings. In particular, the Python binding at
least was somehow freeing returned floating objects, unless assigned to
a variable.
For instance, the widget returned by the following code:
> dialog.get_color_widget('color', True, GimpUi.ColorAreaType.FLAT)
… was freed by the PyGObject binding when it was floating, even though
(transfer none) was set (hence telling the binding it should not free
the returned object). The workaround was to assign it to some variable,
even though I was not planning to use it.
Making sure all references are full fixes it.
GObject docs also notes:
> **Note**: Floating references are a C convenience API and should not
> be used in modern GObject code. Language bindings in particular find
> the concept highly problematic, as floating references are not
> identifiable through annotations, and neither are deviations from the
> floating reference behavior, like types that inherit from
> GInitiallyUnowned and still return a full reference from
> g_object_new().
2021-04-20 23:08:31 +08:00
|
|
|
if (g_object_is_floating (widget))
|
|
|
|
g_object_ref_sink (widget);
|
2020-11-14 04:26:14 +08:00
|
|
|
|
|
|
|
return widget;
|
|
|
|
}
|
|
|
|
|
2020-11-14 07:21:46 +08:00
|
|
|
/**
|
|
|
|
* gimp_procedure_dialog_get_label:
|
|
|
|
* @dialog: the #GimpProcedureDialog.
|
|
|
|
* @label_id: the label for the #GtkLabel.
|
|
|
|
* @text: the text for the label.
|
|
|
|
*
|
|
|
|
* Creates a new #GtkLabel with @text. It can be useful for packing
|
|
|
|
* textual information in between property settings.
|
|
|
|
*
|
|
|
|
* The @label_id must be a unique ID which is neither the name of a
|
|
|
|
* property of the #GimpProcedureConfig associated to @dialog, nor is it
|
|
|
|
* the ID of any previously created label or container. This ID can
|
|
|
|
* later be used together with property names to be packed in other
|
|
|
|
* containers or inside @dialog itself.
|
|
|
|
*
|
|
|
|
* Returns: (transfer none): the #GtkWidget representing @label_id. The
|
|
|
|
* object belongs to @dialog and must not be
|
|
|
|
* freed.
|
|
|
|
*/
|
|
|
|
GtkWidget *
|
|
|
|
gimp_procedure_dialog_get_label (GimpProcedureDialog *dialog,
|
|
|
|
const gchar *label_id,
|
|
|
|
const gchar *text)
|
|
|
|
{
|
|
|
|
GtkWidget *label;
|
|
|
|
|
|
|
|
g_return_val_if_fail (label_id != NULL, NULL);
|
|
|
|
|
|
|
|
if (g_object_class_find_property (G_OBJECT_GET_CLASS (dialog->priv->config),
|
|
|
|
label_id))
|
|
|
|
{
|
|
|
|
g_warning ("%s: label identifier '%s' cannot be an existing property name.",
|
|
|
|
G_STRFUNC, label_id);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((label = g_hash_table_lookup (dialog->priv->widgets, label_id)))
|
|
|
|
{
|
|
|
|
g_warning ("%s: label identifier '%s' was already configured.",
|
|
|
|
G_STRFUNC, label_id);
|
|
|
|
return label;
|
|
|
|
}
|
|
|
|
|
|
|
|
label = gtk_label_new (text);
|
|
|
|
g_hash_table_insert (dialog->priv->widgets, g_strdup (label_id), label);
|
libgimp: sink the floating references after adding them to hash table.
Though the previous implementation worked fine on C plug-ins, I realized
it was problematic on bindings. In particular, the Python binding at
least was somehow freeing returned floating objects, unless assigned to
a variable.
For instance, the widget returned by the following code:
> dialog.get_color_widget('color', True, GimpUi.ColorAreaType.FLAT)
… was freed by the PyGObject binding when it was floating, even though
(transfer none) was set (hence telling the binding it should not free
the returned object). The workaround was to assign it to some variable,
even though I was not planning to use it.
Making sure all references are full fixes it.
GObject docs also notes:
> **Note**: Floating references are a C convenience API and should not
> be used in modern GObject code. Language bindings in particular find
> the concept highly problematic, as floating references are not
> identifiable through annotations, and neither are deviations from the
> floating reference behavior, like types that inherit from
> GInitiallyUnowned and still return a full reference from
> g_object_new().
2021-04-20 23:08:31 +08:00
|
|
|
if (g_object_is_floating (label))
|
|
|
|
g_object_ref_sink (label);
|
2020-11-14 07:21:46 +08:00
|
|
|
|
|
|
|
return label;
|
|
|
|
}
|
|
|
|
|
2020-11-14 04:26:14 +08:00
|
|
|
/**
|
|
|
|
* gimp_procedure_dialog_fill:
|
2020-11-03 05:42:17 +08:00
|
|
|
* @dialog: the #GimpProcedureDialog.
|
2021-04-06 00:51:33 +08:00
|
|
|
* @...: a %NULL-terminated list of property names.
|
2020-11-03 05:42:17 +08:00
|
|
|
*
|
2020-11-14 04:26:14 +08:00
|
|
|
* Populate @dialog with the widgets corresponding to every listed
|
2020-11-03 05:42:17 +08:00
|
|
|
* properties. If the list is empty, @dialog will be filled by the whole
|
|
|
|
* list of properties of the associated #GimpProcedure, in the defined
|
|
|
|
* order:
|
|
|
|
* |[<!-- language="C" -->
|
2020-11-14 04:26:14 +08:00
|
|
|
* gimp_procedure_dialog_fill (dialog, NULL);
|
2020-11-03 05:42:17 +08:00
|
|
|
* ]|
|
|
|
|
* Nevertheless if you only wish to display a partial list of
|
|
|
|
* properties, or if you wish to change the display order, then you have
|
|
|
|
* to give an explicit list:
|
|
|
|
* |[<!-- language="C" -->
|
2020-11-14 04:26:14 +08:00
|
|
|
* gimp_procedure_dialog_fill (dialog, "property-1", "property-2", NULL);
|
2020-11-03 05:42:17 +08:00
|
|
|
* ]|
|
2020-11-14 04:26:14 +08:00
|
|
|
*
|
|
|
|
* Note: you do not have to call gimp_procedure_dialog_get_widget() on
|
|
|
|
* every property before calling this function unless you want a given
|
|
|
|
* property to be represented by an alternative widget type. By default,
|
|
|
|
* each property will get a default representation according to its
|
|
|
|
* type.
|
2020-11-03 05:42:17 +08:00
|
|
|
*/
|
|
|
|
void
|
2020-11-14 04:26:14 +08:00
|
|
|
gimp_procedure_dialog_fill (GimpProcedureDialog *dialog,
|
|
|
|
...)
|
2020-11-03 05:42:17 +08:00
|
|
|
{
|
2021-04-06 00:51:33 +08:00
|
|
|
const gchar *prop_name;
|
2020-11-03 05:42:17 +08:00
|
|
|
GList *list = NULL;
|
|
|
|
va_list va_args;
|
|
|
|
|
|
|
|
g_return_if_fail (GIMP_IS_PROCEDURE_DIALOG (dialog));
|
|
|
|
|
2021-04-06 00:51:33 +08:00
|
|
|
va_start (va_args, dialog);
|
2020-11-03 05:42:17 +08:00
|
|
|
|
2021-04-06 00:51:33 +08:00
|
|
|
while ((prop_name = va_arg (va_args, const gchar *)))
|
|
|
|
list = g_list_prepend (list, (gpointer) prop_name);
|
2020-11-03 05:42:17 +08:00
|
|
|
|
2021-04-06 00:51:33 +08:00
|
|
|
va_end (va_args);
|
2020-11-03 05:42:17 +08:00
|
|
|
|
|
|
|
list = g_list_reverse (list);
|
2020-11-14 04:26:14 +08:00
|
|
|
gimp_procedure_dialog_fill_list (dialog, list);
|
2020-11-03 05:42:17 +08:00
|
|
|
if (list)
|
|
|
|
g_list_free (list);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2020-11-14 04:26:14 +08:00
|
|
|
* gimp_procedure_dialog_fill_list: (rename-to gimp_procedure_dialog_fill)
|
2020-11-03 05:42:17 +08:00
|
|
|
* @dialog: the #GimpProcedureDialog.
|
|
|
|
* @properties: (nullable) (element-type gchar*): the list of property names.
|
|
|
|
*
|
2020-11-14 04:26:14 +08:00
|
|
|
* Populate @dialog with the widgets corresponding to every listed
|
2020-11-03 05:42:17 +08:00
|
|
|
* properties. If the list is %NULL, @dialog will be filled by the whole
|
|
|
|
* list of properties of the associated #GimpProcedure, in the defined
|
|
|
|
* order:
|
|
|
|
* |[<!-- language="C" -->
|
2020-11-14 04:26:14 +08:00
|
|
|
* gimp_procedure_dialog_fill_list (dialog, NULL);
|
2020-11-03 05:42:17 +08:00
|
|
|
* ]|
|
|
|
|
* Nevertheless if you only wish to display a partial list of
|
|
|
|
* properties, or if you wish to change the display order, then you have
|
|
|
|
* to give an explicit list:
|
2020-11-14 04:26:14 +08:00
|
|
|
* |[<!-- language="C" -->
|
|
|
|
* gimp_procedure_dialog_fill (dialog, "property-1", "property-2", NULL);
|
|
|
|
* ]|
|
|
|
|
*
|
|
|
|
* Note: you do not have to call gimp_procedure_dialog_get_widget() on
|
|
|
|
* every property before calling this function unless you want a given
|
|
|
|
* property to be represented by an alternative widget type. By default,
|
|
|
|
* each property will get a default representation according to its
|
|
|
|
* type.
|
2020-11-03 05:42:17 +08:00
|
|
|
*/
|
|
|
|
void
|
2020-11-14 04:26:14 +08:00
|
|
|
gimp_procedure_dialog_fill_list (GimpProcedureDialog *dialog,
|
|
|
|
GList *properties)
|
2020-11-03 05:42:17 +08:00
|
|
|
{
|
2020-11-21 08:15:15 +08:00
|
|
|
gboolean free_properties = FALSE;
|
2020-11-03 05:42:17 +08:00
|
|
|
|
|
|
|
if (! properties)
|
|
|
|
{
|
|
|
|
GParamSpec **pspecs;
|
|
|
|
guint n_pspecs;
|
|
|
|
gint i;
|
|
|
|
|
|
|
|
pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (dialog->priv->config),
|
|
|
|
&n_pspecs);
|
|
|
|
|
|
|
|
for (i = 0; i < n_pspecs; i++)
|
|
|
|
{
|
|
|
|
const gchar *prop_name;
|
|
|
|
GParamSpec *pspec = pspecs[i];
|
|
|
|
|
|
|
|
/* skip our own properties */
|
|
|
|
if (pspec->owner_type == GIMP_TYPE_PROCEDURE_CONFIG)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
prop_name = g_param_spec_get_name (pspec);
|
|
|
|
properties = g_list_prepend (properties, (gpointer) prop_name);
|
|
|
|
}
|
|
|
|
|
|
|
|
properties = g_list_reverse (properties);
|
|
|
|
|
|
|
|
if (properties)
|
|
|
|
free_properties = TRUE;
|
|
|
|
}
|
|
|
|
|
2020-11-21 08:15:15 +08:00
|
|
|
GIMP_PROCEDURE_DIALOG_GET_CLASS (dialog)->fill_list (dialog,
|
|
|
|
dialog->priv->procedure,
|
|
|
|
dialog->priv->config,
|
|
|
|
properties);
|
2020-11-03 05:42:17 +08:00
|
|
|
|
|
|
|
if (free_properties)
|
|
|
|
g_list_free (properties);
|
|
|
|
}
|
|
|
|
|
2020-11-24 22:00:34 +08:00
|
|
|
/**
|
|
|
|
* gimp_procedure_dialog_fill_box:
|
|
|
|
* @dialog: the #GimpProcedureDialog.
|
|
|
|
* @container_id: a container identifier.
|
|
|
|
* @first_property: the first property name.
|
|
|
|
* @...: a %NULL-terminated list of other property names.
|
|
|
|
*
|
|
|
|
* Creates and populates a new #GtkBox with widgets corresponding to
|
|
|
|
* every listed properties. If the list is empty, the created box will
|
|
|
|
* be filled by the whole list of properties of the associated
|
|
|
|
* #GimpProcedure, in the defined order. This is similar of how
|
|
|
|
* gimp_procedure_dialog_fill() works except that it creates a new
|
|
|
|
* widget which is not inside @dialog itself.
|
|
|
|
*
|
|
|
|
* The @container_id must be a unique ID which is neither the name of a
|
|
|
|
* property of the #GimpProcedureConfig associated to @dialog, nor is it
|
|
|
|
* the ID of any previously created container. This ID can later be used
|
|
|
|
* together with property names to be packed in other containers or
|
|
|
|
* inside @dialog itself.
|
|
|
|
*
|
|
|
|
* Returns: (transfer none): the #GtkBox representing @property. The
|
|
|
|
* object belongs to @dialog and must not be
|
|
|
|
* freed.
|
|
|
|
*/
|
|
|
|
GtkWidget *
|
|
|
|
gimp_procedure_dialog_fill_box (GimpProcedureDialog *dialog,
|
|
|
|
const gchar *container_id,
|
|
|
|
const gchar *first_property,
|
|
|
|
...)
|
|
|
|
{
|
|
|
|
const gchar *prop_name = first_property;
|
|
|
|
GtkWidget *box;
|
|
|
|
GList *list = NULL;
|
|
|
|
va_list va_args;
|
|
|
|
|
|
|
|
g_return_val_if_fail (GIMP_IS_PROCEDURE_DIALOG (dialog), NULL);
|
|
|
|
g_return_val_if_fail (container_id != NULL, NULL);
|
|
|
|
|
|
|
|
if (first_property)
|
|
|
|
{
|
|
|
|
va_start (va_args, first_property);
|
|
|
|
|
|
|
|
do
|
|
|
|
list = g_list_prepend (list, (gpointer) prop_name);
|
|
|
|
while ((prop_name = va_arg (va_args, const gchar *)));
|
|
|
|
|
|
|
|
va_end (va_args);
|
|
|
|
}
|
|
|
|
|
|
|
|
list = g_list_reverse (list);
|
|
|
|
box = gimp_procedure_dialog_fill_box_list (dialog, container_id, list);
|
|
|
|
if (list)
|
|
|
|
g_list_free (list);
|
|
|
|
|
|
|
|
return box;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* gimp_procedure_dialog_fill_box_list: (rename-to gimp_procedure_dialog_fill_box)
|
|
|
|
* @dialog: the #GimpProcedureDialog.
|
|
|
|
* @container_id: a container identifier.
|
|
|
|
* @properties: (nullable) (element-type gchar*): the list of property names.
|
|
|
|
*
|
|
|
|
* Creates and populates a new #GtkBox with widgets corresponding to
|
|
|
|
* every listed @properties. If the list is empty, the created box will
|
|
|
|
* be filled by the whole list of properties of the associated
|
|
|
|
* #GimpProcedure, in the defined order. This is similar of how
|
|
|
|
* gimp_procedure_dialog_fill() works except that it creates a new
|
|
|
|
* widget which is not inside @dialog itself.
|
|
|
|
*
|
|
|
|
* The @container_id must be a unique ID which is neither the name of a
|
|
|
|
* property of the #GimpProcedureConfig associated to @dialog, nor is it
|
|
|
|
* the ID of any previously created container. This ID can later be used
|
|
|
|
* together with property names to be packed in other containers or
|
|
|
|
* inside @dialog itself.
|
|
|
|
*
|
|
|
|
* Returns: (transfer none): the #GtkBox representing @property. The
|
|
|
|
* object belongs to @dialog and must not be
|
|
|
|
* freed.
|
|
|
|
*/
|
|
|
|
GtkWidget *
|
|
|
|
gimp_procedure_dialog_fill_box_list (GimpProcedureDialog *dialog,
|
|
|
|
const gchar *container_id,
|
|
|
|
GList *properties)
|
|
|
|
{
|
|
|
|
g_return_val_if_fail (container_id != NULL, NULL);
|
|
|
|
|
|
|
|
return gimp_procedure_dialog_fill_container_list (dialog, container_id,
|
|
|
|
GTK_CONTAINER (gtk_box_new (GTK_ORIENTATION_VERTICAL, 2)),
|
|
|
|
properties);
|
|
|
|
}
|
|
|
|
|
2020-11-14 04:26:14 +08:00
|
|
|
/**
|
|
|
|
* gimp_procedure_dialog_fill_flowbox:
|
|
|
|
* @dialog: the #GimpProcedureDialog.
|
|
|
|
* @container_id: a container identifier.
|
|
|
|
* @first_property: the first property name.
|
|
|
|
* @...: a %NULL-terminated list of other property names.
|
|
|
|
*
|
|
|
|
* Creates and populates a new #GtkFlowBox with widgets corresponding to
|
|
|
|
* every listed properties. If the list is empty, the created flowbox
|
|
|
|
* will be filled by the whole list of properties of the associated
|
|
|
|
* #GimpProcedure, in the defined order. This is similar of how
|
|
|
|
* gimp_procedure_dialog_fill() works except that it creates a new
|
|
|
|
* widget which is not inside @dialog itself.
|
|
|
|
*
|
|
|
|
* The @container_id must be a unique ID which is neither the name of a
|
|
|
|
* property of the #GimpProcedureConfig associated to @dialog, nor is it
|
|
|
|
* the ID of any previously created container. This ID can later be used
|
|
|
|
* together with property names to be packed in other containers or
|
|
|
|
* inside @dialog itself.
|
|
|
|
*
|
|
|
|
* Returns: (transfer none): the #GtkFlowBox representing @property. The
|
|
|
|
* object belongs to @dialog and must not be
|
|
|
|
* freed.
|
|
|
|
*/
|
|
|
|
GtkWidget *
|
|
|
|
gimp_procedure_dialog_fill_flowbox (GimpProcedureDialog *dialog,
|
|
|
|
const gchar *container_id,
|
|
|
|
const gchar *first_property,
|
|
|
|
...)
|
|
|
|
{
|
|
|
|
const gchar *prop_name = first_property;
|
|
|
|
GtkWidget *flowbox;
|
|
|
|
GList *list = NULL;
|
|
|
|
va_list va_args;
|
|
|
|
|
|
|
|
g_return_val_if_fail (GIMP_IS_PROCEDURE_DIALOG (dialog), NULL);
|
|
|
|
g_return_val_if_fail (container_id != NULL, NULL);
|
|
|
|
|
|
|
|
if (first_property)
|
|
|
|
{
|
|
|
|
va_start (va_args, first_property);
|
|
|
|
|
|
|
|
do
|
|
|
|
list = g_list_prepend (list, (gpointer) prop_name);
|
|
|
|
while ((prop_name = va_arg (va_args, const gchar *)));
|
|
|
|
|
|
|
|
va_end (va_args);
|
|
|
|
}
|
|
|
|
|
|
|
|
list = g_list_reverse (list);
|
|
|
|
flowbox = gimp_procedure_dialog_fill_flowbox_list (dialog, container_id, list);
|
|
|
|
if (list)
|
|
|
|
g_list_free (list);
|
|
|
|
|
|
|
|
return flowbox;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* gimp_procedure_dialog_fill_flowbox_list: (rename-to gimp_procedure_dialog_fill_flowbox)
|
|
|
|
* @dialog: the #GimpProcedureDialog.
|
|
|
|
* @container_id: a container identifier.
|
|
|
|
* @properties: (nullable) (element-type gchar*): the list of property names.
|
|
|
|
*
|
|
|
|
* Creates and populates a new #GtkFlowBox with widgets corresponding to
|
|
|
|
* every listed @properties. If the list is empty, the created flowbox
|
|
|
|
* will be filled by the whole list of properties of the associated
|
|
|
|
* #GimpProcedure, in the defined order. This is similar of how
|
|
|
|
* gimp_procedure_dialog_fill() works except that it creates a new
|
|
|
|
* widget which is not inside @dialog itself.
|
|
|
|
*
|
|
|
|
* The @container_id must be a unique ID which is neither the name of a
|
|
|
|
* property of the #GimpProcedureConfig associated to @dialog, nor is it
|
|
|
|
* the ID of any previously created container. This ID can later be used
|
|
|
|
* together with property names to be packed in other containers or
|
|
|
|
* inside @dialog itself.
|
|
|
|
*
|
|
|
|
* Returns: (transfer none): the #GtkFlowBox representing @property. The
|
|
|
|
* object belongs to @dialog and must not be
|
|
|
|
* freed.
|
|
|
|
*/
|
|
|
|
GtkWidget *
|
|
|
|
gimp_procedure_dialog_fill_flowbox_list (GimpProcedureDialog *dialog,
|
|
|
|
const gchar *container_id,
|
|
|
|
GList *properties)
|
|
|
|
{
|
|
|
|
g_return_val_if_fail (container_id != NULL, NULL);
|
|
|
|
|
2020-11-24 22:00:34 +08:00
|
|
|
return gimp_procedure_dialog_fill_container_list (dialog, container_id,
|
|
|
|
GTK_CONTAINER (gtk_flow_box_new ()),
|
|
|
|
properties);
|
2020-11-14 04:26:14 +08:00
|
|
|
}
|
|
|
|
|
2020-11-14 07:21:46 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* gimp_procedure_dialog_fill_frame:
|
|
|
|
* @dialog: the #GimpProcedureDialog.
|
|
|
|
* @container_id: a container identifier.
|
|
|
|
* @title_id: (nullable): the identifier for the title widget.
|
|
|
|
* @invert_title: whether to use the opposite value of @title_id if it
|
|
|
|
* represents a boolean widget.
|
|
|
|
* @contents_id: (nullable): the identifier for the contents.
|
|
|
|
*
|
|
|
|
* Creates a new #GtkFrame and packs @title_id as its title and
|
|
|
|
* @contents_id as its child.
|
|
|
|
* If @title_id represents a boolean property, its value will be used to
|
|
|
|
* renders @contents_id sensitive or not. If @invert_title is TRUE, then
|
|
|
|
* sensitivity binding is inverted.
|
|
|
|
*
|
|
|
|
* The @container_id must be a unique ID which is neither the name of a
|
|
|
|
* property of the #GimpProcedureConfig associated to @dialog, nor is it
|
|
|
|
* the ID of any previously created container. This ID can later be used
|
|
|
|
* together with property names to be packed in other containers or
|
|
|
|
* inside @dialog itself.
|
|
|
|
*
|
|
|
|
* Returns: (transfer none): the #GtkWidget representing @container_id. The
|
|
|
|
* object belongs to @dialog and must not be
|
|
|
|
* freed.
|
|
|
|
*/
|
|
|
|
GtkWidget *
|
|
|
|
gimp_procedure_dialog_fill_frame (GimpProcedureDialog *dialog,
|
|
|
|
const gchar *container_id,
|
|
|
|
const gchar *title_id,
|
|
|
|
gboolean invert_title,
|
|
|
|
const gchar *contents_id)
|
|
|
|
{
|
|
|
|
GtkWidget *frame;
|
|
|
|
GtkWidget *contents = NULL;
|
|
|
|
GtkWidget *title = NULL;
|
|
|
|
|
|
|
|
g_return_val_if_fail (container_id != NULL, NULL);
|
|
|
|
|
|
|
|
if (g_object_class_find_property (G_OBJECT_GET_CLASS (dialog->priv->config),
|
|
|
|
container_id))
|
|
|
|
{
|
|
|
|
g_warning ("%s: frame identifier '%s' cannot be an existing property name.",
|
|
|
|
G_STRFUNC, container_id);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((frame = g_hash_table_lookup (dialog->priv->widgets, container_id)))
|
|
|
|
{
|
|
|
|
g_warning ("%s: frame identifier '%s' was already configured.",
|
|
|
|
G_STRFUNC, container_id);
|
|
|
|
return frame;
|
|
|
|
}
|
|
|
|
|
2020-11-20 07:06:27 +08:00
|
|
|
frame = gimp_frame_new (NULL);
|
2020-11-14 07:21:46 +08:00
|
|
|
|
|
|
|
if (contents_id)
|
|
|
|
{
|
|
|
|
contents = gimp_procedure_dialog_get_widget (dialog, contents_id, G_TYPE_NONE);
|
|
|
|
if (! contents)
|
|
|
|
{
|
|
|
|
g_warning ("%s: no property or configured widget with identifier '%s'.",
|
|
|
|
G_STRFUNC, contents_id);
|
|
|
|
return frame;
|
|
|
|
}
|
|
|
|
|
2020-11-20 07:06:27 +08:00
|
|
|
g_object_ref (contents);
|
2020-11-14 07:21:46 +08:00
|
|
|
gtk_container_add (GTK_CONTAINER (frame), contents);
|
|
|
|
gtk_widget_show (contents);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (title_id)
|
|
|
|
{
|
|
|
|
title = gimp_procedure_dialog_get_widget (dialog, title_id, G_TYPE_NONE);
|
|
|
|
if (! title)
|
|
|
|
{
|
|
|
|
g_warning ("%s: no property or configured widget with identifier '%s'.",
|
|
|
|
G_STRFUNC, title_id);
|
|
|
|
return frame;
|
|
|
|
}
|
|
|
|
|
2020-11-20 07:06:27 +08:00
|
|
|
g_object_ref (title);
|
2020-11-14 07:21:46 +08:00
|
|
|
gtk_frame_set_label_widget (GTK_FRAME (frame), title);
|
|
|
|
gtk_widget_show (title);
|
|
|
|
|
|
|
|
if (contents && (GTK_IS_CHECK_BUTTON (title) || GTK_IS_SWITCH (title)))
|
|
|
|
{
|
|
|
|
GBindingFlags flags = G_BINDING_SYNC_CREATE;
|
|
|
|
|
|
|
|
if (invert_title)
|
|
|
|
flags |= G_BINDING_INVERT_BOOLEAN;
|
|
|
|
|
|
|
|
g_object_bind_property (title, "active",
|
|
|
|
contents, "sensitive",
|
|
|
|
flags);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
g_hash_table_insert (dialog->priv->widgets, g_strdup (container_id), frame);
|
libgimp: sink the floating references after adding them to hash table.
Though the previous implementation worked fine on C plug-ins, I realized
it was problematic on bindings. In particular, the Python binding at
least was somehow freeing returned floating objects, unless assigned to
a variable.
For instance, the widget returned by the following code:
> dialog.get_color_widget('color', True, GimpUi.ColorAreaType.FLAT)
… was freed by the PyGObject binding when it was floating, even though
(transfer none) was set (hence telling the binding it should not free
the returned object). The workaround was to assign it to some variable,
even though I was not planning to use it.
Making sure all references are full fixes it.
GObject docs also notes:
> **Note**: Floating references are a C convenience API and should not
> be used in modern GObject code. Language bindings in particular find
> the concept highly problematic, as floating references are not
> identifiable through annotations, and neither are deviations from the
> floating reference behavior, like types that inherit from
> GInitiallyUnowned and still return a full reference from
> g_object_new().
2021-04-20 23:08:31 +08:00
|
|
|
if (g_object_is_floating (frame))
|
|
|
|
g_object_ref_sink (frame);
|
2020-11-14 07:21:46 +08:00
|
|
|
|
|
|
|
return frame;
|
|
|
|
}
|
|
|
|
|
2021-04-07 02:10:46 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* gimp_procedure_dialog_set_sensitive:
|
|
|
|
* @dialog: the #GimpProcedureDialog.
|
|
|
|
* @property: name of a property of the #GimpProcedure @dialog
|
|
|
|
* has been created for.
|
|
|
|
* @sensitive: whether the widget associated to @property should
|
|
|
|
* be sensitive.
|
|
|
|
* @config: (nullable): an optional config object.
|
|
|
|
* @config_property: (nullable): name of a property of @config.
|
|
|
|
* @config_invert: whether to negate the value of @config_property.
|
|
|
|
*
|
|
|
|
* Sets sensitivity of the widget associated to @property in @dialog. If
|
|
|
|
* @config is %NULL, then it is set to the value of @sensitive.
|
|
|
|
* Otherwise @sensitive is ignored and sensitivity is bound to the value
|
|
|
|
* of @config_property of @config (or the negation of this value
|
|
|
|
* if @config_reverse is %TRUE).
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
gimp_procedure_dialog_set_sensitive (GimpProcedureDialog *dialog,
|
|
|
|
const gchar *property,
|
|
|
|
gboolean sensitive,
|
|
|
|
GObject *config,
|
|
|
|
const gchar *config_property,
|
|
|
|
gboolean config_invert)
|
|
|
|
{
|
|
|
|
GtkWidget *widget = NULL;
|
|
|
|
GParamSpec *pspec;
|
|
|
|
|
|
|
|
g_return_if_fail (GIMP_IS_PROCEDURE_DIALOG (dialog));
|
|
|
|
g_return_if_fail (property != NULL);
|
|
|
|
g_return_if_fail (config == NULL || config_property != NULL);
|
|
|
|
|
|
|
|
widget = g_hash_table_lookup (dialog->priv->widgets, property);
|
|
|
|
|
|
|
|
if (! widget)
|
|
|
|
{
|
|
|
|
pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (dialog->priv->config),
|
|
|
|
property);
|
|
|
|
if (! pspec)
|
|
|
|
{
|
|
|
|
g_warning ("%s: parameter %s does not exist on the GimpProcedure.",
|
|
|
|
G_STRFUNC, property);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (config)
|
|
|
|
{
|
|
|
|
pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (config),
|
|
|
|
config_property);
|
|
|
|
if (! pspec)
|
|
|
|
{
|
|
|
|
g_warning ("%s: parameter %s does not exist on the config object.",
|
|
|
|
G_STRFUNC, config_property);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (widget)
|
|
|
|
{
|
|
|
|
if (config)
|
|
|
|
{
|
|
|
|
g_object_bind_property (config, config_property,
|
|
|
|
widget, "sensitive",
|
|
|
|
G_BINDING_SYNC_CREATE | (config_invert ? G_BINDING_INVERT_BOOLEAN : 0));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
gtk_widget_set_sensitive (widget, sensitive);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* Set for later creation. */
|
|
|
|
GimpProcedureDialogSensitiveData *data;
|
|
|
|
|
|
|
|
data = g_slice_new0 (GimpProcedureDialogSensitiveData);
|
|
|
|
|
|
|
|
data->sensitive = sensitive;
|
|
|
|
if (config)
|
|
|
|
{
|
|
|
|
data->config = g_object_ref (config);
|
|
|
|
data->config_property = g_strdup (config_property);
|
|
|
|
data->config_invert = config_invert;
|
|
|
|
}
|
|
|
|
|
|
|
|
g_hash_table_insert (dialog->priv->sensitive_data, g_strdup (property), data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-14 04:26:14 +08:00
|
|
|
/**
|
|
|
|
* gimp_procedure_dialog_run:
|
|
|
|
* @dialog: the #GimpProcedureDialog.
|
|
|
|
*
|
|
|
|
* Show @dialog and only returns when the user finished interacting with
|
|
|
|
* it (either validating choices or canceling).
|
|
|
|
*
|
|
|
|
* Returns: %TRUE if the dialog was validated, %FALSE otherwise.
|
|
|
|
*/
|
2019-09-21 01:24:40 +08:00
|
|
|
gboolean
|
|
|
|
gimp_procedure_dialog_run (GimpProcedureDialog *dialog)
|
|
|
|
{
|
|
|
|
g_return_val_if_fail (GIMP_IS_PROCEDURE_DIALOG (dialog), FALSE);
|
|
|
|
|
|
|
|
while (TRUE)
|
|
|
|
{
|
|
|
|
gint response = gimp_dialog_run (GIMP_DIALOG (dialog));
|
|
|
|
|
|
|
|
if (response == RESPONSE_RESET)
|
|
|
|
{
|
2019-09-27 01:06:17 +08:00
|
|
|
if (! dialog->priv->reset_popover)
|
|
|
|
{
|
|
|
|
GtkWidget *button;
|
|
|
|
GtkWidget *vbox;
|
|
|
|
|
|
|
|
button = gtk_dialog_get_widget_for_response (GTK_DIALOG (dialog),
|
|
|
|
response);
|
|
|
|
|
|
|
|
dialog->priv->reset_popover = gtk_popover_new (button);
|
|
|
|
|
|
|
|
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 4);
|
|
|
|
gtk_container_set_border_width (GTK_CONTAINER (vbox), 4);
|
|
|
|
gtk_container_add (GTK_CONTAINER (dialog->priv->reset_popover),
|
|
|
|
vbox);
|
|
|
|
gtk_widget_show (vbox);
|
|
|
|
|
|
|
|
button = gtk_button_new_with_mnemonic (_("Reset to _Initial "
|
|
|
|
"Values"));
|
|
|
|
gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
|
|
|
|
gtk_widget_show (button);
|
|
|
|
|
|
|
|
g_signal_connect (button, "clicked",
|
|
|
|
G_CALLBACK (gimp_procedure_dialog_reset_initial),
|
|
|
|
dialog);
|
|
|
|
|
|
|
|
button = gtk_button_new_with_mnemonic (_("Reset to _Factory "
|
|
|
|
"Defaults"));
|
|
|
|
gtk_box_pack_start (GTK_BOX (vbox), button, FALSE, FALSE, 0);
|
|
|
|
gtk_widget_show (button);
|
|
|
|
|
|
|
|
g_signal_connect (button, "clicked",
|
|
|
|
G_CALLBACK (gimp_procedure_dialog_reset_factory),
|
|
|
|
dialog);
|
|
|
|
}
|
|
|
|
|
|
|
|
gtk_popover_popup (GTK_POPOVER (dialog->priv->reset_popover));
|
2019-09-21 01:24:40 +08:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return response == GTK_RESPONSE_OK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-09-27 01:06:17 +08:00
|
|
|
|
|
|
|
|
|
|
|
/* private functions */
|
|
|
|
|
|
|
|
static void
|
|
|
|
gimp_procedure_dialog_reset_initial (GtkWidget *button,
|
|
|
|
GimpProcedureDialog *dialog)
|
|
|
|
{
|
|
|
|
gimp_config_copy (GIMP_CONFIG (dialog->priv->initial_config),
|
|
|
|
GIMP_CONFIG (dialog->priv->config),
|
|
|
|
0);
|
|
|
|
|
|
|
|
gtk_popover_popdown (GTK_POPOVER (dialog->priv->reset_popover));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gimp_procedure_dialog_reset_factory (GtkWidget *button,
|
|
|
|
GimpProcedureDialog *dialog)
|
|
|
|
{
|
|
|
|
gimp_config_reset (GIMP_CONFIG (dialog->priv->config));
|
|
|
|
|
|
|
|
gtk_popover_popdown (GTK_POPOVER (dialog->priv->reset_popover));
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gimp_procedure_dialog_load_defaults (GtkWidget *button,
|
|
|
|
GimpProcedureDialog *dialog)
|
|
|
|
{
|
|
|
|
GError *error = NULL;
|
|
|
|
|
2019-09-29 02:51:16 +08:00
|
|
|
if (! gimp_procedure_config_load_default (dialog->priv->config, &error))
|
2019-09-27 01:06:17 +08:00
|
|
|
{
|
|
|
|
if (error)
|
|
|
|
{
|
|
|
|
g_printerr ("Loading default values from disk failed: %s\n",
|
|
|
|
error->message);
|
|
|
|
g_clear_error (&error);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
g_printerr ("No default values found on disk\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
gimp_procedure_dialog_save_defaults (GtkWidget *button,
|
|
|
|
GimpProcedureDialog *dialog)
|
|
|
|
{
|
|
|
|
GError *error = NULL;
|
|
|
|
|
2019-09-29 02:51:16 +08:00
|
|
|
if (! gimp_procedure_config_save_default (dialog->priv->config, &error))
|
2019-09-27 01:06:17 +08:00
|
|
|
{
|
|
|
|
g_printerr ("Saving default values to disk failed: %s\n",
|
|
|
|
error->message);
|
|
|
|
g_clear_error (&error);
|
|
|
|
}
|
2021-01-08 04:59:59 +08:00
|
|
|
gtk_widget_set_sensitive (dialog->priv->load_settings_button,
|
|
|
|
gimp_procedure_config_has_default (dialog->priv->config));
|
2019-09-27 01:06:17 +08:00
|
|
|
}
|
2020-11-03 05:42:17 +08:00
|
|
|
|
2020-11-20 08:29:08 +08:00
|
|
|
static gboolean
|
|
|
|
gimp_procedure_dialog_check_mnemonic (GimpProcedureDialog *dialog,
|
|
|
|
GtkWidget *widget,
|
|
|
|
const gchar *id,
|
|
|
|
const gchar *core_id)
|
|
|
|
{
|
|
|
|
GtkWidget *label = NULL;
|
|
|
|
gchar *duplicate;
|
|
|
|
gboolean success = TRUE;
|
|
|
|
guint mnemonic = GDK_KEY_VoidSymbol;
|
|
|
|
|
|
|
|
g_return_val_if_fail ((id && ! core_id) || (core_id && ! id), FALSE);
|
|
|
|
|
|
|
|
if (GIMP_IS_LABELED (widget))
|
|
|
|
{
|
|
|
|
label = gimp_labeled_get_label (GIMP_LABELED (widget));
|
|
|
|
}
|
2020-12-30 20:31:16 +08:00
|
|
|
else
|
2020-11-20 08:29:08 +08:00
|
|
|
{
|
2020-12-30 20:31:16 +08:00
|
|
|
GList *labels = gtk_widget_list_mnemonic_labels (widget);
|
|
|
|
|
|
|
|
if (g_list_length (labels) >= 1)
|
|
|
|
{
|
|
|
|
if (g_list_length (labels) > 1)
|
|
|
|
g_printerr ("Procedure '%s': %d mnemonics for property %s. Too much?\n",
|
|
|
|
gimp_procedure_get_name (dialog->priv->procedure),
|
|
|
|
g_list_length (labels),
|
|
|
|
id ? id : core_id);
|
|
|
|
|
|
|
|
label = labels->data;
|
|
|
|
}
|
|
|
|
|
|
|
|
g_list_free (labels);
|
2020-11-20 08:29:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (label &&
|
|
|
|
(mnemonic = gtk_label_get_mnemonic_keyval (GTK_LABEL (label))) &&
|
|
|
|
mnemonic != GDK_KEY_VoidSymbol)
|
|
|
|
{
|
|
|
|
duplicate = g_hash_table_lookup (dialog->priv->core_mnemonics, GINT_TO_POINTER (mnemonic));
|
|
|
|
if (duplicate && g_strcmp0 (duplicate, id ? id : core_id) != 0)
|
|
|
|
{
|
|
|
|
g_printerr ("Procedure '%s': duplicate mnemonic %s for label of property %s and dialog button %s\n",
|
|
|
|
gimp_procedure_get_name (dialog->priv->procedure),
|
|
|
|
gdk_keyval_name (mnemonic), id, duplicate);
|
|
|
|
success = FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (success)
|
|
|
|
{
|
|
|
|
duplicate = g_hash_table_lookup (dialog->priv->mnemonics, GINT_TO_POINTER (mnemonic));
|
|
|
|
if (duplicate && g_strcmp0 (duplicate, id ? id : core_id) != 0)
|
|
|
|
{
|
|
|
|
g_printerr ("Procedure '%s': duplicate mnemonic %s for label of properties %s and %s\n",
|
|
|
|
gimp_procedure_get_name (dialog->priv->procedure),
|
|
|
|
gdk_keyval_name (mnemonic), id, duplicate);
|
|
|
|
success = FALSE;
|
|
|
|
}
|
|
|
|
else if (! duplicate)
|
|
|
|
{
|
|
|
|
if (id)
|
|
|
|
g_hash_table_insert (dialog->priv->mnemonics, GINT_TO_POINTER (mnemonic), g_strdup (id));
|
|
|
|
else
|
|
|
|
g_hash_table_insert (dialog->priv->core_mnemonics, GINT_TO_POINTER (mnemonic), g_strdup (core_id));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
g_printerr ("Procedure '%s': no mnemonic for property %s\n",
|
2020-12-30 20:31:16 +08:00
|
|
|
gimp_procedure_get_name (dialog->priv->procedure),
|
|
|
|
id ? id : core_id);
|
2020-11-20 08:29:08 +08:00
|
|
|
success = FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return success;
|
|
|
|
}
|
2020-11-24 22:00:34 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* gimp_procedure_dialog_fill_container_list:
|
2021-01-02 20:00:37 +08:00
|
|
|
* @dialog: the #GimpProcedureDialog.
|
|
|
|
* @container_id: a container identifier.
|
|
|
|
* @container: (transfer full): The new container that should be used if none
|
|
|
|
* exists yet
|
|
|
|
* @properties: (nullable) (element-type gchar*): the list of property names.
|
2020-11-24 22:00:34 +08:00
|
|
|
*
|
2021-02-24 19:33:03 +08:00
|
|
|
* A generic function to be used by various public functions
|
2020-11-24 22:00:34 +08:00
|
|
|
* gimp_procedure_dialog_fill_*_list(). Note in particular that
|
|
|
|
* @container is taken over by this function which may return it or not.
|
|
|
|
* @container is assumed to be a floating GtkContainer (i.e. newly
|
|
|
|
* created widget without a parent yet).
|
|
|
|
* If the object returns a different object (because @container_id
|
|
|
|
* already represents another widget) or %NULL, the function takes care
|
|
|
|
* of freeing @container. Calling code must therefore not reuse the
|
|
|
|
* pointer anymore.
|
|
|
|
*/
|
|
|
|
static GtkWidget *
|
|
|
|
gimp_procedure_dialog_fill_container_list (GimpProcedureDialog *dialog,
|
|
|
|
const gchar *container_id,
|
|
|
|
GtkContainer *container,
|
|
|
|
GList *properties)
|
|
|
|
{
|
|
|
|
GList *iter;
|
|
|
|
gboolean free_properties = FALSE;
|
|
|
|
|
|
|
|
g_return_val_if_fail (container_id != NULL, NULL);
|
|
|
|
g_return_val_if_fail (GTK_IS_CONTAINER (container), NULL);
|
|
|
|
g_return_val_if_fail (g_object_is_floating (G_OBJECT (container)), NULL);
|
|
|
|
|
|
|
|
g_object_ref_sink (container);
|
|
|
|
if (g_object_class_find_property (G_OBJECT_GET_CLASS (dialog->priv->config),
|
|
|
|
container_id))
|
|
|
|
{
|
|
|
|
g_warning ("%s: container identifier '%s' cannot be an existing property name.",
|
|
|
|
G_STRFUNC, container_id);
|
|
|
|
g_object_unref (container);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (g_hash_table_lookup (dialog->priv->widgets, container_id))
|
|
|
|
{
|
|
|
|
g_warning ("%s: container identifier '%s' was already configured.",
|
|
|
|
G_STRFUNC, container_id);
|
|
|
|
g_object_unref (container);
|
|
|
|
return g_hash_table_lookup (dialog->priv->widgets, container_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (! properties)
|
|
|
|
{
|
|
|
|
GParamSpec **pspecs;
|
|
|
|
guint n_pspecs;
|
|
|
|
gint i;
|
|
|
|
|
|
|
|
pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (dialog->priv->config),
|
|
|
|
&n_pspecs);
|
|
|
|
|
|
|
|
for (i = 0; i < n_pspecs; i++)
|
|
|
|
{
|
|
|
|
const gchar *prop_name;
|
|
|
|
GParamSpec *pspec = pspecs[i];
|
|
|
|
|
|
|
|
/* skip our own properties */
|
|
|
|
if (pspec->owner_type == GIMP_TYPE_PROCEDURE_CONFIG)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
prop_name = g_param_spec_get_name (pspec);
|
|
|
|
properties = g_list_prepend (properties, (gpointer) prop_name);
|
|
|
|
}
|
|
|
|
|
|
|
|
properties = g_list_reverse (properties);
|
|
|
|
|
|
|
|
if (properties)
|
|
|
|
free_properties = TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (iter = properties; iter; iter = iter->next)
|
|
|
|
{
|
|
|
|
GtkWidget *widget;
|
|
|
|
|
|
|
|
widget = gimp_procedure_dialog_get_widget (dialog, iter->data, G_TYPE_NONE);
|
|
|
|
if (widget)
|
|
|
|
{
|
|
|
|
/* Reference the widget because the hash table will
|
|
|
|
* unreference it anyway when getting destroyed so we don't
|
|
|
|
* want to give the only reference to the parent widget.
|
|
|
|
*/
|
|
|
|
g_object_ref (widget);
|
|
|
|
gtk_container_add (container, widget);
|
|
|
|
gtk_widget_show (widget);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (free_properties)
|
|
|
|
g_list_free (properties);
|
|
|
|
|
|
|
|
g_hash_table_insert (dialog->priv->widgets, g_strdup (container_id), container);
|
libgimp: sink the floating references after adding them to hash table.
Though the previous implementation worked fine on C plug-ins, I realized
it was problematic on bindings. In particular, the Python binding at
least was somehow freeing returned floating objects, unless assigned to
a variable.
For instance, the widget returned by the following code:
> dialog.get_color_widget('color', True, GimpUi.ColorAreaType.FLAT)
… was freed by the PyGObject binding when it was floating, even though
(transfer none) was set (hence telling the binding it should not free
the returned object). The workaround was to assign it to some variable,
even though I was not planning to use it.
Making sure all references are full fixes it.
GObject docs also notes:
> **Note**: Floating references are a C convenience API and should not
> be used in modern GObject code. Language bindings in particular find
> the concept highly problematic, as floating references are not
> identifiable through annotations, and neither are deviations from the
> floating reference behavior, like types that inherit from
> GInitiallyUnowned and still return a full reference from
> g_object_new().
2021-04-20 23:08:31 +08:00
|
|
|
if (g_object_is_floating (container))
|
|
|
|
g_object_ref_sink (container);
|
2020-11-24 22:00:34 +08:00
|
|
|
|
|
|
|
return GTK_WIDGET (container);
|
|
|
|
}
|
2021-04-07 02:10:46 +08:00
|
|
|
|
|
|
|
static void
|
|
|
|
gimp_procedure_dialog_sensitive_data_free (GimpProcedureDialogSensitiveData *data)
|
|
|
|
{
|
|
|
|
g_free (data->config_property);
|
|
|
|
g_clear_object (&data->config);
|
|
|
|
|
|
|
|
g_slice_free (GimpProcedureDialogSensitiveData, data);
|
|
|
|
}
|