PIKApp/libpika/pikaproceduredialog.c

2843 lines
104 KiB
C
Raw Normal View History

2023-09-26 00:35:21 +02:00
/* PIKA - Photo and Image Kooker Application
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
*
* Original copyright, applying to most contents (license remains unchanged):
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* pikaproceduredialog.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 "libpikawidgets/pikawidgets.h"
2023-10-30 23:55:30 +01:00
#include "libpika/pikapropwidgets.h"
2023-09-26 00:35:21 +02:00
#include "pika.h"
2023-10-30 23:55:30 +01:00
#include "pikapropwidgets.h"
2023-09-26 00:35:21 +02:00
#include "pikaui.h"
#include "pikaprocedureconfig-private.h"
#include "libpika-intl.h"
enum
{
PROP_0,
PROP_PROCEDURE,
PROP_CONFIG,
/* From here the overridden properties. */
PROP_TITLE,
N_PROPS
};
#define RESPONSE_RESET 1
struct _PikaProcedureDialogPrivate
{
PikaProcedure *procedure;
PikaProcedureConfig *config;
PikaProcedureConfig *initial_config;
2023-10-30 23:55:30 +01:00
GtkWidget *ok_button;
2023-09-26 00:35:21 +02:00
GtkWidget *reset_popover;
GtkWidget *load_settings_button;
GHashTable *widgets;
GHashTable *mnemonics;
GHashTable *core_mnemonics;
GtkSizeGroup *label_group;
GHashTable *sensitive_data;
};
typedef struct PikaProcedureDialogSensitiveData
{
gboolean sensitive;
GObject *config;
gchar *config_property;
gboolean config_invert;
} PikaProcedureDialogSensitiveData;
2023-10-30 23:55:30 +01:00
typedef struct PikaProcedureDialogSensitiveData2
{
PikaProcedureDialog *dialog;
gchar *widget_property;
PikaValueArray *values;
gboolean in_values;
} PikaProcedureDialogSensitiveData2;
2023-09-26 00:35:21 +02:00
static GObject * pika_procedure_dialog_constructor (GType type,
guint n_construct_properties,
GObjectConstructParam *construct_properties);
static void pika_procedure_dialog_constructed (GObject *object);
static void pika_procedure_dialog_dispose (GObject *object);
static void pika_procedure_dialog_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec);
static void pika_procedure_dialog_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec);
static void pika_procedure_dialog_real_fill_list (PikaProcedureDialog *dialog,
PikaProcedure *procedure,
PikaProcedureConfig *config,
GList *properties);
static void pika_procedure_dialog_reset_initial (GtkWidget *button,
PikaProcedureDialog *dialog);
static void pika_procedure_dialog_reset_factory (GtkWidget *button,
PikaProcedureDialog *dialog);
static void pika_procedure_dialog_load_defaults (GtkWidget *button,
PikaProcedureDialog *dialog);
static void pika_procedure_dialog_save_defaults (GtkWidget *button,
PikaProcedureDialog *dialog);
static gboolean pika_procedure_dialog_check_mnemonic (PikaProcedureDialog *dialog,
GtkWidget *widget,
const gchar *id,
const gchar *core_id);
static GtkWidget *
pika_procedure_dialog_fill_container_list (PikaProcedureDialog *dialog,
const gchar *container_id,
GtkContainer *container,
GList *properties);
2023-10-30 23:55:30 +01:00
static void pika_procedure_dialog_set_sensitive_if_in_cb (GObject *config,
GParamSpec *param_spec,
PikaProcedureDialogSensitiveData2 *data);
static void pika_procedure_dialog_sensitive_data_free (PikaProcedureDialogSensitiveData *data);
static void pika_procedure_dialog_sensitive_cb_data_free (PikaProcedureDialogSensitiveData2 *data);
2023-09-26 00:35:21 +02:00
G_DEFINE_TYPE_WITH_PRIVATE (PikaProcedureDialog, pika_procedure_dialog,
PIKA_TYPE_DIALOG)
#define parent_class pika_procedure_dialog_parent_class
static GParamSpec *props[N_PROPS] = { NULL, };
static void
pika_procedure_dialog_class_init (PikaProcedureDialogClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->constructor = pika_procedure_dialog_constructor;
object_class->constructed = pika_procedure_dialog_constructed;
object_class->dispose = pika_procedure_dialog_dispose;
object_class->get_property = pika_procedure_dialog_get_property;
object_class->set_property = pika_procedure_dialog_set_property;
klass->fill_list = pika_procedure_dialog_real_fill_list;
props[PROP_PROCEDURE] =
g_param_spec_object ("procedure",
"Procedure",
"The PikaProcedure this dialog is used with",
PIKA_TYPE_PROCEDURE,
PIKA_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY);
props[PROP_CONFIG] =
g_param_spec_object ("config",
"Config",
"The PikaProcedureConfig this dialog is editing",
PIKA_TYPE_PROCEDURE_CONFIG,
PIKA_PARAM_READWRITE |
G_PARAM_CONSTRUCT);
g_object_class_install_properties (object_class, N_PROPS - 1, props);
/**
* PikaProcedureDialog:title:
*
* Overrides the "title" property of #GtkWindow.
* When %NULL, the title is taken from the #PikaProcedure menu label.
*
* Since: 3.0
*/
g_object_class_override_property (object_class, PROP_TITLE, "title");
}
static void
pika_procedure_dialog_init (PikaProcedureDialog *dialog)
{
dialog->priv = pika_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) pika_procedure_dialog_sensitive_data_free);
}
static GObject *
pika_procedure_dialog_constructor (GType type,
guint n_construct_properties,
GObjectConstructParam *construct_properties)
{
gboolean use_header_bar = FALSE;
gboolean use_header_bar_edited = FALSE;
gboolean help_func_edited = FALSE;
2023-10-30 23:55:30 +01:00
if (gtk_settings_get_default () != NULL)
g_object_get (gtk_settings_get_default (),
"gtk-dialogs-use-header", &use_header_bar,
NULL);
else
g_warning ("%s: no default screen. Did you call pika_ui_init()?", G_STRFUNC);
2023-09-26 00:35:21 +02:00
for (guint i = 0; i < n_construct_properties; i++)
{
/* We need to override the default values of some properties and
* can't do it in _init() or _constructed() because it's too late
* for G_PARAM_CONSTRUCT_ONLY properties.
* Moreover we don't do it in _new() because we need this to work
* also in bindings sometimes using only constructors to
* initialize their object, hence we can't rely on our _new()
* function (relying on _new() functions doing more than the
* constructor is now discouraged by GTK/GLib developers).
*/
GObjectConstructParam property;
property = construct_properties[i];
if (! use_header_bar_edited &&
g_strcmp0 (g_param_spec_get_name (property.pspec),
"use-header-bar") == 0)
{
if (g_value_get_int (property.value) == -1)
g_value_set_int (property.value, (gint) use_header_bar);
use_header_bar_edited = TRUE;
}
else if (! help_func_edited &&
g_strcmp0 (g_param_spec_get_name (property.pspec),
"help-func") == 0)
{
if (g_value_get_pointer (property.value) == NULL)
g_value_set_pointer (property.value,
pika_standard_help_func);
help_func_edited = TRUE;
}
if (use_header_bar_edited && help_func_edited)
break;
}
return G_OBJECT_CLASS (parent_class)->constructor (type,
n_construct_properties,
construct_properties);
}
static void
pika_procedure_dialog_constructed (GObject *object)
{
PikaProcedureDialog *dialog;
PikaProcedure *procedure;
const gchar *help_id;
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 = PIKA_PROCEDURE_DIALOG (object);
procedure = dialog->priv->procedure;
role = g_strdup_printf ("pika-%s", pika_procedure_get_name (procedure));
help_id = pika_procedure_get_help_id (procedure);
g_object_set (object,
"role", role,
"help-id", help_id,
/* This may seem weird, but this is actually because we
* are overriding this property and if the title is NULL
* in particular, we create one out of the procedure's
* menu label. So we force the reset this way.
*/
"title", gtk_window_get_title (GTK_WINDOW (dialog)),
NULL);
g_free (role);
if (PIKA_IS_LOAD_PROCEDURE (procedure))
ok_label = _("_Open");
else if (PIKA_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 (PIKA_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);
pika_procedure_dialog_check_mnemonic (PIKA_PROCEDURE_DIALOG (dialog), button, NULL, "reset");
/* Cancel and OK buttons. */
button = pika_dialog_add_button (PIKA_DIALOG (dialog),
_("_Cancel"), GTK_RESPONSE_CANCEL);
pika_procedure_dialog_check_mnemonic (PIKA_PROCEDURE_DIALOG (dialog), button, NULL, "cancel");
button = pika_dialog_add_button (PIKA_DIALOG (dialog),
ok_label, GTK_RESPONSE_OK);
2023-10-30 23:55:30 +01:00
dialog->priv->ok_button = button;
2023-09-26 00:35:21 +02:00
pika_procedure_dialog_check_mnemonic (PIKA_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);
pika_dialog_set_alternative_button_order (GTK_DIALOG (dialog),
GTK_RESPONSE_OK,
RESPONSE_RESET,
GTK_RESPONSE_CANCEL,
-1);
pika_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"));
pika_procedure_dialog_check_mnemonic (PIKA_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 (pika_procedure_dialog_load_defaults),
dialog);
gtk_widget_set_sensitive (button,
pika_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"));
pika_procedure_dialog_check_mnemonic (PIKA_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 (pika_procedure_dialog_save_defaults),
dialog);
}
static void
pika_procedure_dialog_dispose (GObject *object)
{
PikaProcedureDialog *dialog = PIKA_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
pika_procedure_dialog_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
PikaProcedureDialog *dialog = PIKA_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 =
pika_config_duplicate (PIKA_CONFIG (dialog->priv->config));
break;
case PROP_TITLE:
{
GtkWidget *bogus = NULL;
const gchar *title;
title = g_value_get_string (value);
if (title == NULL)
{
/* Use the procedure menu label, but remove mnemonic
* underscore. Ugly yet 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)),
pika_procedure_get_menu_label (dialog->priv->procedure));
title = gtk_label_get_text (GTK_LABEL (bogus));
}
if (title != NULL)
gtk_window_set_title (GTK_WINDOW (dialog), title);
g_clear_object (&bogus);
}
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
pika_procedure_dialog_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
PikaProcedureDialog *dialog = PIKA_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;
case PROP_TITLE:
g_value_set_string (value,
gtk_window_get_title (GTK_WINDOW (dialog)));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
pika_procedure_dialog_real_fill_list (PikaProcedureDialog *dialog,
PikaProcedure *procedure,
PikaProcedureConfig *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 = pika_procedure_dialog_get_widget (dialog, iter->data, G_TYPE_NONE);
if (widget)
{
gtk_box_pack_start (GTK_BOX (content_area), widget, TRUE, TRUE, 0);
gtk_widget_show (widget);
}
}
}
/**
* pika_procedure_dialog_new:
* @procedure: the associated #PikaProcedure.
* @config: a #PikaProcedureConfig 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
* pika_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.
*
* As for all #GtkWindow, the returned #PikaProcedureDialog object is
* owned by GTK and its initial reference is stored in an internal list
* of top-level windows. To delete the dialog, call
* gtk_widget_destroy().
*
* Returns: (transfer none): the newly created #PikaProcedureDialog.
*/
GtkWidget *
pika_procedure_dialog_new (PikaProcedure *procedure,
PikaProcedureConfig *config,
const gchar *title)
{
g_return_val_if_fail (PIKA_IS_PROCEDURE (procedure), NULL);
g_return_val_if_fail (PIKA_IS_PROCEDURE_CONFIG (config), NULL);
g_return_val_if_fail (pika_procedure_config_get_procedure (config) ==
procedure, NULL);
g_return_val_if_fail (title != NULL || pika_procedure_get_menu_label (procedure), NULL);
return g_object_new (PIKA_TYPE_PROCEDURE_DIALOG,
"procedure", procedure,
"config", config,
"title", title,
NULL);
}
2023-10-30 23:55:30 +01:00
/**
* pika_procedure_dialog_set_ok_label:
* @dialog: the associated #PikaProcedureDialog.
* @ok_label: a label to replace the OK button's text.
*
* Changes the "OK" button's label of @dialog to @ok_label.
*/
void
pika_procedure_dialog_set_ok_label (PikaProcedureDialog *dialog,
const gchar *ok_label)
{
if (ok_label == NULL)
{
PikaProcedure *procedure = dialog->priv->procedure;
if (PIKA_IS_LOAD_PROCEDURE (procedure))
ok_label = _("_Open");
else if (PIKA_IS_SAVE_PROCEDURE (procedure))
ok_label = _("_Export");
else
ok_label = _("_OK");
}
gtk_button_set_label (GTK_BUTTON (dialog->priv->ok_button), ok_label);
pika_procedure_dialog_check_mnemonic (PIKA_PROCEDURE_DIALOG (dialog),
dialog->priv->ok_button, NULL, "ok");
}
2023-09-26 00:35:21 +02:00
/**
* pika_procedure_dialog_get_widget:
* @dialog: the associated #PikaProcedureDialog.
* @property: name of the property to build a widget for. It must be
* a property of the #PikaProcedure @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)
* * %GTK_TYPE_SWITCH
* - %G_TYPE_PARAM_INT or %G_TYPE_PARAM_DOUBLE:
* * %PIKA_TYPE_LABEL_SPIN (default): a spin button with a label.
* * %PIKA_TYPE_SCALE_ENTRY: a scale entry with label.
* * %PIKA_TYPE_SPIN_SCALE: a spin scale with label embedded.
* * %PIKA_TYPE_SPIN_BUTTON: a spin button with no label.
* - %G_TYPE_PARAM_STRING:
* * %PIKA_TYPE_LABEL_ENTRY (default): an entry with a label.
* * %GTK_TYPE_ENTRY: an entry with no label.
* * %GTK_TYPE_TEXT_VIEW: a text view with no label.
* - %PIKA_TYPE_PARAM_RGB:
* * %PIKA_TYPE_LABEL_COLOR (default): a color button with a label.
* Please use pika_procedure_dialog_get_color_widget() for a
* non-editable color area with a label.
* * %PIKA_TYPE_COLOR_BUTTON: a color button with no label.
* * %PIKA_TYPE_COLOR_AREA: a color area with no label.
* - %G_TYPE_PARAM_FILE:
* * %GTK_FILE_CHOOSER_BUTTON (default): generic file chooser button
* in %GTK_FILE_CHOOSER_ACTION_OPEN mode. Please use
* pika_procedure_dialog_get_file_chooser() to create buttons in
* other modes.
*
* 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 *
pika_procedure_dialog_get_widget (PikaProcedureDialog *dialog,
const gchar *property,
GType widget_type)
{
GtkWidget *widget = NULL;
GtkWidget *label = NULL;
PikaProcedureDialogSensitiveData *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 = pika_prop_check_button_new (G_OBJECT (dialog->priv->config),
property,
g_param_spec_get_nick (pspec));
else if (widget_type == GTK_TYPE_SWITCH)
widget = pika_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;
}
pika_range_estimate_settings (minimum, maximum, &step, &page, &digits);
if (widget_type == G_TYPE_NONE || widget_type == PIKA_TYPE_LABEL_SPIN)
{
widget = pika_prop_label_spin_new (G_OBJECT (dialog->priv->config),
property, digits);
}
else if (widget_type == PIKA_TYPE_SCALE_ENTRY)
{
widget = pika_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 == PIKA_TYPE_SPIN_SCALE)
{
widget = pika_prop_spin_scale_new (G_OBJECT (dialog->priv->config),
property, step, page, digits);
}
else if (widget_type == PIKA_TYPE_SPIN_BUTTON)
{
/* Just some spin button without label. */
widget = pika_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 == PIKA_TYPE_LABEL_ENTRY)
{
widget = pika_prop_label_entry_new (G_OBJECT (dialog->priv->config),
property, -1);
}
else if (widget_type == GTK_TYPE_TEXT_VIEW)
{
GtkTextBuffer *buffer;
/* Text view with no label. */
buffer = pika_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)
{
/* Entry with no label. */
widget = pika_prop_entry_new (G_OBJECT (dialog->priv->config),
property, -1);
}
}
else if (G_PARAM_SPEC_TYPE (pspec) == PIKA_TYPE_PARAM_RGB)
{
if (widget_type == G_TYPE_NONE || widget_type == PIKA_TYPE_LABEL_COLOR)
{
widget = pika_prop_label_color_new (G_OBJECT (dialog->priv->config),
property, TRUE);
}
else if (widget_type == PIKA_TYPE_COLOR_BUTTON)
{
widget = pika_prop_color_select_new (G_OBJECT (dialog->priv->config),
property, 20, 20,
PIKA_COLOR_AREA_SMALL_CHECKS);
gtk_widget_set_vexpand (widget, FALSE);
gtk_widget_set_hexpand (widget, FALSE);
}
else if (widget_type == PIKA_TYPE_COLOR_AREA)
{
widget = pika_prop_color_area_new (G_OBJECT (dialog->priv->config),
property, 20, 20,
PIKA_COLOR_AREA_SMALL_CHECKS);
gtk_widget_set_vexpand (widget, FALSE);
gtk_widget_set_hexpand (widget, FALSE);
}
}
else if (G_IS_PARAM_SPEC_OBJECT (pspec) && pspec->value_type == G_TYPE_FILE)
{
widget = pika_prop_file_chooser_button_new (G_OBJECT (dialog->priv->config),
property, NULL,
GTK_FILE_CHOOSER_ACTION_OPEN);
}
2023-10-30 23:55:30 +01:00
else if (G_PARAM_SPEC_TYPE (pspec) == PIKA_TYPE_PARAM_CHOICE)
{
widget = pika_prop_choice_combo_box_new (G_OBJECT (dialog->priv->config), property);
gtk_widget_set_vexpand (widget, FALSE);
gtk_widget_set_hexpand (widget, TRUE);
widget = pika_label_string_widget_new (g_param_spec_get_nick (pspec), widget);
}
2023-09-26 00:35:21 +02:00
/* PikaResource subclasses */
/* FUTURE: title the chooser more specifically, with a prefix that is the nick of the property. */
else if (G_IS_PARAM_SPEC_OBJECT (pspec) && pspec->value_type == PIKA_TYPE_BRUSH)
{
2023-10-30 23:55:30 +01:00
widget = pika_prop_brush_chooser_new (G_OBJECT (dialog->priv->config), property, _("Brush Chooser"));
2023-09-26 00:35:21 +02:00
}
else if (G_IS_PARAM_SPEC_OBJECT (pspec) && pspec->value_type == PIKA_TYPE_FONT)
{
2023-10-30 23:55:30 +01:00
widget = pika_prop_font_chooser_new (G_OBJECT (dialog->priv->config), property, _("Font Chooser"));
2023-09-26 00:35:21 +02:00
}
else if (G_IS_PARAM_SPEC_OBJECT (pspec) && pspec->value_type == PIKA_TYPE_GRADIENT)
{
2023-10-30 23:55:30 +01:00
widget = pika_prop_gradient_chooser_new (G_OBJECT (dialog->priv->config), property, _("Gradient Chooser"));
2023-09-26 00:35:21 +02:00
}
else if (G_IS_PARAM_SPEC_OBJECT (pspec) && pspec->value_type == PIKA_TYPE_PALETTE)
{
2023-10-30 23:55:30 +01:00
widget = pika_prop_palette_chooser_new (G_OBJECT (dialog->priv->config), property, _("Palette Chooser"));
2023-09-26 00:35:21 +02:00
}
else if (G_IS_PARAM_SPEC_OBJECT (pspec) && pspec->value_type == PIKA_TYPE_PATTERN)
{
2023-10-30 23:55:30 +01:00
widget = pika_prop_pattern_chooser_new (G_OBJECT (dialog->priv->config), property, _("Pattern Chooser"));
}
else if (G_IS_PARAM_SPEC_OBJECT (pspec) && (pspec->value_type == PIKA_TYPE_DRAWABLE ||
pspec->value_type == PIKA_TYPE_LAYER ||
pspec->value_type == PIKA_TYPE_CHANNEL))
{
widget = pika_prop_drawable_chooser_new (G_OBJECT (dialog->priv->config), property, NULL);
2023-09-26 00:35:21 +02:00
}
else if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_ENUM)
{
PikaIntStore *store;
store = (PikaIntStore *) pika_enum_store_new (G_PARAM_SPEC_VALUE_TYPE (pspec));
widget = pika_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 = pika_label_int_widget_new (g_param_spec_get_nick (pspec),
widget);
}
else
{
g_warning ("%s: parameter %s has non supported type %s for value type %s",
G_STRFUNC, property,
G_PARAM_SPEC_TYPE_NAME (pspec),
g_type_name (G_PARAM_SPEC_VALUE_TYPE (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);
2023-10-30 23:55:30 +01:00
2023-09-26 00:35:21 +02:00
if (tooltip)
pika_help_set_help_data (widget, tooltip, NULL);
2023-10-30 23:55:30 +01:00
if (label == NULL)
2023-09-26 00:35:21 +02:00
{
2023-10-30 23:55:30 +01:00
if (PIKA_IS_LABELED (widget))
2023-09-26 00:35:21 +02:00
label = pika_labeled_get_label (PIKA_LABELED (widget));
2023-10-30 23:55:30 +01:00
else if (PIKA_IS_RESOURCE_CHOOSER (widget))
label = pika_resource_chooser_get_label (PIKA_RESOURCE_CHOOSER (widget));
else if (PIKA_IS_DRAWABLE_CHOOSER (widget))
label = pika_drawable_chooser_get_label (PIKA_DRAWABLE_CHOOSER (widget));
2023-09-26 00:35:21 +02:00
}
2023-10-30 23:55:30 +01:00
if (label != NULL)
gtk_size_group_add_widget (dialog->priv->label_group, label);
2023-09-26 00:35:21 +02: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);
}
pika_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;
}
/**
* pika_procedure_dialog_get_color_widget:
* @dialog: the associated #PikaProcedureDialog.
* @property: name of the #PikaRGB property to build a widget for. It
* must be a property of the #PikaProcedure @dialog has been
* created for.
* @editable: whether the color can be edited or is only for display.
* @type: the #PikaColorAreaType.
*
* Creates a new widget for @property which must necessarily be a
* #PikaRGB property.
* This must be used instead of pika_procedure_dialog_get_widget() when
* you want a #PikaLabelColor which is not customizable for an RGB
* property, or when to set a specific @type.
*
* If a widget has already been created for this procedure, it will be
* returned instead (whatever its actual widget type).
*
* Returns: (transfer none): a #PikaLabelColor representing @property.
* The object belongs to @dialog and must not
* be freed.
*/
GtkWidget *
pika_procedure_dialog_get_color_widget (PikaProcedureDialog *dialog,
const gchar *property,
gboolean editable,
PikaColorAreaType 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) == PIKA_TYPE_PARAM_RGB)
{
widget = pika_prop_label_color_new (G_OBJECT (dialog->priv->config),
property, editable);
gtk_widget_set_vexpand (widget, FALSE);
gtk_widget_set_hexpand (widget, FALSE);
}
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 (PIKA_IS_LABELED (widget))
{
GtkWidget *label = pika_labeled_get_label (PIKA_LABELED (widget));
const gchar *tooltip = g_param_spec_get_blurb (pspec);
gtk_size_group_add_widget (dialog->priv->label_group, label);
if (tooltip)
pika_help_set_help_data (label, tooltip, NULL);
}
pika_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;
}
/**
* pika_procedure_dialog_get_int_combo:
* @dialog: the associated #PikaProcedureDialog.
* @property: name of the int property to build a combo for. It must be
* a property of the #PikaProcedure @dialog has been created
* for.
* @store: (transfer full): the #PikaIntStore which will be used.
*
* Creates a new #PikaLabelIntWidget for @property which must
* necessarily be an integer or boolean property.
* This must be used instead of pika_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 *
pika_procedure_dialog_get_int_combo (PikaProcedureDialog *dialog,
const gchar *property,
PikaIntStore *store)
{
GtkWidget *widget = NULL;
GParamSpec *pspec;
g_return_val_if_fail (PIKA_IS_PROCEDURE_DIALOG (dialog), NULL);
g_return_val_if_fail (property != NULL, NULL);
g_return_val_if_fail (PIKA_IS_INT_STORE (store), NULL);
/* First check if it already exists. */
widget = g_hash_table_lookup (dialog->priv->widgets, property);
if (widget)
{
g_object_unref (store);
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);
g_object_unref (store);
return NULL;
}
if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_BOOLEAN ||
G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_INT)
{
widget = pika_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 = pika_label_int_widget_new (g_param_spec_get_nick (pspec),
widget);
}
g_object_unref (store);
if (! widget)
{
g_warning ("%s: parameter '%s' of type %s not suitable as PikaIntComboBox",
G_STRFUNC, property, G_PARAM_SPEC_TYPE_NAME (pspec));
return NULL;
}
else
{
const gchar *tooltip = g_param_spec_get_blurb (pspec);
if (tooltip)
pika_help_set_help_data (widget, tooltip, NULL);
if (PIKA_IS_LABELED (widget))
{
GtkWidget *label = pika_labeled_get_label (PIKA_LABELED (widget));
gtk_size_group_add_widget (dialog->priv->label_group, label);
}
}
pika_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;
}
/**
* pika_procedure_dialog_get_int_radio:
* @dialog: the associated #PikaProcedureDialog.
* @property: name of the int property to build radio buttons for. It
* must be a property of the #PikaProcedure @dialog has been
* created for.
* @store: (transfer full): the #PikaIntStore which will be used.
*
* Creates a new #PikaLabelIntRadioFrame for @property which must
* necessarily be an integer, enum or boolean property.
* This must be used instead of pika_procedure_dialog_get_widget() when
* you want to create a group of %GtkRadioButton-s 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 *
pika_procedure_dialog_get_int_radio (PikaProcedureDialog *dialog,
const gchar *property,
PikaIntStore *store)
{
GtkWidget *widget = NULL;
GParamSpec *pspec;
g_return_val_if_fail (PIKA_IS_PROCEDURE_DIALOG (dialog), NULL);
g_return_val_if_fail (property != NULL, NULL);
g_return_val_if_fail (PIKA_IS_INT_STORE (store), NULL);
/* First check if it already exists. */
widget = g_hash_table_lookup (dialog->priv->widgets, property);
if (widget)
{
g_object_unref (store);
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);
g_object_unref (store);
return NULL;
}
if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_BOOLEAN ||
G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_INT)
{
widget = pika_prop_int_radio_frame_new (G_OBJECT (dialog->priv->config),
property, NULL, store);
gtk_widget_set_vexpand (widget, FALSE);
gtk_widget_set_hexpand (widget, TRUE);
}
g_object_unref (store);
if (! widget)
{
g_warning ("%s: parameter '%s' of type %s not suitable as PikaIntRadioFrame",
G_STRFUNC, property, G_PARAM_SPEC_TYPE_NAME (pspec));
return NULL;
}
pika_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;
}
/**
* pika_procedure_dialog_get_spin_scale:
* @dialog: the associated #PikaProcedureDialog.
* @property: name of the int or double property to build a
* #PikaSpinScale for. It must be a property of the
* #PikaProcedure @dialog has been created for.
* @factor: a display factor for the range shown by the widget.
* It must be set to 1.0 for integer properties.
*
* Creates a new #PikaSpinScale for @property which must necessarily be
* an integer or double property.
* This can be used instead of pika_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 *
pika_procedure_dialog_get_spin_scale (PikaProcedureDialog *dialog,
const gchar *property,
gdouble factor)
{
GtkWidget *widget = NULL;
GParamSpec *pspec;
gdouble minimum;
gdouble maximum;
gdouble step = 0.0;
gdouble page = 0.0;
gint digits = 0;
g_return_val_if_fail (PIKA_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_DOUBLE ||
(G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_INT &&
factor == 1.0), NULL);
/* First check if it already exists. */
widget = g_hash_table_lookup (dialog->priv->widgets, property);
if (widget)
return widget;
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;
}
pika_range_estimate_settings (minimum * factor, maximum * factor, &step, &page, &digits);
widget = pika_prop_spin_scale_new (G_OBJECT (dialog->priv->config),
property, step, page, digits);
if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_DOUBLE)
pika_prop_widget_set_factor (widget, factor, step, page, digits);
pika_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;
}
/**
* pika_procedure_dialog_get_scale_entry:
* @dialog: the associated #PikaProcedureDialog.
* @property: name of the int property to build a combo for. It must be
* a property of the #PikaProcedure @dialog has been created
* for.
* @factor: a display factor for the range shown by the widget.
*
* Creates a new #PikaScaleEntry for @property which must necessarily be
* an integer or double property.
* This can be used instead of pika_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 *
pika_procedure_dialog_get_scale_entry (PikaProcedureDialog *dialog,
const gchar *property,
gdouble factor)
{
GtkWidget *widget = NULL;
GParamSpec *pspec;
g_return_val_if_fail (PIKA_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 = pika_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,
pika_labeled_get_label (PIKA_LABELED (widget)));
pika_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;
}
/**
* pika_procedure_dialog_get_size_entry:
* @dialog: the associated #PikaProcedureDialog.
* @property: name of the int property to build an entry for.
* It must be a property of the #PikaProcedure @dialog
* has been created for.
* @property_is_pixel: when %TRUE, the property value is in pixels,
* and in the selected unit otherwise.
* @unit_property: name of unit property.
* @unit_format: a printf-like unit-format string used for unit
* labels.
* @update_policy: how the automatic pixel <-> real-world-unit
* calculations should be done.
* @resolution: the resolution (in dpi) for the field.
*
* Creates a new #PikaSizeEntry for @property which must necessarily be
* an integer or double property. The associated @unit_property must be
* a PikaUnit or 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 *
pika_procedure_dialog_get_size_entry (PikaProcedureDialog *dialog,
const gchar *property,
gboolean property_is_pixel,
const gchar *unit_property,
const gchar *unit_format,
PikaSizeEntryUpdatePolicy update_policy,
gdouble resolution)
{
GtkWidget *widget = NULL;
GtkWidget *label = NULL;
GtkSizeGroup *group;
GParamSpec *pspec;
GParamSpec *pspec_unit;
g_return_val_if_fail (PIKA_IS_PROCEDURE_DIALOG (dialog), NULL);
g_return_val_if_fail (property != NULL, NULL);
g_return_val_if_fail (unit_property != NULL, NULL);
pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (dialog->priv->config),
property);
pspec_unit = g_object_class_find_property (G_OBJECT_GET_CLASS (dialog->priv->config),
unit_property);
if (! pspec)
{
g_warning ("%s: parameter %s does not exist.",
G_STRFUNC, property);
return NULL;
}
if (! pspec_unit)
{
g_warning ("%s: unit parameter %s does not exist.",
G_STRFUNC, unit_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);
g_return_val_if_fail (G_PARAM_SPEC_TYPE (pspec_unit) == PIKA_TYPE_PARAM_UNIT, NULL);
/* First check if it already exists. */
widget = g_hash_table_lookup (dialog->priv->widgets, property);
if (widget)
return widget;
widget = pika_prop_size_entry_new (G_OBJECT (dialog->priv->config), property,
property_is_pixel, unit_property,
unit_format, update_policy, resolution);
/* Add label */
label = pika_size_entry_attach_label (PIKA_SIZE_ENTRY (widget),
g_param_spec_get_nick (pspec), 1, 0, 0.0);
gtk_widget_set_margin_end (label, 6);
group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
gtk_size_group_add_widget (group, label);
g_object_unref (group);
g_hash_table_insert (dialog->priv->widgets, g_strdup (property), widget);
if (g_object_is_floating (widget))
g_object_ref_sink (widget);
return widget;
}
/**
* pika_procedure_dialog_get_label:
2023-10-30 23:55:30 +01:00
* @dialog: the #PikaProcedureDialog.
* @label_id: the label for the #GtkLabel.
* @text: the text for the label.
* @is_markup: whether @text is formatted with Pango markup.
* @with_mnemonic: whether @text contains a mnemonic character.
2023-09-26 00:35:21 +02:00
*
* Creates a new #GtkLabel with @text. It can be useful for packing
* textual information in between property settings.
*
2023-10-30 23:55:30 +01:00
* If @label_id is an existing string property of the #PikaProcedureConfig
* associated to @dialog, then it will sync to the property value. In this case,
* @text should be %NULL.
*
* If @label_id is a unique ID which is neither the name of a property of the
* #PikaProcedureConfig associated to @dialog, nor is it the ID of any
* previously created label or container, it will be initialized to @text. This
* ID can later be used together with property names to be packed in other
2023-09-26 00:35:21 +02:00
* containers or inside @dialog itself.
*
* Returns: (transfer none): the #GtkWidget representing @label_id. The
* object belongs to @dialog and must not be
* freed.
*/
GtkWidget *
pika_procedure_dialog_get_label (PikaProcedureDialog *dialog,
const gchar *label_id,
2023-10-30 23:55:30 +01:00
const gchar *text,
gboolean is_markup,
gboolean with_mnemonic)
2023-09-26 00:35:21 +02:00
{
2023-10-30 23:55:30 +01:00
GtkWidget *label;
GParamSpec *pspec;
2023-09-26 00:35:21 +02:00
g_return_val_if_fail (label_id != NULL, NULL);
2023-10-30 23:55:30 +01:00
pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (dialog->priv->config),
label_id);
if (pspec != NULL && G_PARAM_SPEC_TYPE (pspec) != G_TYPE_PARAM_STRING)
2023-09-26 00:35:21 +02:00
{
2023-10-30 23:55:30 +01:00
g_warning ("%s: label identifier '%s' must either not already exist or "
"be an existing string property.",
2023-09-26 00:35:21 +02:00
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;
}
2023-10-30 23:55:30 +01:00
label = gtk_label_new (NULL);
g_object_set (label,
"use-markup", is_markup,
"use-underline", with_mnemonic,
NULL);
if (pspec != NULL)
g_object_bind_property (dialog->priv->config, label_id,
label, "label",
G_BINDING_BIDIRECTIONAL | G_BINDING_SYNC_CREATE);
else
g_object_set (label, "label", text, NULL);
2023-09-26 00:35:21 +02:00
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;
}
/**
* pika_procedure_dialog_get_file_chooser:
* @dialog: the associated #PikaProcedureDialog.
* @property: name of the %PikaParamConfigPath or %GParamObject of value
* type %GFile property to build a #GtkFileChooserButton for.
* It must be a property of the #PikaProcedure @dialog has
* been created for.
* @action: The open mode for the widget.
*
* Creates a new %GtkFileChooserButton for @property which must
* necessarily be a config path or %GFile property.
* This can be used instead of pika_procedure_dialog_get_widget() in
* particular if you want to create a button in non-open modes (i.e. to
* save files, and select or create folders).
*
* 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 *
pika_procedure_dialog_get_file_chooser (PikaProcedureDialog *dialog,
const gchar *property,
GtkFileChooserAction action)
{
GtkWidget *widget = NULL;
GParamSpec *pspec;
g_return_val_if_fail (PIKA_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 (PIKA_IS_PARAM_SPEC_CONFIG_PATH (pspec) ||
(G_IS_PARAM_SPEC_OBJECT (pspec) && pspec->value_type != G_TYPE_FILE),
NULL);
/* First check if it already exists. */
widget = g_hash_table_lookup (dialog->priv->widgets, property);
if (widget)
return widget;
widget = pika_prop_file_chooser_button_new (G_OBJECT (dialog->priv->config),
property, NULL, action);
/* TODO: make is a file chooser with label. */
/*gtk_size_group_add_widget (dialog->priv->label_group,
pika_labeled_get_label (PIKA_LABELED (widget)));
pika_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;
}
/**
* pika_procedure_dialog_fill:
* @dialog: the #PikaProcedureDialog.
* @...: 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 #PikaProcedure, in the defined
* order:
* |[<!-- language="C" -->
* pika_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" -->
* pika_procedure_dialog_fill (dialog, "property-1", "property-2", NULL);
* ]|
*
* Note: you do not have to call pika_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
pika_procedure_dialog_fill (PikaProcedureDialog *dialog,
...)
{
const gchar *prop_name;
GList *list = NULL;
va_list va_args;
g_return_if_fail (PIKA_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);
pika_procedure_dialog_fill_list (dialog, list);
if (list)
g_list_free (list);
}
/**
* pika_procedure_dialog_fill_list: (rename-to pika_procedure_dialog_fill)
* @dialog: the #PikaProcedureDialog.
* @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 #PikaProcedure, in the defined
* order:
* |[<!-- language="C" -->
* pika_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" -->
* pika_procedure_dialog_fill (dialog, "property-1", "property-2", NULL);
* ]|
*
* Note: you do not have to call pika_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
pika_procedure_dialog_fill_list (PikaProcedureDialog *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 == PIKA_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;
2023-10-30 23:55:30 +01:00
g_free (pspecs);
2023-09-26 00:35:21 +02:00
}
PIKA_PROCEDURE_DIALOG_GET_CLASS (dialog)->fill_list (dialog,
dialog->priv->procedure,
dialog->priv->config,
properties);
if (free_properties)
g_list_free (properties);
}
/**
* pika_procedure_dialog_fill_box:
* @dialog: the #PikaProcedureDialog.
* @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
* #PikaProcedure, in the defined order. This is similar of how
* pika_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 #PikaProcedureConfig 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 *
pika_procedure_dialog_fill_box (PikaProcedureDialog *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 (PIKA_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 = pika_procedure_dialog_fill_box_list (dialog, container_id, list);
if (list)
g_list_free (list);
return box;
}
/**
* pika_procedure_dialog_fill_box_list: (rename-to pika_procedure_dialog_fill_box)
* @dialog: the #PikaProcedureDialog.
* @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
* #PikaProcedure, in the defined order. This is similar of how
* pika_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 #PikaProcedureConfig 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 *
pika_procedure_dialog_fill_box_list (PikaProcedureDialog *dialog,
const gchar *container_id,
GList *properties)
{
g_return_val_if_fail (container_id != NULL, NULL);
return pika_procedure_dialog_fill_container_list (dialog, container_id,
GTK_CONTAINER (gtk_box_new (GTK_ORIENTATION_VERTICAL, 2)),
properties);
}
/**
* pika_procedure_dialog_fill_flowbox:
* @dialog: the #PikaProcedureDialog.
* @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
* #PikaProcedure, in the defined order. This is similar of how
* pika_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 #PikaProcedureConfig 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 *
pika_procedure_dialog_fill_flowbox (PikaProcedureDialog *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 (PIKA_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 = pika_procedure_dialog_fill_flowbox_list (dialog, container_id, list);
if (list)
g_list_free (list);
return flowbox;
}
/**
* pika_procedure_dialog_fill_flowbox_list: (rename-to pika_procedure_dialog_fill_flowbox)
* @dialog: the #PikaProcedureDialog.
* @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
* #PikaProcedure, in the defined order. This is similar of how
* pika_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 #PikaProcedureConfig 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 *
pika_procedure_dialog_fill_flowbox_list (PikaProcedureDialog *dialog,
const gchar *container_id,
GList *properties)
{
g_return_val_if_fail (container_id != NULL, NULL);
return pika_procedure_dialog_fill_container_list (dialog, container_id,
GTK_CONTAINER (gtk_flow_box_new ()),
properties);
}
/**
* pika_procedure_dialog_fill_frame:
* @dialog: the #PikaProcedureDialog.
* @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 #PikaProcedureConfig 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 *
pika_procedure_dialog_fill_frame (PikaProcedureDialog *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 = pika_frame_new (NULL);
if (contents_id)
{
contents = pika_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;
}
gtk_container_add (GTK_CONTAINER (frame), contents);
gtk_widget_show (contents);
}
if (title_id)
{
title = pika_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;
}
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;
}
/**
* pika_procedure_dialog_fill_expander:
* @dialog: the #PikaProcedureDialog.
* @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 #PikaProcedureConfig 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 *
pika_procedure_dialog_fill_expander (PikaProcedureDialog *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 = pika_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;
}
gtk_container_add (GTK_CONTAINER (expander), contents);
gtk_widget_show (contents);
}
if (title_id)
{
title = pika_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;
}
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;
}
/**
* pika_procedure_dialog_fill_scrolled_window:
* @dialog: the #PikaProcedureDialog.
* @container_id: a container identifier.
* @contents_id: The identifier for the contents.
*
* Creates and populates a new #GtkScrolledWindow with a widget corresponding
* to the declared content id.
*
* The @container_id must be a unique ID which is neither the name of a
* property of the #PikaProcedureConfig 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 #GtkScrolledWindow representing @contents_id.
* The object belongs to @dialog and must not be
* freed.
*/
GtkWidget *
pika_procedure_dialog_fill_scrolled_window (PikaProcedureDialog *dialog,
const gchar *container_id,
const gchar *contents_id)
{
GtkWidget *scrolled_window;
GList *single_list = NULL;
g_return_val_if_fail (PIKA_IS_PROCEDURE_DIALOG (dialog), NULL);
g_return_val_if_fail (container_id != NULL, NULL);
/* GtkScrolledWindow can only have one child */
g_return_val_if_fail (contents_id != NULL, NULL);
single_list = g_list_prepend (single_list, (gpointer) contents_id);
scrolled_window =
pika_procedure_dialog_fill_container_list (dialog, container_id,
GTK_CONTAINER (gtk_scrolled_window_new (NULL, NULL)),
single_list);
if (single_list)
g_list_free (single_list);
return scrolled_window;
}
2023-10-30 23:55:30 +01:00
/**
* pika_procedure_dialog_fill_notebook:
* @dialog: the #PikaProcedureDialog.
* @container_id: a container identifier.
* @label_id: the first page's label.
* @page_id: the first page's contents.
* @...: a %NULL-terminated list of other property names.
*
* Creates and populates a new #GtkNotebook with widgets corresponding to every
* listed properties.
* This is similar of how pika_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 #PikaProcedureConfig 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 #GtkNotebook representing @property. The
* object belongs to @dialog and must not be
* freed.
*/
GtkWidget *
pika_procedure_dialog_fill_notebook (PikaProcedureDialog *dialog,
const gchar *container_id,
const gchar *label_id,
const gchar *page_id,
...)
{
GtkWidget *notebook;
GList *label_list = NULL;
GList *page_list = NULL;
va_list va_args;
g_return_val_if_fail (PIKA_IS_PROCEDURE_DIALOG (dialog), NULL);
g_return_val_if_fail (container_id != NULL, NULL);
g_return_val_if_fail (label_id != NULL && page_id != NULL, NULL);
va_start (va_args, page_id);
do
{
label_list = g_list_prepend (label_list, (gpointer) label_id);
page_list = g_list_prepend (page_list, (gpointer) page_id);
label_id = va_arg (va_args, const gchar *);
page_id = va_arg (va_args, const gchar *);
}
while (label_id != NULL && page_id != NULL);
va_end (va_args);
label_list = g_list_reverse (label_list);
page_list = g_list_reverse (page_list);
notebook = pika_procedure_dialog_fill_notebook_list (dialog, container_id, label_list, page_list);
g_list_free (label_list);
g_list_free (page_list);
return notebook;
}
/**
* pika_procedure_dialog_fill_notebook_list: (rename-to pika_procedure_dialog_fill_notebook)
* @dialog: the #PikaProcedureDialog.
* @container_id: a container identifier.
* @label_list: (not nullable) (element-type gchar*): the list of label IDs.
* @page_list: (not nullable) (element-type gchar*): the list of page IDs.
*
* Creates and populates a new #GtkNotebook with widgets corresponding to every
* listed properties.
* This is similar of how pika_procedure_dialog_fill_list() 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 #PikaProcedureConfig 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 #GtkNotebook representing @property. The
* object belongs to @dialog and must not be
* freed.
*/
GtkWidget *
pika_procedure_dialog_fill_notebook_list (PikaProcedureDialog *dialog,
const gchar *container_id,
GList *label_list,
GList *page_list)
{
GtkWidget *notebook;
GList *iter_label = label_list;
GList *iter_page = page_list;
g_return_val_if_fail (container_id != NULL, NULL);
g_return_val_if_fail (g_list_length (label_list) > 0 &&
g_list_length (label_list) == g_list_length (page_list), NULL);
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);
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);
return g_hash_table_lookup (dialog->priv->widgets, container_id);
}
notebook = gtk_notebook_new ();
g_object_ref_sink (notebook);
for (; iter_label; iter_label = iter_label->next, iter_page = iter_page->next)
{
GtkWidget *label;
GtkWidget *page;
label = pika_procedure_dialog_get_widget (dialog, iter_label->data, G_TYPE_NONE);
page = pika_procedure_dialog_get_widget (dialog, iter_page->data, G_TYPE_NONE);
if (label != NULL && page != NULL)
{
gtk_notebook_append_page (GTK_NOTEBOOK (notebook), page, label);
gtk_widget_show (label);
gtk_widget_show (page);
}
}
g_hash_table_insert (dialog->priv->widgets, g_strdup (container_id), notebook);
return notebook;
}
/**
* pika_procedure_dialog_fill_paned:
* @dialog: the #PikaProcedureDialog.
* @container_id: a container identifier.
* @child1_id: (nullable): the first child's ID.
* @child2_id: (nullable): the second child's ID.
*
* Creates and populates a new #GtkPaned containing widgets corresponding to
* @child1_id and @child2_id.
* This is similar of how pika_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 #PikaProcedureConfig 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 #GtkPaned representing @property. The
* object belongs to @dialog and must not be
* freed.
*/
GtkWidget *
pika_procedure_dialog_fill_paned (PikaProcedureDialog *dialog,
const gchar *container_id,
GtkOrientation orientation,
const gchar *child1_id,
const gchar *child2_id)
{
GtkWidget *paned;
GtkWidget *child1;
GtkWidget *child2;
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: container identifier '%s' cannot be an existing property name.",
G_STRFUNC, container_id);
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);
return g_hash_table_lookup (dialog->priv->widgets, container_id);
}
paned = gtk_paned_new (orientation);
g_object_ref_sink (paned);
if (child1_id != NULL)
{
child1 = pika_procedure_dialog_get_widget (dialog, child1_id, G_TYPE_NONE);
gtk_paned_pack1 (GTK_PANED (paned), child1, TRUE, FALSE);
gtk_widget_show (child1);
}
if (child2_id != NULL)
{
child2 = pika_procedure_dialog_get_widget (dialog, child2_id, G_TYPE_NONE);
gtk_paned_pack2 (GTK_PANED (paned), child2, TRUE, FALSE);
gtk_widget_show (child2);
}
g_hash_table_insert (dialog->priv->widgets, g_strdup (container_id), paned);
return paned;
}
2023-09-26 00:35:21 +02:00
/**
* pika_procedure_dialog_set_sensitive:
* @dialog: the #PikaProcedureDialog.
* @property: name of a property of the #PikaProcedure @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
pika_procedure_dialog_set_sensitive (PikaProcedureDialog *dialog,
const gchar *property,
gboolean sensitive,
GObject *config,
const gchar *config_property,
gboolean config_invert)
{
GtkWidget *widget = NULL;
GParamSpec *pspec;
g_return_if_fail (PIKA_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 PikaProcedure.",
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. */
PikaProcedureDialogSensitiveData *data;
data = g_slice_new0 (PikaProcedureDialogSensitiveData);
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);
}
}
2023-10-30 23:55:30 +01:00
/**
* pika_procedure_dialog_set_sensitive_if_in:
* @dialog: the #PikaProcedureDialog.
* @property: name of a property of the #PikaProcedure @dialog
* has been created for.
* @config: (nullable): an optional config object (if %NULL,
* @property's config will be used).
* @config_property: name of a property of @config.
* @values: (not nullable) (transfer full):
* an array of GValues which could be values of @config_property.
* @in_values: whether @property should be sensitive when @config_property
* is one of @values, or the opposite.
*
* Sets sensitivity of the widget associated to @property in @dialog if the
* value of @config_property in @config is equal to one of @values.
*
* If @config is %NULL, then the configuration object of @dialog is used.
*
* If @in_values is FALSE, then the widget is set sensitive if the value of
* @config_property is **not** in @values.
*/
void
pika_procedure_dialog_set_sensitive_if_in (PikaProcedureDialog *dialog,
const gchar *property,
GObject *config,
const gchar *config_property,
PikaValueArray *values,
gboolean in_values)
{
PikaProcedureDialogSensitiveData2 *data;
GParamSpec *pspec;
gchar *signal_name;
g_return_if_fail (PIKA_IS_PROCEDURE_DIALOG (dialog));
g_return_if_fail (property != NULL);
g_return_if_fail (config_property != NULL);
g_return_if_fail (values != 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 on the PikaProcedure.",
G_STRFUNC, property);
return;
}
if (! config)
config = G_OBJECT (dialog->priv->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;
}
data = g_new (PikaProcedureDialogSensitiveData2, 1);
data->dialog = dialog;
data->widget_property = g_strdup (property);
data->values = values;
data->in_values = in_values;
signal_name = g_strconcat ("notify::", config_property, NULL);
g_signal_connect_data (config, signal_name,
G_CALLBACK (pika_procedure_dialog_set_sensitive_if_in_cb),
data,
(GClosureNotify) pika_procedure_dialog_sensitive_cb_data_free,
0);
pika_procedure_dialog_set_sensitive_if_in_cb (config, pspec, data);
g_free (signal_name);
}
2023-09-26 00:35:21 +02:00
/**
* pika_procedure_dialog_run:
* @dialog: the #PikaProcedureDialog.
*
* 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
pika_procedure_dialog_run (PikaProcedureDialog *dialog)
{
g_return_val_if_fail (PIKA_IS_PROCEDURE_DIALOG (dialog), FALSE);
while (TRUE)
{
gint response = pika_dialog_run (PIKA_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 (pika_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 (pika_procedure_dialog_reset_factory),
dialog);
}
gtk_popover_popup (GTK_POPOVER (dialog->priv->reset_popover));
}
else
{
return response == GTK_RESPONSE_OK;
}
}
}
/* private functions */
static void
pika_procedure_dialog_reset_initial (GtkWidget *button,
PikaProcedureDialog *dialog)
{
pika_config_copy (PIKA_CONFIG (dialog->priv->initial_config),
PIKA_CONFIG (dialog->priv->config),
0);
gtk_popover_popdown (GTK_POPOVER (dialog->priv->reset_popover));
}
static void
pika_procedure_dialog_reset_factory (GtkWidget *button,
PikaProcedureDialog *dialog)
{
pika_config_reset (PIKA_CONFIG (dialog->priv->config));
gtk_popover_popdown (GTK_POPOVER (dialog->priv->reset_popover));
}
static void
pika_procedure_dialog_load_defaults (GtkWidget *button,
PikaProcedureDialog *dialog)
{
GError *error = NULL;
if (! pika_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
pika_procedure_dialog_save_defaults (GtkWidget *button,
PikaProcedureDialog *dialog)
{
GError *error = NULL;
if (! pika_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,
pika_procedure_config_has_default (dialog->priv->config));
}
static gboolean
pika_procedure_dialog_check_mnemonic (PikaProcedureDialog *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 (PIKA_IS_LABELED (widget))
{
label = pika_labeled_get_label (PIKA_LABELED (widget));
}
2023-10-30 23:55:30 +01:00
else if (PIKA_IS_RESOURCE_CHOOSER (widget))
{
label = pika_resource_chooser_get_label (PIKA_RESOURCE_CHOOSER (widget));
}
else if (PIKA_IS_DRAWABLE_CHOOSER (widget))
{
label = pika_drawable_chooser_get_label (PIKA_DRAWABLE_CHOOSER (widget));
}
2023-09-26 00:35:21 +02:00
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",
pika_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));
else if (PIKA_IS_SPIN_SCALE (widget))
mnemonic = pika_spin_scale_get_mnemonic_keyval (PIKA_SPIN_SCALE (widget));
if (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",
pika_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",
pika_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",
pika_procedure_get_name (dialog->priv->procedure),
id ? id : core_id);
success = FALSE;
}
return success;
}
/**
* pika_procedure_dialog_fill_container_list:
* @dialog: the #PikaProcedureDialog.
* @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
* pika_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 *
pika_procedure_dialog_fill_container_list (PikaProcedureDialog *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 == PIKA_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;
2023-10-30 23:55:30 +01:00
g_free (pspecs);
2023-09-26 00:35:21 +02:00
}
sz_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL);
for (iter = properties; iter; iter = iter->next)
{
GtkWidget *widget;
widget = pika_procedure_dialog_get_widget (dialog, iter->data, G_TYPE_NONE);
if (widget)
{
gtk_container_add (container, widget);
if (PIKA_IS_LABELED (widget))
{
GtkWidget *label = pika_labeled_get_label (PIKA_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);
}
2023-10-30 23:55:30 +01:00
static void
pika_procedure_dialog_set_sensitive_if_in_cb (GObject *config,
GParamSpec *param_spec,
PikaProcedureDialogSensitiveData2 *data)
{
PikaProcedureDialog *dialog = data->dialog;
GtkWidget *widget;
widget = g_hash_table_lookup (dialog->priv->widgets, data->widget_property);
if (widget)
{
GValue param_value = G_VALUE_INIT;
gboolean sensitive;
gint n_values = pika_value_array_length (data->values);
g_value_init (&param_value, param_spec->value_type);
g_object_get_property (config, param_spec->name, &param_value);
sensitive = (! data->in_values);
for (gint i = 0; i < n_values; i++)
{
GValue *value;
value = pika_value_array_index (data->values, i);
if (g_param_values_cmp (param_spec, &param_value, value) == 0)
{
sensitive = data->in_values;
break;
}
}
gtk_widget_set_sensitive (widget, sensitive);
g_value_unset (&param_value);
}
else
{
g_printerr ("pika_procedure_dialog_set_sensitive_if_in: "
"no widget was created for property \"%s\".\n",
data->widget_property);
}
}
2023-09-26 00:35:21 +02:00
static void
pika_procedure_dialog_sensitive_data_free (PikaProcedureDialogSensitiveData *data)
{
g_free (data->config_property);
g_clear_object (&data->config);
g_slice_free (PikaProcedureDialogSensitiveData, data);
}
2023-10-30 23:55:30 +01:00
static void
pika_procedure_dialog_sensitive_cb_data_free (PikaProcedureDialogSensitiveData2 *data)
{
g_free (data->widget_property);
pika_value_array_unref (data->values);
g_free (data);
}