[gimp] Issue #5402 Scriptfu handle GFile and GimpObjectArray types



commit e57304f71a44ccb1393a75fac85231293d6a40a2
Author: lloyd konneker <konnekerl gmail com>
Date:   Fri Apr 30 13:51:04 2021 -0400

    Issue #5402 Scriptfu handle GFile and GimpObjectArray types

 plug-ins/script-fu/Makefile.am      |   2 +
 plug-ins/script-fu/meson.build      |   1 +
 plug-ins/script-fu/scheme-marshal.c | 233 ++++++++++++++++++++++++++++++++++++
 plug-ins/script-fu/scheme-marshal.h |  45 +++++++
 plug-ins/script-fu/scheme-wrapper.c | 135 +++++++++++++++------
 5 files changed, 377 insertions(+), 39 deletions(-)
---
diff --git a/plug-ins/script-fu/Makefile.am b/plug-ins/script-fu/Makefile.am
index b39dd999e0..bc5e0f829b 100644
--- a/plug-ins/script-fu/Makefile.am
+++ b/plug-ins/script-fu/Makefile.am
@@ -85,6 +85,8 @@ script_fu_SOURCES = \
        script-fu-errors.h              \
        script-fu-compat.c              \
        script-fu-compat.h              \
+       scheme-marshal.c                \
+       scheme-marshal.h                \
        scheme-wrapper.c                \
        scheme-wrapper.h
 
diff --git a/plug-ins/script-fu/meson.build b/plug-ins/script-fu/meson.build
index c13a6c14ce..1a2fb4ccb2 100644
--- a/plug-ins/script-fu/meson.build
+++ b/plug-ins/script-fu/meson.build
@@ -8,6 +8,7 @@ plugin_name = 'script-fu'
 
 plugin_sources = [
   'scheme-wrapper.c',
+  'scheme-marshal.c',
   'script-fu-console.c',
   'script-fu-eval.c',
   'script-fu-interface.c',
diff --git a/plug-ins/script-fu/scheme-marshal.c b/plug-ins/script-fu/scheme-marshal.c
new file mode 100644
index 0000000000..afe05e7ab0
--- /dev/null
+++ b/plug-ins/script-fu/scheme-marshal.c
@@ -0,0 +1,233 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * 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 "libgimp/gimp.h"
+#include "tinyscheme/scheme-private.h"
+#include "scheme-marshal.h"
+#include "script-fu-errors.h"
+
+
+/*
+ * Marshal arguments to, and return values from, calls to PDB.
+ * Convert Scheme constructs to/from a GValue.
+ *
+ * For each marshalling function:
+ *   - a returned "pointer" is a scheme pointer to a foreign error or NULL.
+ *   - marshal into a GValue holding a designated type,
+ *     usually a GIMP type but also GLib types, e.g. GFile.
+ *     The GValue's held type is already set, but value is uninitialized.
+ *
+ * When marshalling into a GimpObjectArray, arbitrarily say the contained type is GIMP_TYPE_DRAWABLE.
+ * The actual contained type is opaque to the PDB calling mechanism.
+ * Setting the GValue's value does not check the contained type.
+ * But we do call gimp_drawable_get_by_id.
+ * GIMP_TYPE_DRAWABLE is a superclass of most common uses.
+ * But perhaps we should call gimp_item_get_by_id
+ * and arbitrarily say GIMP_TYPE_ITEM, a superclass of drawable.
+ */
+
+
+
+
+/* Marshal single drawable ID from script into a single GObject. */
+pointer
+marshal_ID_to_drawable (scheme   *sc,
+                        pointer   a,
+                        gint      id,
+                        GValue   *value)
+{
+  GimpDrawable *drawable;
+
+  pointer error = get_drawable_from_script (sc, a, id, &drawable);
+  if (error)
+    return error;
+
+  /* drawable is NULL or valid */
+
+  /* Shallow copy, adding a reference while the GValue exists. */
+  g_value_set_object (value, drawable);
+  return NULL;  /* no error */
+}
+
+/* Marshal a vector of ID into GimpObjectArray of same length. */
+pointer
+marshal_vector_to_drawable_array (scheme   *sc,
+                                  pointer   vector,
+                                  GValue   *value)
+{
+  GimpDrawable **drawable_array;
+  gint           id;
+  pointer        error;
+
+  guint num_elements = sc->vptr->vector_length (vector);
+  g_debug ("vector has %d elements", num_elements);
+  /* empty vector will produce empty GimpObjectArray */
+
+  drawable_array = g_new0 (GimpDrawable*, num_elements);
+
+  for (int j = 0; j < num_elements; ++j)
+    {
+      pointer element = sc->vptr->vector_elem (vector, j);
+
+      if (!sc->vptr->is_number (element))
+        {
+          g_free (drawable_array);
+          return script_error (sc, "Expected numeric in drawable vector", vector);
+          /* FUTURE more detailed error msg:
+           * return script_type_error_in_container (sc, "numeric", i, j, proc_name, vector);
+           */
+        }
+
+      id = sc->vptr->ivalue (element);
+      error = get_drawable_from_script (sc, element, id, &drawable_array[j]);
+      if (error)
+        {
+          g_free (drawable_array);
+          return error;
+        }
+    }
+
+  /* Shallow copy. */
+  gimp_value_set_object_array (value, GIMP_TYPE_DRAWABLE, (GObject**)drawable_array, num_elements);
+
+  g_free (drawable_array);
+
+  return NULL;  /* no error */
+}
+
+
+/* Marshal path string from script into a GValue holding type GFile */
+void
+marshal_path_string_to_gfile (scheme     *sc,
+                              pointer     a,
+                              GValue     *value)
+{
+  /* require sc->vptr->is_string (sc->vptr->pair_car (a)) */
+
+  GFile *gfile = g_file_new_for_path (sc->vptr->string_value (sc->vptr->pair_car (a)));
+  /* GLib docs say that g_file_new_for_path():
+   * "never fails, but the returned object might not support any I/O operation if path is malformed."
+   */
+
+  g_value_set_object (value, gfile);
+  g_debug ("gfile arg is '%s'\n", g_file_get_parse_name (gfile));
+}
+
+
+/* Marshal values returned from PDB call in a GValue, into a Scheme construct to a script. */
+
+
+/* Marshal a GValue holding a GFile into a string.
+ *
+ * Returns NULL or a string that must be freed.
+ */
+ gchar *
+ marshal_returned_gfile_to_string (GValue   *value)
+{
+  gchar * filepath = NULL;
+
+  GObject *object = g_value_get_object (value);
+  /* object can be NULL, the GValue's type only indicates what should have been returned. */
+  if (object)
+    {
+      filepath = g_file_get_parse_name ((GFile *) object);
+      /* GLib docs:
+       * For local files with names that can safely be converted to UTF-8 the pathname is used,
+       * otherwise the IRI is used (a form of URI that allows UTF-8 characters unescaped).
+       */
+     }
+  return filepath;
+}
+
+
+/* Marshal a GimpObjectArray into a Scheme list of ID's.
+ *
+ * Before v3.0, PDB procedure's return type was say INT32ARRAY,
+ * preceded by a type INT32 designating array length.
+ * Now return type is GimpObjectArray preceded by length.
+ *
+ * Returns a vector, since most arrays in Scriptfu are returned as vectors.
+ * An alternate implementation would be return list.
+ *
+ * Existing scheme plugins usually expect PDB to return values: len, vector.
+ * If ever the PDB is changed to be more object-oriented,
+ * scripts could use a scheme call: (vector-length vector)
+ * to get the length of the vector.
+ */
+pointer
+marshal_returned_object_array_to_vector (scheme   *sc,
+                                         GValue   *value)
+{
+  GObject **object_array;
+  gint32    n;
+  pointer   vector;
+
+  object_array = gimp_value_get_object_array (value);
+  /* array knows own length, ignore length in preceding return value */
+  n = ((GimpObjectArray*)g_value_get_boxed (value))->length;
+
+  vector = sc->vptr->mk_vector (sc, n);
+
+  /* Iterate starting at the back of the array, and prefix to container
+   * so the order of objects is not changed.
+   */
+  for (int j = n - 1; j >= 0; j--)
+    {
+      gint     id;
+      GObject *object = object_array[j];
+
+      if (object)
+        g_object_get (object, "id", &id, NULL); /* get property "id" */
+      else
+        /* Scriptfu language represents NULL object by ID of -1*/
+        id = -1;
+
+      sc->vptr->set_vector_elem (vector, j, sc->vptr->mk_integer (sc, id));
+      /* Alt: list = sc->vptr->cons (sc, sc->vptr->mk_integer (sc, id), list); */
+    }
+  /* ensure container's len equals object array's len and all elements are ID's or -1 */
+  return vector;
+}
+
+
+ /* From a script numeric (a drawable ID) set a handle to a drawable.
+  * When ID is -1, sets drawable to NULL and returns no error.
+  * When ID is valid, sets drawable and returns no error.
+  * Otherwise (ID is not -1 and not valid ID of a drawable) returns error.
+  */
+pointer
+get_drawable_from_script (scheme        *sc,
+                          pointer        a,
+                          gint           id,
+                          GimpDrawable **drawable_handle)
+{
+  if (id == -1)
+    {
+      /* -1 is scriptfu language for NULL i.e. none for an optional */
+      *drawable_handle = NULL;
+    }
+  else
+    {
+      *drawable_handle = gimp_drawable_get_by_id (id);
+      if (! *drawable_handle)
+          return script_error (sc, "Invalid drawable ID", a);
+    }
+
+  /* ensure *drawable_handle is NULL or a valid reference to a drawable */
+  return NULL;  /* no error */
+}
diff --git a/plug-ins/script-fu/scheme-marshal.h b/plug-ins/script-fu/scheme-marshal.h
new file mode 100644
index 0000000000..8a499c8723
--- /dev/null
+++ b/plug-ins/script-fu/scheme-marshal.h
@@ -0,0 +1,45 @@
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * 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/>.
+ */
+
+#ifndef __SCHEME_MARSHAL_H__
+#define __SCHEME_MARSHAL_H__
+
+pointer       get_drawable_from_script         (scheme        *sc,
+                                                pointer        a,
+                                                gint           id,
+                                                GimpDrawable **drawable_handle);
+
+pointer       marshal_ID_to_drawable           (scheme   *sc,
+                                                pointer   a,
+                                                gint      id,
+                                                GValue   *value);
+
+pointer       marshal_vector_to_drawable_array (scheme   *sc,
+                                                pointer   a,
+                                                GValue   *value);
+
+void          marshal_path_string_to_gfile     (scheme   *sc,
+                                                pointer   a,
+                                                GValue   *value);
+
+
+pointer       marshal_returned_object_array_to_vector (scheme   *sc,
+                                                       GValue   *value);
+
+gchar *       marshal_returned_gfile_to_string        (GValue   *value);
+
+#endif /* __SCHEME_MARSHAL_H__ */
diff --git a/plug-ins/script-fu/scheme-wrapper.c b/plug-ins/script-fu/scheme-wrapper.c
index 6417c96378..7698e56ec1 100644
--- a/plug-ins/script-fu/scheme-wrapper.c
+++ b/plug-ins/script-fu/scheme-wrapper.c
@@ -45,6 +45,7 @@
 #include "script-fu-compat.h"
 
 #include "scheme-wrapper.h"
+#include "scheme-marshal.h"
 
 
 #undef cons
@@ -538,6 +539,8 @@ script_fu_marshal_procedure_call (scheme   *sc,
   gchar           *proc_name;
   GParamSpec     **arg_specs;
   gint             n_arg_specs;
+  gint             actual_arg_count;
+  gint             consumed_arg_count = 0;
   gchar            error_str[1024];
   gint             i;
   pointer          return_val = sc->NIL;
@@ -581,44 +584,34 @@ script_fu_marshal_procedure_call (scheme   *sc,
     }
 
   arg_specs = gimp_procedure_get_arguments (procedure, &n_arg_specs);
-
-  /*  Check the supplied number of arguments  */
+  actual_arg_count = sc->vptr->list_length (sc, a) - 1;
+
+  /* Check the supplied number of arguments.
+   * This only gives warnings to the console.
+   * It does not ensure that the count of supplied args equals the count of formal args.
+   * Subsequent code must not assume that.
+   *
+   * When too few supplied args, when permissive, scriptfu or downstream machinery
+   * can try to provide missing args e.g. defaults.
+   *
+   * Extra supplied args can be discarded.
+   * Formerly, this was a deprecated behavior depending on "permissive".
+   */
   {
-    int actual_arg_count = sc->vptr->list_length (sc, a) - 1;
-
-    if (n_arg_specs == 0)
+    if (actual_arg_count > n_arg_specs)
       {
-        if (actual_arg_count > 0 )
-          {
-            if (permissive)
-              {
-                /* Warn but permit extra args to a procedure that takes zero args (nullary)
-                 * Deprecated behaviour, may go away.
-                 */
-                g_warning ("in script, permitting too many args to %s", proc_name);
-              }
-            else
-              {
-                g_snprintf (error_str, sizeof (error_str),
-                            "in script, arguments passed to %s which takes no arguments",
-                            proc_name);
-                return script_error (sc, error_str, 0);
-              }
-          }
-          /* else both actual and formal counts zero */
+        /* Warn, but permit extra args. Will discard args from script.*/
+        g_warning ("in script, permitting too many args to %s", proc_name);
       }
-    else /* formal arg count > 0 */
+    else if (actual_arg_count < n_arg_specs)
       {
-        if ( actual_arg_count != n_arg_specs)
-          {
-            /* Not permitted. We don't say whether too few or too many. */
-            g_snprintf (error_str, sizeof (error_str),
-                        "in script, wrong number of arguments for %s (expected %d but received %d)",
-                        proc_name, n_arg_specs, actual_arg_count);
-            return script_error (sc, error_str, 0);
-          }
-        /* else matching counts of args. */
+        /* Warn, but permit too few args.
+         * Scriptfu or downstream might provide missing args.
+         * It is author friendly to continue to parse the script for type errors.
+         */
+        g_warning ("in script, permitting too few args to %s", proc_name);
       }
+    /* else equal counts of args. */
   }
 
   /*  Marshall the supplied arguments  */
@@ -632,7 +625,20 @@ script_fu_marshal_procedure_call (scheme   *sc,
       pointer     vector;   /* !!! list or vector */
       gint        j;
 
-      a = sc->vptr->pair_cdr (a);
+      consumed_arg_count++;
+
+      if (consumed_arg_count > actual_arg_count)
+        {
+          /* Exhausted supplied arguments before formal specs. */
+
+          /* Say formal type of first missing arg. */
+          g_warning ("Missing arg type: %s", g_type_name (G_PARAM_SPEC_VALUE_TYPE (arg_spec)));
+
+          /* Break loop over formal specs. Continuation is to call PDB with partial args. */
+          break;
+        }
+      else
+        a = sc->vptr->pair_cdr (a);  /* advance pointer to next arg in list. */
 
       g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (arg_spec));
 
@@ -760,10 +766,11 @@ script_fu_marshal_procedure_call (scheme   *sc,
             return script_type_error (sc, "numeric", i, proc_name);
           else
             {
-              GimpDrawable *drawable =
-                gimp_drawable_get_by_id (sc->vptr->ivalue (sc->vptr->pair_car (a)));
+              gint id = sc->vptr->ivalue (sc->vptr->pair_car (a));
 
-              g_value_set_object (&value, drawable);
+              pointer error = marshal_ID_to_drawable(sc, a, id, &value);
+              if (error)
+                return error;
             }
         }
       else if (GIMP_VALUE_HOLDS_VECTORS (&value))
@@ -1113,6 +1120,25 @@ script_fu_marshal_procedure_call (scheme   *sc,
               g_value_set_boxed (&value, &parasite);
             }
         }
+      else if (GIMP_VALUE_HOLDS_OBJECT_ARRAY (&value))
+        {
+          vector = sc->vptr->pair_car (a);
+
+          if (sc->vptr->is_vector (vector))
+            {
+              pointer error = marshal_vector_to_drawable_array (sc, vector, &value);
+              if (error)
+                return error;
+            }
+          else
+              return script_type_error (sc, "vector", i, proc_name);
+        }
+      else if (G_VALUE_TYPE (&value) == G_TYPE_FILE)
+        {
+          if (! sc->vptr->is_string (sc->vptr->pair_car (a)))
+            return script_type_error (sc, "string for path", i, proc_name);
+          marshal_path_string_to_gfile (sc, a, &value);
+        }
       else if (G_VALUE_TYPE (&value) == GIMP_TYPE_PDB_STATUS_TYPE)
         {
           /* A PDB procedure signature wrongly requires a status. */
@@ -1215,8 +1241,31 @@ script_fu_marshal_procedure_call (scheme   *sc,
 
           g_debug ("Return value %d is type %s", i+1, G_VALUE_TYPE_NAME (value));
 
-          if (G_VALUE_HOLDS_OBJECT (value))
+          /* Order is important. GFile before GIMP objects. */
+          if (G_VALUE_TYPE (value) == G_TYPE_FILE)
+            {
+              gchar *parsed_filepath = marshal_returned_gfile_to_string (value);
+
+              if (parsed_filepath)
+                {
+                  g_debug ("PDB procedure returned GFile '%s'", parsed_filepath);
+                  /* copy string into interpreter state. */
+                  return_val = sc->vptr->cons (sc, sc->vptr->mk_string (sc, parsed_filepath), return_val);
+                  g_free (parsed_filepath);
+                }
+              else
+                {
+                  g_warning ("PDB procedure failed to return a valid GFile");
+                  return_val = sc->vptr->cons (sc, sc->vptr->mk_string (sc, ""), return_val);
+                }
+              /* Ensure return_val holds a string, possibly empty. */
+            }
+          else if (G_VALUE_HOLDS_OBJECT (value))
             {
+              /* G_VALUE_HOLDS_OBJECT only ensures value derives from GObject.
+               * Could be a GIMP or a GLib type.
+               * Here we handle GIMP types, which all have an id property.
+               */
               GObject *object = g_value_get_object (value);
               gint     id     = -1;
 
@@ -1227,8 +1276,11 @@ script_fu_marshal_procedure_call (scheme   *sc,
               /* id is -1 when the gvalue had no GObject*,
                * or the referenced object had no property "id".
                * This can be an undetected fault in the called procedure.
-               * But it is not an error in the script.
+               * It is not necessarily an error in the script.
                */
+              if (id == -1)
+                g_warning("PDB procedure returned NULL GIMP object or non-GIMP object.");
+
               g_debug ("PDB procedure returned object ID: %i", id);
 
               /* Scriptfu stores object IDs as int. */
@@ -1433,6 +1485,11 @@ script_fu_marshal_procedure_call (scheme   *sc,
                   g_debug ("data '%.*s'", v->size, (gchar *) v->data);
                 }
             }
+          else if (GIMP_VALUE_HOLDS_OBJECT_ARRAY (value))
+            {
+              pointer vector = marshal_returned_object_array_to_vector (sc, value);
+              return_val = sc->vptr->cons (sc, vector, return_val);
+            }
           else if (G_VALUE_TYPE (&value) == GIMP_TYPE_PDB_STATUS_TYPE)
             {
               /* Called procedure implemented incorrectly. */


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