From 3fb2ff1b9d1380be35e68861cdb1dc2ac70c5723 Mon Sep 17 00:00:00 2001 From: Jehan Date: Tue, 24 Nov 2020 15:00:34 +0100 Subject: [PATCH] libgimp: improvements to GimpProcedureDialog API. - New gimp_procedure_dialog_fill_box(_list)() functions to create a GtkBox in the layout. - Generating widgets for parameters of type double (and computing appropriate "ok defaults" digits for these, depending on the min-max range of the property). --- libgimp/gimpproceduredialog.c | 337 +++++++++++++++++++++++++--------- libgimp/gimpproceduredialog.h | 7 + libgimp/gimpui.def | 2 + 3 files changed, 259 insertions(+), 87 deletions(-) diff --git a/libgimp/gimpproceduredialog.c b/libgimp/gimpproceduredialog.c index 47e292482c..21202e50ba 100644 --- a/libgimp/gimpproceduredialog.c +++ b/libgimp/gimpproceduredialog.c @@ -88,12 +88,18 @@ static void gimp_procedure_dialog_save_defaults (GtkWidget *button, static void gimp_procedure_dialog_estimate_increments (gdouble lower, gdouble upper, gdouble *step, - gdouble *page); + gdouble *page, + gint *digits); 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); G_DEFINE_TYPE_WITH_PRIVATE (GimpProcedureDialog, gimp_procedure_dialog, GIMP_TYPE_DIALOG) @@ -427,32 +433,48 @@ gimp_procedure_dialog_get_widget (GimpProcedureDialog *dialog, _(g_param_spec_get_nick (pspec)), &label, NULL); } - else if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_INT) + 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_procedure_dialog_estimate_increments (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, 0); + 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)), - 0, FALSE, 0.0, 0.0); + digits, FALSE, 0.0, 0.0); } else if (widget_type == GIMP_TYPE_SPIN_BUTTON) { /* Just some spin button without label. */ - GParamSpecInt *pspecint = (GParamSpecInt *) pspec; - gdouble step = 0.0; - gdouble page = 0.0; - - gimp_procedure_dialog_estimate_increments (pspecint->minimum, - pspecint->maximum, - &step, &page); widget = gimp_prop_spin_button_new (G_OBJECT (dialog->priv->config), - property, step, page, 0); + property, step, page, digits); } } else if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_STRING) @@ -755,6 +777,98 @@ gimp_procedure_dialog_fill_list (GimpProcedureDialog *dialog, 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. @@ -840,80 +954,11 @@ gimp_procedure_dialog_fill_flowbox_list (GimpProcedureDialog *dialog, const gchar *container_id, GList *properties) { - GtkWidget *flowbox; - GList *iter; - gboolean free_properties = FALSE; - 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 ((flowbox = g_hash_table_lookup (dialog->priv->widgets, container_id))) - { - g_warning ("%s: container identifier '%s' was already configured.", - G_STRFUNC, container_id); - return flowbox; - } - - flowbox = gtk_flow_box_new (); - - if (! properties) - { - GParamSpec **pspecs; - guint n_pspecs; - gint i; - - pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (dialog->priv->config), - &n_pspecs); - - for (i = 0; i < n_pspecs; i++) - { - const gchar *prop_name; - GParamSpec *pspec = pspecs[i]; - - /* skip our own properties */ - if (pspec->owner_type == GIMP_TYPE_PROCEDURE_CONFIG) - continue; - - prop_name = g_param_spec_get_name (pspec); - properties = g_list_prepend (properties, (gpointer) prop_name); - } - - properties = g_list_reverse (properties); - - if (properties) - free_properties = TRUE; - } - - for (iter = properties; iter; iter = iter->next) - { - GtkWidget *widget; - - widget = gimp_procedure_dialog_get_widget (dialog, iter->data, G_TYPE_NONE); - if (widget) - { - /* Reference the widget because the hash table will - * unreference it anyway when getting destroyed so we don't - * want to give the only reference to the parent widget. - */ - g_object_ref (widget); - gtk_container_add (GTK_CONTAINER (flowbox), widget); - gtk_widget_show (widget); - } - } - - if (free_properties) - g_list_free (properties); - - g_hash_table_insert (dialog->priv->widgets, g_strdup (container_id), flowbox); - - return flowbox; + return gimp_procedure_dialog_fill_container_list (dialog, container_id, + GTK_CONTAINER (gtk_flow_box_new ()), + properties); } @@ -1157,12 +1202,13 @@ static void gimp_procedure_dialog_estimate_increments (gdouble lower, gdouble upper, gdouble *step, - gdouble *page) + gdouble *page, + gint *digits) { gdouble range; g_return_if_fail (upper >= lower); - g_return_if_fail (step || page); + g_return_if_fail (step || page || digits); range = upper - lower; @@ -1170,6 +1216,9 @@ gimp_procedure_dialog_estimate_increments (gdouble lower, { gdouble places = 10.0; + if (digits) + *digits = 3; + /* Compute some acceptable step and page increments always in the * format `10**-X` where X is the rounded precision. * So for instance: @@ -1178,7 +1227,11 @@ gimp_procedure_dialog_estimate_increments (gdouble lower, * 0.06 will also have increments 0.001 and 0.01. */ while (range * places < 5.0) - places *= 10.0; + { + places *= 10.0; + if (digits) + (*digits)++; + } if (step) @@ -1192,6 +1245,9 @@ gimp_procedure_dialog_estimate_increments (gdouble lower, *step = 0.01; if (page) *page = 0.1; + + if (digits) + *digits = 3; } else if (range <= 5.0) { @@ -1199,6 +1255,8 @@ gimp_procedure_dialog_estimate_increments (gdouble lower, *step = 0.1; if (page) *page = 1.0; + if (digits) + *digits = 2; } else if (range <= 40.0) { @@ -1206,6 +1264,8 @@ gimp_procedure_dialog_estimate_increments (gdouble lower, *step = 1.0; if (page) *page = 2.0; + if (digits) + *digits = 2; } else { @@ -1213,6 +1273,8 @@ gimp_procedure_dialog_estimate_increments (gdouble lower, *step = 1.0; if (page) *page = 10.0; + if (digits) + *digits = 1; } } @@ -1285,3 +1347,104 @@ gimp_procedure_dialog_check_mnemonic (GimpProcedureDialog *dialog, return success; } + +/** + * gimp_procedure_dialog_fill_container_list: + * @dialog: + * @container_id: + * @container: (transfer full): + * @properties: + * + * A generic function to be used by various publich 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; + + g_return_val_if_fail (container_id != NULL, NULL); + g_return_val_if_fail (GTK_IS_CONTAINER (container), NULL); + g_return_val_if_fail (g_object_is_floating (G_OBJECT (container)), NULL); + + g_object_ref_sink (container); + if (g_object_class_find_property (G_OBJECT_GET_CLASS (dialog->priv->config), + container_id)) + { + g_warning ("%s: container identifier '%s' cannot be an existing property name.", + G_STRFUNC, container_id); + g_object_unref (container); + return NULL; + } + + if (g_hash_table_lookup (dialog->priv->widgets, container_id)) + { + g_warning ("%s: container identifier '%s' was already configured.", + G_STRFUNC, container_id); + g_object_unref (container); + return g_hash_table_lookup (dialog->priv->widgets, container_id); + } + + if (! properties) + { + GParamSpec **pspecs; + guint n_pspecs; + gint i; + + pspecs = g_object_class_list_properties (G_OBJECT_GET_CLASS (dialog->priv->config), + &n_pspecs); + + for (i = 0; i < n_pspecs; i++) + { + const gchar *prop_name; + GParamSpec *pspec = pspecs[i]; + + /* skip our own properties */ + if (pspec->owner_type == GIMP_TYPE_PROCEDURE_CONFIG) + continue; + + prop_name = g_param_spec_get_name (pspec); + properties = g_list_prepend (properties, (gpointer) prop_name); + } + + properties = g_list_reverse (properties); + + if (properties) + free_properties = TRUE; + } + + for (iter = properties; iter; iter = iter->next) + { + GtkWidget *widget; + + widget = gimp_procedure_dialog_get_widget (dialog, iter->data, G_TYPE_NONE); + if (widget) + { + /* Reference the widget because the hash table will + * unreference it anyway when getting destroyed so we don't + * want to give the only reference to the parent widget. + */ + g_object_ref (widget); + gtk_container_add (container, widget); + gtk_widget_show (widget); + } + } + + if (free_properties) + g_list_free (properties); + + g_hash_table_insert (dialog->priv->widgets, g_strdup (container_id), container); + + return GTK_WIDGET (container); +} diff --git a/libgimp/gimpproceduredialog.h b/libgimp/gimpproceduredialog.h index 8825f93d7c..d8d321f357 100644 --- a/libgimp/gimpproceduredialog.h +++ b/libgimp/gimpproceduredialog.h @@ -85,6 +85,13 @@ GtkWidget * gimp_procedure_dialog_get_label (GimpProcedureDialog *dialog const gchar *label_id, const gchar *text); +GtkWidget * gimp_procedure_dialog_fill_box (GimpProcedureDialog *dialog, + const gchar *container_id, + const gchar *first_property, + ...); +GtkWidget * gimp_procedure_dialog_fill_box_list (GimpProcedureDialog *dialog, + const gchar *container_id, + GList *properties); GtkWidget * gimp_procedure_dialog_fill_flowbox (GimpProcedureDialog *dialog, const gchar *container_id, const gchar *first_property, diff --git a/libgimp/gimpui.def b/libgimp/gimpui.def index e61530fc74..2477e8e26b 100644 --- a/libgimp/gimpui.def +++ b/libgimp/gimpui.def @@ -40,6 +40,8 @@ EXPORTS gimp_proc_browser_dialog_new gimp_proc_view_new gimp_procedure_dialog_fill + gimp_procedure_dialog_fill_box + gimp_procedure_dialog_fill_box_list gimp_procedure_dialog_fill_flowbox gimp_procedure_dialog_fill_flowbox_list gimp_procedure_dialog_fill_frame