[gimp] libgimp: add some first API to populate a GimpProcedureDialog.



commit c1a32e57637f564c802f423ca2d393fe81846b0f
Author: Jehan <jehan girinstud io>
Date:   Mon Nov 2 22:42:17 2020 +0100

    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.

 libgimp/gimpproceduredialog.c | 329 ++++++++++++++++++++++++++++++++++++++++++
 libgimp/gimpproceduredialog.h |  21 ++-
 libgimp/gimpui.def            |   3 +
 3 files changed, 348 insertions(+), 5 deletions(-)
---
diff --git a/libgimp/gimpproceduredialog.c b/libgimp/gimpproceduredialog.c
index 0cda474f14..8dd7551e8e 100644
--- a/libgimp/gimpproceduredialog.c
+++ b/libgimp/gimpproceduredialog.c
@@ -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;
+    }
+}
diff --git a/libgimp/gimpproceduredialog.h b/libgimp/gimpproceduredialog.h
index 1c54da7589..356c92345a 100644
--- a/libgimp/gimpproceduredialog.h
+++ b/libgimp/gimpproceduredialog.h
@@ -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
diff --git a/libgimp/gimpui.def b/libgimp/gimpui.def
index 5b0e544a70..ed1ab8c8e4 100644
--- a/libgimp/gimpui.def
+++ b/libgimp/gimpui.def
@@ -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


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]