mirror of https://github.com/GNOME/gimp.git
libgimp: add some first API to populate a GimpProcedureDialog.
This is still a very early baseline for a more extended API. This first version is not able to capture the complexity of most existing plug-in dialogs.
This commit is contained in:
parent
4ce6d35a53
commit
c1a32e5763
|
@ -3,6 +3,7 @@
|
|||
*
|
||||
* gimpproceduredialog.c
|
||||
* Copyright (C) 2019 Michael Natterer <mitch@gimp.org>
|
||||
* Copyright (C) 2020 Jehan
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
|
@ -51,6 +52,8 @@ struct _GimpProcedureDialogPrivate
|
|||
GimpProcedureConfig *initial_config;
|
||||
|
||||
GtkWidget *reset_popover;
|
||||
|
||||
GHashTable *widgets;
|
||||
};
|
||||
|
||||
|
||||
|
@ -73,6 +76,11 @@ static void gimp_procedure_dialog_load_defaults (GtkWidget *button,
|
|||
static void gimp_procedure_dialog_save_defaults (GtkWidget *button,
|
||||
GimpProcedureDialog *dialog);
|
||||
|
||||
static void gimp_procedure_dialog_estimate_increments (gdouble lower,
|
||||
gdouble upper,
|
||||
gdouble *step,
|
||||
gdouble *page);
|
||||
|
||||
|
||||
G_DEFINE_TYPE_WITH_PRIVATE (GimpProcedureDialog, gimp_procedure_dialog,
|
||||
GIMP_TYPE_DIALOG)
|
||||
|
@ -113,6 +121,8 @@ static void
|
|||
gimp_procedure_dialog_init (GimpProcedureDialog *dialog)
|
||||
{
|
||||
dialog->priv = gimp_procedure_dialog_get_instance_private (dialog);
|
||||
|
||||
dialog->priv->widgets = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -125,6 +135,13 @@ gimp_procedure_dialog_dispose (GObject *object)
|
|||
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_unref);
|
||||
|
||||
if (dialog->priv->widgets)
|
||||
{
|
||||
g_hash_table_destroy (dialog->priv->widgets);
|
||||
dialog->priv->widgets = NULL;
|
||||
}
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->dispose (object);
|
||||
}
|
||||
|
@ -268,6 +285,243 @@ gimp_procedure_dialog_new (GimpProcedure *procedure,
|
|||
return GTK_WIDGET (dialog);
|
||||
}
|
||||
|
||||
/**
|
||||
* gimp_procedure_dialog_get_widget:
|
||||
* @dialog: the associated #GimpProcedureDialog.
|
||||
* @property: name of the property to build a dialog for. It must be a
|
||||
* property of the #GimpProcedure @dialog has been created
|
||||
* for.
|
||||
* @widget_type: alternative widget type. %G_TYPE_NONE will create the
|
||||
* default type of widget for the associated property
|
||||
* type.
|
||||
*
|
||||
* Creates a new property #GtkWidget for @property according to the
|
||||
* property type. For instance by default a %G_TYPE_PARAM_BOOLEAN
|
||||
* property will be represented by a #GtkCheckButton.
|
||||
* Alternative @widget_type are possible, such as a %G_TYPE_SWITCH for a
|
||||
* %G_TYPE_PARAM_BOOLEAN property. If the @widget_type is not
|
||||
* supported, the function will fail. To keep the default, set to
|
||||
* %G_TYPE_NONE).
|
||||
* If a widget has already been created for this procedure, it will be
|
||||
* returned instead (even if with a different @widget_type).
|
||||
*
|
||||
* Returns: (transfer none): the #GtkWidget representing @property. The
|
||||
* object belongs to @dialog and must not be
|
||||
* freed.
|
||||
*/
|
||||
GtkWidget *
|
||||
gimp_procedure_dialog_get_widget (GimpProcedureDialog *dialog,
|
||||
const gchar *property,
|
||||
GType widget_type)
|
||||
{
|
||||
GtkWidget *widget = NULL;
|
||||
GParamSpec *pspec;
|
||||
|
||||
g_return_val_if_fail (property != NULL, NULL);
|
||||
|
||||
/* First check if it already exists. */
|
||||
widget = g_hash_table_lookup (dialog->priv->widgets, property);
|
||||
|
||||
if (widget)
|
||||
return widget;
|
||||
|
||||
pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (dialog->priv->config),
|
||||
property);
|
||||
if (! pspec)
|
||||
{
|
||||
g_warning ("%s: parameter %s does not exist.",
|
||||
G_STRFUNC, property);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_BOOLEAN)
|
||||
{
|
||||
if (widget_type == G_TYPE_NONE || widget_type == GTK_TYPE_CHECK_BUTTON)
|
||||
widget = gimp_prop_check_button_new (G_OBJECT (dialog->priv->config),
|
||||
property,
|
||||
_(g_param_spec_get_nick (pspec)));
|
||||
else if (widget_type == GTK_TYPE_SWITCH)
|
||||
widget = gimp_prop_switch_new (G_OBJECT (dialog->priv->config),
|
||||
property,
|
||||
_(g_param_spec_get_nick (pspec)),
|
||||
NULL, NULL);
|
||||
}
|
||||
else if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_INT)
|
||||
{
|
||||
if (widget_type == G_TYPE_NONE || 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);
|
||||
}
|
||||
else if (widget_type == GIMP_TYPE_SPIN_BUTTON)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
else if (G_PARAM_SPEC_TYPE (pspec) == G_TYPE_PARAM_STRING)
|
||||
{
|
||||
widget = gimp_prop_entry_new (G_OBJECT (dialog->priv->config),
|
||||
property, -1);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_warning ("%s: parameter %s has non supported type %s",
|
||||
G_STRFUNC, property, G_PARAM_SPEC_TYPE_NAME (pspec));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (! widget)
|
||||
{
|
||||
g_warning ("%s: widget type %s not supported for parameter '%s' of type %s",
|
||||
G_STRFUNC, G_OBJECT_TYPE_NAME (widget_type),
|
||||
property, G_PARAM_SPEC_TYPE_NAME (pspec));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
g_hash_table_insert (dialog->priv->widgets, g_strdup (property), widget);
|
||||
|
||||
return widget;
|
||||
}
|
||||
|
||||
/**
|
||||
* gimp_procedure_dialog_populate:
|
||||
* @dialog: the #GimpProcedureDialog.
|
||||
* @first_property: the first property name.
|
||||
* @...: a %NULL-terminated list of other property names.
|
||||
*
|
||||
* Populated @dialog with the widgets corresponding to every listed
|
||||
* properties. If the list is empty, @dialog will be filled by the whole
|
||||
* list of properties of the associated #GimpProcedure, in the defined
|
||||
* order:
|
||||
* |[<!-- language="C" -->
|
||||
* gimp_procedure_dialog_populate (dialog, NULL);
|
||||
* ]|
|
||||
* Nevertheless if you only wish to display a partial list of
|
||||
* properties, or if you wish to change the display order, then you have
|
||||
* to give an explicit list:
|
||||
* |[<!-- language="C" -->
|
||||
* gimp_procedure_dialog_populate (dialog, "property-1", "property-1", NULL);
|
||||
* ]|
|
||||
* You do not have gimp_procedure_dialog_get_widget() before calling
|
||||
* this function unless you want a given property to be represented by
|
||||
* an alternative widget type.
|
||||
*/
|
||||
void
|
||||
gimp_procedure_dialog_populate (GimpProcedureDialog *dialog,
|
||||
const gchar *first_property,
|
||||
...)
|
||||
{
|
||||
const gchar *prop_name = first_property;
|
||||
GList *list = NULL;
|
||||
va_list va_args;
|
||||
|
||||
g_return_if_fail (GIMP_IS_PROCEDURE_DIALOG (dialog));
|
||||
|
||||
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);
|
||||
gimp_procedure_dialog_populate_list (dialog, list);
|
||||
if (list)
|
||||
g_list_free (list);
|
||||
}
|
||||
|
||||
/**
|
||||
* gimp_procedure_dialog_populate_list: (rename-to gimp_procedure_dialog_populate)
|
||||
* @dialog: the #GimpProcedureDialog.
|
||||
* @properties: (nullable) (element-type gchar*): the list of property names.
|
||||
*
|
||||
* Populated @dialog with the widgets corresponding to every listed
|
||||
* properties. If the list is %NULL, @dialog will be filled by the whole
|
||||
* list of properties of the associated #GimpProcedure, in the defined
|
||||
* order:
|
||||
* |[<!-- language="C" -->
|
||||
* gimp_procedure_dialog_populate_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:
|
||||
* You do not have gimp_procedure_dialog_get_widget() before calling
|
||||
* this function unless you want a given property to be represented by
|
||||
* an alternative widget type.
|
||||
*/
|
||||
void
|
||||
gimp_procedure_dialog_populate_list (GimpProcedureDialog *dialog,
|
||||
GList *properties)
|
||||
{
|
||||
GtkWidget *content_area;
|
||||
GList *iter;
|
||||
gboolean free_properties = FALSE;
|
||||
|
||||
content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
|
||||
|
||||
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_box_pack_start (GTK_BOX (content_area), widget, TRUE, TRUE, 0);
|
||||
gtk_widget_show (widget);
|
||||
}
|
||||
}
|
||||
|
||||
if (free_properties)
|
||||
g_list_free (properties);
|
||||
}
|
||||
|
||||
gboolean
|
||||
gimp_procedure_dialog_run (GimpProcedureDialog *dialog)
|
||||
{
|
||||
|
@ -380,3 +634,78 @@ gimp_procedure_dialog_save_defaults (GtkWidget *button,
|
|||
g_clear_error (&error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* gimp_procedure_dialog_estimate_increments:
|
||||
* @lower:
|
||||
* @upper:
|
||||
* @step:
|
||||
* @page:
|
||||
*
|
||||
* Though sometimes you might want to specify step and page increments
|
||||
* on widgets explicitly, sometimes you are fine with just anything
|
||||
* which doesn't give you absurd values. This procedure just tries to
|
||||
* return such sensible increment values.
|
||||
*/
|
||||
static void
|
||||
gimp_procedure_dialog_estimate_increments (gdouble lower,
|
||||
gdouble upper,
|
||||
gdouble *step,
|
||||
gdouble *page)
|
||||
{
|
||||
gdouble range;
|
||||
|
||||
g_return_if_fail (upper >= lower);
|
||||
g_return_if_fail (step || page);
|
||||
|
||||
range = upper - lower;
|
||||
|
||||
if (range > 0 && range <= 1.0)
|
||||
{
|
||||
gdouble places = 10.0;
|
||||
|
||||
/* Compute some acceptable step and page increments always in the
|
||||
* format `10**-X` where X is the rounded precision.
|
||||
* So for instance:
|
||||
* 0.8 will have increments 0.01 and 0.1.
|
||||
* 0.3 will have increments 0.001 and 0.01.
|
||||
* 0.06 will also have increments 0.001 and 0.01.
|
||||
*/
|
||||
while (range * places < 5.0)
|
||||
places *= 10.0;
|
||||
|
||||
|
||||
if (step)
|
||||
*step = 0.1 / places;
|
||||
if (page)
|
||||
*page = 1.0 / places;
|
||||
}
|
||||
else if (range <= 2.0)
|
||||
{
|
||||
if (step)
|
||||
*step = 0.01;
|
||||
if (page)
|
||||
*page = 0.1;
|
||||
}
|
||||
else if (range <= 5.0)
|
||||
{
|
||||
if (step)
|
||||
*step = 0.1;
|
||||
if (page)
|
||||
*page = 1.0;
|
||||
}
|
||||
else if (range <= 40.0)
|
||||
{
|
||||
if (step)
|
||||
*step = 1.0;
|
||||
if (page)
|
||||
*page = 2.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (step)
|
||||
*step = 1.0;
|
||||
if (page)
|
||||
*page = 10.0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -64,13 +64,24 @@ struct _GimpProcedureDialogClass
|
|||
};
|
||||
|
||||
|
||||
GType gimp_procedure_dialog_get_type (void) G_GNUC_CONST;
|
||||
GType gimp_procedure_dialog_get_type (void) G_GNUC_CONST;
|
||||
|
||||
GtkWidget * gimp_procedure_dialog_new (GimpProcedure *procedure,
|
||||
GimpProcedureConfig *config,
|
||||
const gchar *title);
|
||||
GtkWidget * gimp_procedure_dialog_new (GimpProcedure *procedure,
|
||||
GimpProcedureConfig *config,
|
||||
const gchar *title);
|
||||
|
||||
gboolean gimp_procedure_dialog_run (GimpProcedureDialog *dialog);
|
||||
GtkWidget * gimp_procedure_dialog_get_widget (GimpProcedureDialog *dialog,
|
||||
const gchar *property,
|
||||
GType widget_type);
|
||||
|
||||
void gimp_procedure_dialog_populate (GimpProcedureDialog *dialog,
|
||||
const gchar *first_property,
|
||||
...);
|
||||
void gimp_procedure_dialog_populate_list (GimpProcedureDialog *dialog,
|
||||
GList *properties);
|
||||
|
||||
|
||||
gboolean gimp_procedure_dialog_run (GimpProcedureDialog *dialog);
|
||||
|
||||
|
||||
G_END_DECLS
|
||||
|
|
|
@ -40,7 +40,10 @@ EXPORTS
|
|||
gimp_proc_browser_dialog_new
|
||||
gimp_proc_view_new
|
||||
gimp_procedure_dialog_get_type
|
||||
gimp_procedure_dialog_get_widget
|
||||
gimp_procedure_dialog_new
|
||||
gimp_procedure_dialog_populate
|
||||
gimp_procedure_dialog_populate_list
|
||||
gimp_procedure_dialog_run
|
||||
gimp_progress_bar_get_type
|
||||
gimp_progress_bar_new
|
||||
|
|
Loading…
Reference in New Issue