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).
This commit is contained in:
Jehan 2020-11-24 15:00:34 +01:00
parent e53ce2fde9
commit 3fb2ff1b9d
3 changed files with 259 additions and 87 deletions

View File

@ -88,12 +88,18 @@ static void gimp_procedure_dialog_save_defaults (GtkWidget *button,
static void gimp_procedure_dialog_estimate_increments (gdouble lower, static void gimp_procedure_dialog_estimate_increments (gdouble lower,
gdouble upper, gdouble upper,
gdouble *step, gdouble *step,
gdouble *page); gdouble *page,
gint *digits);
static gboolean gimp_procedure_dialog_check_mnemonic (GimpProcedureDialog *dialog, static gboolean gimp_procedure_dialog_check_mnemonic (GimpProcedureDialog *dialog,
GtkWidget *widget, GtkWidget *widget,
const gchar *id, const gchar *id,
const gchar *core_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, G_DEFINE_TYPE_WITH_PRIVATE (GimpProcedureDialog, gimp_procedure_dialog,
GIMP_TYPE_DIALOG) GIMP_TYPE_DIALOG)
@ -427,32 +433,48 @@ gimp_procedure_dialog_get_widget (GimpProcedureDialog *dialog,
_(g_param_spec_get_nick (pspec)), _(g_param_spec_get_nick (pspec)),
&label, NULL); &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) if (widget_type == G_TYPE_NONE || widget_type == GIMP_TYPE_LABEL_SPIN)
{ {
widget = gimp_prop_label_spin_new (G_OBJECT (dialog->priv->config), widget = gimp_prop_label_spin_new (G_OBJECT (dialog->priv->config),
property, 0); property, digits);
} }
else if (widget_type == GIMP_TYPE_SCALE_ENTRY) else if (widget_type == GIMP_TYPE_SCALE_ENTRY)
{ {
widget = gimp_prop_scale_entry_new (G_OBJECT (dialog->priv->config), widget = gimp_prop_scale_entry_new (G_OBJECT (dialog->priv->config),
property, property,
_(g_param_spec_get_nick (pspec)), _(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) else if (widget_type == GIMP_TYPE_SPIN_BUTTON)
{ {
/* Just some spin button without label. */ /* 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), 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) 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); 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: * gimp_procedure_dialog_fill_flowbox:
* @dialog: the #GimpProcedureDialog. * @dialog: the #GimpProcedureDialog.
@ -840,80 +954,11 @@ gimp_procedure_dialog_fill_flowbox_list (GimpProcedureDialog *dialog,
const gchar *container_id, const gchar *container_id,
GList *properties) GList *properties)
{ {
GtkWidget *flowbox;
GList *iter;
gboolean free_properties = FALSE;
g_return_val_if_fail (container_id != NULL, NULL); g_return_val_if_fail (container_id != NULL, NULL);
if (g_object_class_find_property (G_OBJECT_GET_CLASS (dialog->priv->config), return gimp_procedure_dialog_fill_container_list (dialog, container_id,
container_id)) GTK_CONTAINER (gtk_flow_box_new ()),
{ properties);
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;
} }
@ -1157,12 +1202,13 @@ static void
gimp_procedure_dialog_estimate_increments (gdouble lower, gimp_procedure_dialog_estimate_increments (gdouble lower,
gdouble upper, gdouble upper,
gdouble *step, gdouble *step,
gdouble *page) gdouble *page,
gint *digits)
{ {
gdouble range; gdouble range;
g_return_if_fail (upper >= lower); g_return_if_fail (upper >= lower);
g_return_if_fail (step || page); g_return_if_fail (step || page || digits);
range = upper - lower; range = upper - lower;
@ -1170,6 +1216,9 @@ gimp_procedure_dialog_estimate_increments (gdouble lower,
{ {
gdouble places = 10.0; gdouble places = 10.0;
if (digits)
*digits = 3;
/* Compute some acceptable step and page increments always in the /* Compute some acceptable step and page increments always in the
* format `10**-X` where X is the rounded precision. * format `10**-X` where X is the rounded precision.
* So for instance: * 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. * 0.06 will also have increments 0.001 and 0.01.
*/ */
while (range * places < 5.0) while (range * places < 5.0)
{
places *= 10.0; places *= 10.0;
if (digits)
(*digits)++;
}
if (step) if (step)
@ -1192,6 +1245,9 @@ gimp_procedure_dialog_estimate_increments (gdouble lower,
*step = 0.01; *step = 0.01;
if (page) if (page)
*page = 0.1; *page = 0.1;
if (digits)
*digits = 3;
} }
else if (range <= 5.0) else if (range <= 5.0)
{ {
@ -1199,6 +1255,8 @@ gimp_procedure_dialog_estimate_increments (gdouble lower,
*step = 0.1; *step = 0.1;
if (page) if (page)
*page = 1.0; *page = 1.0;
if (digits)
*digits = 2;
} }
else if (range <= 40.0) else if (range <= 40.0)
{ {
@ -1206,6 +1264,8 @@ gimp_procedure_dialog_estimate_increments (gdouble lower,
*step = 1.0; *step = 1.0;
if (page) if (page)
*page = 2.0; *page = 2.0;
if (digits)
*digits = 2;
} }
else else
{ {
@ -1213,6 +1273,8 @@ gimp_procedure_dialog_estimate_increments (gdouble lower,
*step = 1.0; *step = 1.0;
if (page) if (page)
*page = 10.0; *page = 10.0;
if (digits)
*digits = 1;
} }
} }
@ -1285,3 +1347,104 @@ gimp_procedure_dialog_check_mnemonic (GimpProcedureDialog *dialog,
return success; 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);
}

View File

@ -85,6 +85,13 @@ GtkWidget * gimp_procedure_dialog_get_label (GimpProcedureDialog *dialog
const gchar *label_id, const gchar *label_id,
const gchar *text); 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, GtkWidget * gimp_procedure_dialog_fill_flowbox (GimpProcedureDialog *dialog,
const gchar *container_id, const gchar *container_id,
const gchar *first_property, const gchar *first_property,

View File

@ -40,6 +40,8 @@ EXPORTS
gimp_proc_browser_dialog_new gimp_proc_browser_dialog_new
gimp_proc_view_new gimp_proc_view_new
gimp_procedure_dialog_fill 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
gimp_procedure_dialog_fill_flowbox_list gimp_procedure_dialog_fill_flowbox_list
gimp_procedure_dialog_fill_frame gimp_procedure_dialog_fill_frame