mirror of
https://gitlab.gnome.org/GNOME/gimp.git
synced 2025-07-03 09:23:24 +00:00

As explained in GtkSizeGroup docs, all objects inside a size group holds a reference to it. So once we destroy the last object inside these, it will be freed too and we should drop the initial reference after adding the objects. Only the main size group reference is kept until the end, because we are adding and removing objects from it regularly, so it is possible that it is empty again at some intermediary states. Yet we don't want to free it when this happens.
1860 lines
66 KiB
C
1860 lines
66 KiB
C
/* GIMP - The GNU Image Manipulation Program
|
|
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
|
*
|
|
* gimpproceduredialog.c
|
|
* Copyright (C) 2019 Michael Natterer <mitch@gimp.org>
|
|
* Copyright (C) 2020 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 <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"
|
|
|
|
#include "gimpprocedureconfig-private.h"
|
|
|
|
#include "libgimp-intl.h"
|
|
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_PROCEDURE,
|
|
PROP_CONFIG,
|
|
N_PROPS
|
|
};
|
|
|
|
#define RESPONSE_RESET 1
|
|
|
|
|
|
struct _GimpProcedureDialogPrivate
|
|
{
|
|
GimpProcedure *procedure;
|
|
GimpProcedureConfig *config;
|
|
GimpProcedureConfig *initial_config;
|
|
|
|
GtkWidget *reset_popover;
|
|
GtkWidget *load_settings_button;
|
|
|
|
GHashTable *widgets;
|
|
GHashTable *mnemonics;
|
|
GHashTable *core_mnemonics;
|
|
GtkSizeGroup *label_group;
|
|
|
|
GHashTable *sensitive_data;
|
|
};
|
|
|
|
typedef struct GimpProcedureDialogSensitiveData
|
|
{
|
|
gboolean sensitive;
|
|
|
|
GObject *config;
|
|
gchar *config_property;
|
|
gboolean config_invert;
|
|
} GimpProcedureDialogSensitiveData;
|
|
|
|
|
|
static void gimp_procedure_dialog_constructed (GObject *object);
|
|
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);
|
|
|
|
static void gimp_procedure_dialog_real_fill_list (GimpProcedureDialog *dialog,
|
|
GimpProcedure *procedure,
|
|
GimpProcedureConfig *config,
|
|
GList *properties);
|
|
|
|
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);
|
|
|
|
static gboolean gimp_procedure_dialog_check_mnemonic (GimpProcedureDialog *dialog,
|
|
GtkWidget *widget,
|
|
const gchar *id,
|
|
const gchar *core_id);
|
|
static GtkWidget *
|
|
gimp_procedure_dialog_fill_container_list (GimpProcedureDialog *dialog,
|
|
const gchar *container_id,
|
|
GtkContainer *container,
|
|
GList *properties);
|
|
|
|
static void gimp_procedure_dialog_sensitive_data_free (GimpProcedureDialogSensitiveData *data);
|
|
|
|
|
|
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);
|
|
|
|
object_class->constructed = gimp_procedure_dialog_constructed;
|
|
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;
|
|
|
|
klass->fill_list = gimp_procedure_dialog_real_fill_list;
|
|
|
|
props[PROP_PROCEDURE] =
|
|
g_param_spec_object ("procedure",
|
|
"Procedure",
|
|
"The GimpProcedure this dialog is used with",
|
|
GIMP_TYPE_PROCEDURE,
|
|
GIMP_PARAM_READWRITE |
|
|
G_PARAM_CONSTRUCT);
|
|
|
|
props[PROP_CONFIG] =
|
|
g_param_spec_object ("config",
|
|
"Config",
|
|
"The GimpProcedureConfig this dialog is editing",
|
|
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);
|
|
|
|
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);
|
|
dialog->priv->core_mnemonics = g_hash_table_new_full (g_direct_hash, NULL, NULL, g_free);
|
|
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);
|
|
}
|
|
|
|
static void
|
|
gimp_procedure_dialog_constructed (GObject *object)
|
|
{
|
|
GimpProcedureDialog *dialog;
|
|
GimpProcedure *procedure;
|
|
const gchar *ok_label;
|
|
GtkWidget *hbox;
|
|
GtkWidget *button;
|
|
GtkWidget *widget;
|
|
GtkWidget *box;
|
|
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");
|
|
|
|
/* 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);
|
|
gimp_procedure_dialog_check_mnemonic (GIMP_PROCEDURE_DIALOG (dialog), button, NULL, "reset");
|
|
|
|
/* Cancel and OK buttons. */
|
|
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");
|
|
/* 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);
|
|
|
|
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);
|
|
|
|
button = gtk_button_new_with_mnemonic (_("_Load Saved Settings"));
|
|
gtk_widget_set_tooltip_text (button, _("Load settings saved with \"Save Settings\" button"));
|
|
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);
|
|
gtk_widget_set_sensitive (button,
|
|
gimp_procedure_config_has_default (dialog->priv->config));
|
|
dialog->priv->load_settings_button = button;
|
|
|
|
button = gtk_button_new_with_mnemonic (_("_Save Settings"));
|
|
gtk_widget_set_tooltip_text (button, _("Store current settings for later reuse"));
|
|
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);
|
|
}
|
|
|
|
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);
|
|
g_clear_object (&dialog->priv->initial_config);
|
|
|
|
g_clear_pointer (&dialog->priv->reset_popover, gtk_widget_destroy);
|
|
|
|
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);
|
|
|
|
g_clear_pointer (&dialog->priv->sensitive_data, g_hash_table_destroy);
|
|
|
|
g_clear_object (&dialog->priv->label_group);
|
|
|
|
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);
|
|
|
|
if (dialog->priv->config)
|
|
dialog->priv->initial_config =
|
|
gimp_config_duplicate (GIMP_CONFIG (dialog->priv->config));
|
|
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;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* gimp_procedure_dialog_new:
|
|
* @procedure: the associated #GimpProcedure.
|
|
* @config: a #GimpProcedureConfig from which properties will be
|
|
* turned into widgets.
|
|
* @title: (nullable): a dialog title.
|
|
*
|
|
* Creates a new dialog for @procedure using widgets generated from
|
|
* properties of @config.
|
|
* 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.
|
|
*
|
|
* Returns: (transfer full): the newly created #GimpProcedureDialog.
|
|
*/
|
|
GtkWidget *
|
|
gimp_procedure_dialog_new (GimpProcedure *procedure,
|
|
GimpProcedureConfig *config,
|
|
const gchar *title)
|
|
{
|
|
GtkWidget *dialog;
|
|
GtkWidget *bogus = NULL;
|
|
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);
|
|
g_return_val_if_fail (gimp_procedure_config_get_procedure (config) ==
|
|
procedure, NULL);
|
|
g_return_val_if_fail (title != NULL || gimp_procedure_get_menu_label (procedure), NULL);
|
|
|
|
help_id = gimp_procedure_get_help_id (procedure);
|
|
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));
|
|
}
|
|
|
|
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);
|
|
|
|
g_clear_object (&bogus);
|
|
|
|
return GTK_WIDGET (dialog);
|
|
}
|
|
|
|
/**
|
|
* gimp_procedure_dialog_get_widget:
|
|
* @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.
|
|
* @widget_type: alternative widget type. %G_TYPE_NONE will create the
|
|
* default type of widget for the associated property
|
|
* type.
|
|
*
|
|
* 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.
|
|
*
|
|
* 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)
|
|
{
|
|
GtkWidget *widget = NULL;
|
|
GtkWidget *label = NULL;
|
|
GimpProcedureDialogSensitiveData *binding;
|
|
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)
|
|
{
|
|
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)
|
|
widget = gimp_prop_switch_new (G_OBJECT (dialog->priv->config),
|
|
property,
|
|
_(g_param_spec_get_nick (pspec)),
|
|
&label, NULL);
|
|
}
|
|
else if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_INT ||
|
|
G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_DOUBLE)
|
|
{
|
|
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;
|
|
}
|
|
gimp_range_estimate_settings (minimum, maximum, &step, &page, &digits);
|
|
|
|
if (widget_type == G_TYPE_NONE || widget_type == GIMP_TYPE_LABEL_SPIN)
|
|
{
|
|
widget = gimp_prop_label_spin_new (G_OBJECT (dialog->priv->config),
|
|
property, digits);
|
|
}
|
|
else if (widget_type == GIMP_TYPE_SCALE_ENTRY)
|
|
{
|
|
widget = gimp_prop_scale_entry_new (G_OBJECT (dialog->priv->config),
|
|
property,
|
|
_(g_param_spec_get_nick (pspec)),
|
|
1.0, FALSE, 0.0, 0.0);
|
|
}
|
|
else if (widget_type == GIMP_TYPE_SPIN_BUTTON)
|
|
{
|
|
/* Just some spin button without label. */
|
|
widget = gimp_prop_spin_button_new (G_OBJECT (dialog->priv->config),
|
|
property, step, page, digits);
|
|
}
|
|
}
|
|
else if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_STRING)
|
|
{
|
|
if (widget_type == G_TYPE_NONE || widget_type == GTK_TYPE_TEXT_VIEW)
|
|
{
|
|
GtkTextBuffer *buffer;
|
|
|
|
buffer = gimp_prop_text_buffer_new (G_OBJECT (dialog->priv->config),
|
|
property, -1);
|
|
widget = gtk_text_view_new_with_buffer (buffer);
|
|
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);
|
|
g_object_unref (buffer);
|
|
}
|
|
else if (widget_type == GTK_TYPE_ENTRY)
|
|
{
|
|
widget = gimp_prop_entry_new (G_OBJECT (dialog->priv->config),
|
|
property, -1);
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
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",
|
|
G_STRFUNC, g_type_name (widget_type),
|
|
property, G_PARAM_SPEC_TYPE_NAME (pspec));
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
const gchar *tooltip = g_param_spec_get_blurb (pspec);
|
|
if (tooltip)
|
|
gimp_help_set_help_data (widget, tooltip, NULL);
|
|
if (GIMP_IS_LABELED (widget) || label)
|
|
{
|
|
if (! label)
|
|
label = gimp_labeled_get_label (GIMP_LABELED (widget));
|
|
|
|
gtk_size_group_add_widget (dialog->priv->label_group, label);
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
gimp_procedure_dialog_check_mnemonic (dialog, widget, property, NULL);
|
|
g_hash_table_insert (dialog->priv->widgets, g_strdup (property), widget);
|
|
if (g_object_is_floating (widget))
|
|
g_object_ref_sink (widget);
|
|
|
|
return widget;
|
|
}
|
|
|
|
/**
|
|
* 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));
|
|
const gchar *tooltip = g_param_spec_get_blurb (pspec);
|
|
|
|
gtk_size_group_add_widget (dialog->priv->label_group, label);
|
|
if (tooltip)
|
|
gimp_help_set_help_data (label, tooltip, NULL);
|
|
}
|
|
|
|
|
|
gimp_procedure_dialog_check_mnemonic (dialog, widget, property, NULL);
|
|
g_hash_table_insert (dialog->priv->widgets, g_strdup (property), widget);
|
|
if (g_object_is_floating (widget))
|
|
g_object_ref_sink (widget);
|
|
|
|
return widget;
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
{
|
|
const gchar *tooltip = g_param_spec_get_blurb (pspec);
|
|
if (tooltip)
|
|
gimp_help_set_help_data (widget, tooltip, NULL);
|
|
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);
|
|
if (g_object_is_floating (widget))
|
|
g_object_ref_sink (widget);
|
|
|
|
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);
|
|
g_hash_table_insert (dialog->priv->widgets, g_strdup (property), widget);
|
|
if (g_object_is_floating (widget))
|
|
g_object_ref_sink (widget);
|
|
|
|
return widget;
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
if (g_object_is_floating (label))
|
|
g_object_ref_sink (label);
|
|
|
|
return label;
|
|
}
|
|
|
|
/**
|
|
* gimp_procedure_dialog_fill:
|
|
* @dialog: the #GimpProcedureDialog.
|
|
* @...: a %NULL-terminated list of property names.
|
|
*
|
|
* Populate @dialog with the widgets corresponding to every listed
|
|
* 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" -->
|
|
* gimp_procedure_dialog_fill (dialog, NULL);
|
|
* ]|
|
|
* 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" -->
|
|
* 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.
|
|
*/
|
|
void
|
|
gimp_procedure_dialog_fill (GimpProcedureDialog *dialog,
|
|
...)
|
|
{
|
|
const gchar *prop_name;
|
|
GList *list = NULL;
|
|
va_list va_args;
|
|
|
|
g_return_if_fail (GIMP_IS_PROCEDURE_DIALOG (dialog));
|
|
|
|
va_start (va_args, dialog);
|
|
|
|
while ((prop_name = va_arg (va_args, const gchar *)))
|
|
list = g_list_prepend (list, (gpointer) prop_name);
|
|
|
|
va_end (va_args);
|
|
|
|
list = g_list_reverse (list);
|
|
gimp_procedure_dialog_fill_list (dialog, list);
|
|
if (list)
|
|
g_list_free (list);
|
|
}
|
|
|
|
/**
|
|
* gimp_procedure_dialog_fill_list: (rename-to gimp_procedure_dialog_fill)
|
|
* @dialog: the #GimpProcedureDialog.
|
|
* @properties: (nullable) (element-type gchar*): the list of property names.
|
|
*
|
|
* Populate @dialog with the widgets corresponding to every listed
|
|
* 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" -->
|
|
* gimp_procedure_dialog_fill_list (dialog, NULL);
|
|
* ]|
|
|
* 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" -->
|
|
* 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.
|
|
*/
|
|
void
|
|
gimp_procedure_dialog_fill_list (GimpProcedureDialog *dialog,
|
|
GList *properties)
|
|
{
|
|
gboolean free_properties = FALSE;
|
|
|
|
if (! properties)
|
|
{
|
|
GParamSpec **pspecs;
|
|
guint n_pspecs;
|
|
guint 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;
|
|
}
|
|
|
|
GIMP_PROCEDURE_DIALOG_GET_CLASS (dialog)->fill_list (dialog,
|
|
dialog->priv->procedure,
|
|
dialog->priv->config,
|
|
properties);
|
|
|
|
if (free_properties)
|
|
g_list_free (properties);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
|
|
return gimp_procedure_dialog_fill_container_list (dialog, container_id,
|
|
GTK_CONTAINER (gtk_flow_box_new ()),
|
|
properties);
|
|
}
|
|
|
|
|
|
/**
|
|
* 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;
|
|
}
|
|
|
|
frame = gimp_frame_new (NULL);
|
|
|
|
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;
|
|
}
|
|
|
|
g_object_ref (contents);
|
|
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;
|
|
}
|
|
|
|
g_object_ref (title);
|
|
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);
|
|
if (g_object_is_floating (frame))
|
|
g_object_ref_sink (frame);
|
|
|
|
return frame;
|
|
}
|
|
|
|
|
|
/**
|
|
* gimp_procedure_dialog_fill_expander:
|
|
* @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 #GtkExpander and packs @title_id as its title
|
|
* and @contents_id as content.
|
|
* If @title_id represents a boolean property, its value will be used to
|
|
* expand the #GtkExpander. If @invert_title is TRUE, then expand 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_expander (GimpProcedureDialog *dialog,
|
|
const gchar *container_id,
|
|
const gchar *title_id,
|
|
gboolean invert_title,
|
|
const gchar *contents_id)
|
|
{
|
|
GtkWidget *expander;
|
|
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: expander identifier '%s' cannot be an existing property name.",
|
|
G_STRFUNC, container_id);
|
|
return NULL;
|
|
}
|
|
|
|
if ((expander = g_hash_table_lookup (dialog->priv->widgets, container_id)))
|
|
{
|
|
g_warning ("%s: expander identifier '%s' was already configured.",
|
|
G_STRFUNC, container_id);
|
|
return expander;
|
|
}
|
|
|
|
expander = gtk_expander_new (NULL);
|
|
|
|
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 expander;
|
|
}
|
|
|
|
g_object_ref (contents);
|
|
gtk_container_add (GTK_CONTAINER (expander), 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 expander;
|
|
}
|
|
|
|
g_object_ref (title);
|
|
gtk_expander_set_label_widget (GTK_EXPANDER (expander), title);
|
|
gtk_expander_set_resize_toplevel (GTK_EXPANDER (expander), TRUE);
|
|
gtk_widget_show (title);
|
|
g_object_bind_property (title, "sensitive",
|
|
expander, "sensitive",
|
|
G_BINDING_SYNC_CREATE);
|
|
|
|
if (contents && (GTK_IS_CHECK_BUTTON (title) || GTK_IS_SWITCH (title)))
|
|
{
|
|
GBindingFlags flags = G_BINDING_SYNC_CREATE;
|
|
gboolean active;
|
|
|
|
/* Workaround for connecting check button state to expanded state of
|
|
* GtkExpander. This is required as GtkExpander do not pass click
|
|
* events to label widget.
|
|
* Please see https://bugzilla.gnome.org/show_bug.cgi?id=705971
|
|
*/
|
|
if (invert_title)
|
|
flags |= G_BINDING_INVERT_BOOLEAN;
|
|
|
|
g_object_get (title, "active", &active, NULL);
|
|
gtk_expander_set_expanded (GTK_EXPANDER (expander),
|
|
invert_title ? ! active : active);
|
|
g_object_bind_property (expander, "expanded",
|
|
title, "active",
|
|
flags);
|
|
}
|
|
}
|
|
|
|
g_hash_table_insert (dialog->priv->widgets, g_strdup (container_id), expander);
|
|
if (g_object_is_floating (expander))
|
|
g_object_ref_sink (expander);
|
|
|
|
return expander;
|
|
}
|
|
|
|
|
|
/**
|
|
* 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);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
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)
|
|
{
|
|
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));
|
|
}
|
|
else
|
|
{
|
|
return response == GTK_RESPONSE_OK;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/* 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;
|
|
|
|
if (! gimp_procedure_config_load_default (dialog->priv->config, &error))
|
|
{
|
|
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;
|
|
|
|
if (! gimp_procedure_config_save_default (dialog->priv->config, &error))
|
|
{
|
|
g_printerr ("Saving default values to disk failed: %s\n",
|
|
error->message);
|
|
g_clear_error (&error);
|
|
}
|
|
gtk_widget_set_sensitive (dialog->priv->load_settings_button,
|
|
gimp_procedure_config_has_default (dialog->priv->config));
|
|
}
|
|
|
|
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));
|
|
}
|
|
else
|
|
{
|
|
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);
|
|
}
|
|
|
|
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",
|
|
gimp_procedure_get_name (dialog->priv->procedure),
|
|
id ? id : core_id);
|
|
success = FALSE;
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
/**
|
|
* gimp_procedure_dialog_fill_container_list:
|
|
* @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.
|
|
*
|
|
* A generic function to be used by various public functions
|
|
* 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;
|
|
GtkSizeGroup *sz_group;
|
|
|
|
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;
|
|
guint 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;
|
|
}
|
|
|
|
sz_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
|
|
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);
|
|
if (GIMP_IS_LABELED (widget))
|
|
{
|
|
GtkWidget *label = gimp_labeled_get_label (GIMP_LABELED (widget));
|
|
gtk_size_group_remove_widget (dialog->priv->label_group, label);
|
|
gtk_size_group_add_widget (sz_group, label);
|
|
}
|
|
gtk_widget_show (widget);
|
|
}
|
|
}
|
|
g_clear_object (&sz_group);
|
|
|
|
if (free_properties)
|
|
g_list_free (properties);
|
|
|
|
g_hash_table_insert (dialog->priv->widgets, g_strdup (container_id), container);
|
|
if (g_object_is_floating (container))
|
|
g_object_ref_sink (container);
|
|
|
|
return GTK_WIDGET (container);
|
|
}
|
|
|
|
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);
|
|
}
|