[gimp] plug-ins: nearly-complete goat-exercise in JavaScript.



commit 88ff7ca2580aed440a797420ac503781bc477bc0
Author: Jehan <jehan girinstud io>
Date:   Thu Aug 8 11:07:49 2019 +0200

    plug-ins: nearly-complete goat-exercise in JavaScript.
    
    Only missing parts are:
    - localization.
    - help function and id unset.
    - gimp_procedure_set_icon() should probably be reviewed for proper
      introspection.
    - Finally there is a String() conversion which raises a warning, but I
      couldn't find the proper standard replacement code.
    Other than this, the goat-exercise was totally reproduced in JavaScript.

 plug-ins/goat-exercises/Makefile.am          |   8 +
 plug-ins/goat-exercises/goat-exercise-gjs.js | 217 +++++++++++++++++++++++++++
 plug-ins/goat-exercises/hello-world.js       |  69 ---------
 3 files changed, 225 insertions(+), 69 deletions(-)
---
diff --git a/plug-ins/goat-exercises/Makefile.am b/plug-ins/goat-exercises/Makefile.am
index 3a7394d885..f91c138d27 100644
--- a/plug-ins/goat-exercises/Makefile.am
+++ b/plug-ins/goat-exercises/Makefile.am
@@ -33,6 +33,9 @@ AM_CPPFLAGS = \
        $(GEGL_CFLAGS)  \
        -I$(includedir)
 
+
+# C version.
+
 goat_exercise_libexecdir = $(gimpplugindir)/plug-ins/goat-exercise
 
 goat_exercise_libexec_PROGRAMS = goat-exercise
@@ -56,3 +59,8 @@ goat_exercise_LDADD = \
        $(RT_LIBS)              \
        $(INTLLIBS)             \
        $(goat_exercise_RC)
+
+# Javascript (GJS) version.
+
+goat_exercise_gjsdir = $(gimpplugindir)/plug-ins/goat-exercise-gjs
+goat_exercise_gjs_SCRIPTS = goat-exercise-gjs.js
diff --git a/plug-ins/goat-exercises/goat-exercise-gjs.js b/plug-ins/goat-exercises/goat-exercise-gjs.js
new file mode 100755
index 0000000000..81e12d6734
--- /dev/null
+++ b/plug-ins/goat-exercises/goat-exercise-gjs.js
@@ -0,0 +1,217 @@
+#!/usr/bin/gjs
+
+/* GIMP - The GNU Image Manipulation Program
+ * Copyright (C) 1995 Spencer Kimball and Peter Mattis
+ *
+ * hello-world.js
+ * Copyright (C) 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
+ * 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/>.
+ */
+
+const System = imports.system
+
+imports.gi.versions.Gimp = '3.0';
+const Gimp = imports.gi.Gimp;
+imports.gi.versions.Gegl = '0.4';
+const Gegl = imports.gi.Gegl;
+imports.gi.versions.Gtk = '3.0';
+const Gtk = imports.gi.Gtk;
+imports.gi.versions.Gdk = '3.0';
+const Gdk = imports.gi.Gdk;
+
+const GLib = imports.gi.GLib;
+const GObject = imports.gi.GObject;
+const Gio = imports.gi.Gio;
+
+/* Trick coming from extensionUtils.js in gnome-shell.
+ * See in particular implementation of getCurrentExtension().
+ */
+function getCurrentPath() {
+    let stackLine = (new Error()).stack.split('\n')[1];
+
+    /* In GIMP case, I saw strings such as:
+     *    run@/home/jehan/.config/GIMP/2.99/plug-ins/hello-world/hello-world.js:150:24
+     * Let's make sure all cases are handled.
+     */
+    let match = new RegExp('@(.+\\.js)(:\\d+)+').exec(stackLine);
+    if (! match)
+        return null;
+
+    return match[1];
+}
+
+/* gjs's ARGV is not C-style. We must add the program name as first
+ * value.
+ */
+ARGV.unshift(System.programInvocationName);
+
+let url = "https://gitlab.gnome.org/GNOME/gimp/blob/master/plug-ins/goat-exercises/goat-exercise-gjs.js";;
+
+var Goat = GObject.registerClass({
+    GTypeName: 'Goat',
+}, class Goat extends Gimp.PlugIn {
+
+    vfunc_query_procedures() {
+        return ["plug-in-goat-exercise-gjs"];
+    }
+
+    vfunc_create_procedure(name) {
+        let procedure = Gimp.Procedure.new(this, name, Gimp.PDBProcType.PLUGIN, this.run);
+
+        procedure.set_image_types("RGB*, INDEXED*, GRAY*");
+        procedure.set_menu_label("Exercise a JavaScript goat");
+        procedure.set_documentation("Exercise a goat in the JavaScript language (GJS)",
+                                    "Takes a goat for a walk in Javascript with the GJS interpreter",
+                                    "");
+        procedure.add_menu_path ('<Image>/Filters/Development/Goat exercises/');
+        procedure.set_attribution("Jehan", "Jehan", "2019");
+        /* TODO */
+        /*procedure.set_icon (Gimp.IconType.ICON_NAME, Gimp.ICON_GEGL);*/
+
+
+        procedure.add_argument(GObject.param_spec_enum("run-mode",
+                                                       "Run mode",
+                                                       "The run mode",
+                                                       Gimp.RunMode.$gtype,
+                                                       Gimp.RunMode.NONINTERACTIVE,
+                                                       GObject.ParamFlags.READWRITE));
+        procedure.add_argument(Gimp.param_spec_image_id ("image",
+                                                         "Image",
+                                                         "The input image",
+                                                         false,
+                                                         GObject.ParamFlags.READWRITE));
+        procedure.add_argument(Gimp.param_spec_drawable_id ("drawable",
+                                                            "Drawable",
+                                                            "The input drawable",
+                                                            false,
+                                                            GObject.ParamFlags.READWRITE));
+
+        return procedure;
+    }
+
+    run(procedure, args, data) {
+        /* TODO: localization. */
+        let run_mode = args.index(0);
+        if (run_mode == Gimp.RunMode.INTERACTIVE) {
+            Gimp.ui_init("goat-exercise-gjs", false);
+            /* TODO: help function and ID. */
+            let dialog = new Gimp.Dialog({
+              title: "Exercise a goat (JavaScript)",
+              role: "goat-exercise-JavaScript",
+              use_header_bar: true,
+            });
+            dialog.add_button("_Cancel", Gtk.ResponseType.CANCEL);
+            dialog.add_button("_Source", Gtk.ResponseType.APPLY);
+            dialog.add_button("_OK", Gtk.ResponseType.OK);
+
+            let geometry = new Gdk.Geometry();
+            geometry.min_aspect = 0.5;
+            geometry.max_aspect = 1.0;
+            dialog.set_geometry_hints (null, geometry, Gdk.WindowHints.ASPECT);
+
+            let box = new Gtk.Box({
+              orientation: Gtk.Orientation.VERTICAL,
+              spacing: 2
+            });
+            dialog.get_content_area().add(box);
+            box.show();
+
+            let lang = "JavaScript (GJS)";
+            /* XXX Can't we have nicer-looking multiline strings and
+             * also in printf format like in C for sharing the same
+             * string in localization?
+             */
+            let head_text = `This plug-in is an exercise in '${lang}' to demo plug-in creation.\n` +
+                            `Check out the last version of the source code online by clicking the \"Source\" 
button.`;
+
+            let label = new Gtk.Label({label:head_text});
+            box.pack_start(label, false, false, 1);
+            label.show();
+
+            let path = getCurrentPath();
+            if (path) {
+                let scrolled = new Gtk.ScrolledWindow();
+                scrolled.set_vexpand (true);
+                box.pack_start(scrolled, true, true, 1);
+                scrolled.show();
+
+                let contents = String(GLib.file_get_contents(path)[1]);
+                let view = new Gtk.TextView();
+                view.set_wrap_mode(Gtk.WrapMode.WORD);
+                view.set_editable(false);
+                let buffer = view.get_buffer();
+                buffer.set_text(contents, -1);
+                scrolled.add(view);
+                view.show();
+            }
+
+            while (true) {
+                let response = dialog.run();
+
+                if (response == Gtk.ResponseType.OK) {
+                    dialog.destroy();
+                    break;
+                }
+                else if (response == Gtk.ResponseType.APPLY) {
+                    Gio.app_info_launch_default_for_uri(url, null);
+                    continue;
+                }
+                else { /* CANCEL, CLOSE, DELETE_EVENT */
+                    dialog.destroy();
+                    return procedure.new_return_values(Gimp.PDBStatusType.CANCEL, null)
+                }
+            }
+        }
+
+        let drawable_id = args.index(2);
+        let [ success, x, y, width, height ] = Gimp.drawable_mask_intersect (drawable_id);
+        if (success) {
+            Gegl.init(null);
+
+            let buffer = Gimp.drawable_get_buffer (drawable_id);
+            let shadow_buffer = Gimp.drawable_get_shadow_buffer (drawable_id);
+
+            let graph = new Gegl.Node();
+            let input = graph.create_child("gegl:buffer-source");
+            input.set_property("buffer", buffer);
+            let invert = graph.create_child("gegl:invert");
+            let output = graph.create_child("gegl:write-buffer");
+            output.set_property("buffer", shadow_buffer);
+            input.link(invert);
+            invert.link(output);
+            output.process();
+
+            /* This is extremely important in bindings, since we don't
+             * unref buffers. If we don't explicitly flush a buffer, we
+             * may left hanging forever. This step is usually done
+             * during an unref().
+             */
+            shadow_buffer.flush();
+
+            Gimp.drawable_merge_shadow(drawable_id, true);
+            Gimp.drawable_update(drawable_id, x, y, width, height);
+            Gimp.displays_flush();
+        }
+        else {
+            let error = GLib.Error.new_literal(GLib.quark_from_string("goat-error-quark"), 0,
+                                               "No pixels to process in the selected area.");
+            return procedure.new_return_values(Gimp.PDBStatusType.CALLING_ERROR, error);
+        }
+
+        return procedure.new_return_values(Gimp.PDBStatusType.SUCCESS, null);
+    }
+});
+
+Gimp.main(Goat.$gtype, ARGV);


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