From d269e93d9344162bdbaf9830d6aa34c108a77a9b Mon Sep 17 00:00:00 2001
From: Jonathan Wilkes <jon.w.wilkes@gmail.com>
Date: Wed, 4 Jan 2017 22:44:29 -0500
Subject: [PATCH] fix bugs with grid, add a rudimentary external dialog
 interface for it and other externals

---
 externals/unauthorized/grid/grid.c |  90 ++++++++++---
 pd/nw/dialog_external.html         | 204 +++++++++++++++++++++++++++++
 pd/nw/pdgui.js                     |  77 +++++++----
 3 files changed, 325 insertions(+), 46 deletions(-)
 create mode 100644 pd/nw/dialog_external.html

diff --git a/externals/unauthorized/grid/grid.c b/externals/unauthorized/grid/grid.c
index ac814f953..2349ce64d 100644
--- a/externals/unauthorized/grid/grid.c
+++ b/externals/unauthorized/grid/grid.c
@@ -99,7 +99,8 @@ static void grid_draw_update(t_grid *x, t_glist *glist)
     // }
 }
 
-static void grid_draw_new(t_grid *x, t_glist *glist)
+static void grid_draw_select(t_grid* x, t_glist* glist);
+static void grid_draw_configure(t_grid *x, t_glist *glist)
 {
     t_canvas *canvas=glist_getcanvas(glist);
     //GRID_SYS_VGUI8(".x%lx.c create rectangle %d %d %d %d "
@@ -148,14 +149,7 @@ static void grid_draw_new(t_grid *x, t_glist *glist)
     //}
 
     // gui_gobj_new, "xx type text_xpix text_ypix istoplevel"
-    gui_vmess("gui_gobj_new", "xxsiii",
-        canvas,
-        x,
-        "obj",
-        text_xpix(&x->x_obj, glist),
-        text_ypix(&x->x_obj, glist),
-        glist_istoplevel(glist));
-    gui_vmess("gui_grid_draw_bg", "xxiisiii",
+    gui_vmess("gui_configure_grid", "xxiisiii",
         canvas,
         x,
         x->x_width,
@@ -164,9 +158,23 @@ static void grid_draw_new(t_grid *x, t_glist *glist)
         x->x_grid,
         x->x_xlines,
         x->x_ylines);
+    if (glist_isselected(glist, &x->x_obj))
+        grid_draw_select(x, glist);
     canvas_fixlinesfor( canvas, (t_text*)x );
 }
 
+static void grid_draw_new(t_grid *x, t_glist *glist)
+{
+    t_canvas *canvas=glist_getcanvas(glist);
+    gui_vmess("gui_grid_new", "xxiii",
+        canvas,
+        x,
+        text_xpix(&x->x_obj, glist),
+        text_ypix(&x->x_obj, glist),
+        glist_istoplevel(glist));
+    grid_draw_configure(x, glist);
+}
+
 static void grid_draw_move(t_grid *x, t_glist *glist)
 {
     t_canvas *canvas=glist_getcanvas(glist);
@@ -323,14 +331,36 @@ static void grid_save(t_gobj *z, t_binbuf *b)
 static void grid_properties(t_gobj *z, t_glist *owner)
 {
     char buf[800];
+    char *gfx_tag;
     t_grid *x=(t_grid *)z;
 
-    sprintf(buf, "pdtk_grid_dialog %%s %s %d %.2f %.2f %d %.2f %.2f %d %.2f %.2f %d %d\n",
-        x->x_name->s_name, x->x_width, x->x_min, x->x_max, x->x_height, 
-        x->y_min, x->y_max, x->x_grid, x->x_xstep, x->x_ystep,
-        x->x_xlines, x->x_ylines );
+    //sprintf(buf, "pdtk_grid_dialog %%s %s %d %.2f %.2f %d %.2f %.2f %d %.2f %.2f %d %d\n",
+    //    x->x_name->s_name, x->x_width, x->x_min, x->x_max, x->x_height, 
+    //    x->y_min, x->y_max, x->x_grid, x->x_xstep, x->x_ystep,
+    //    x->x_xlines, x->x_ylines );
         // post("grid_properties : %s", buf );
-    gfxstub_new(&x->x_obj.ob_pd, x, buf);
+    //gfxstub_new(&x->x_obj.ob_pd, x, buf);
+    gfx_tag = gfxstub_new2(&x->x_obj.ob_pd, x);
+
+    gui_start_vmess("gui_external_dialog", "ss", gfx_tag, "grid");
+    gui_start_array();
+
+    gui_s("receive_symbol"); gui_s(x->x_name->s_name);
+    gui_s("width"); gui_i(x->x_width);
+    gui_s("min"); gui_f(x->x_min);
+    gui_s("max"); gui_f(x->x_max);
+    gui_s("height"); gui_i(x->x_height);
+    gui_s("y-min"); gui_f(x->y_min);
+    gui_s("y-max"); gui_f(x->y_max);
+    gui_s("grid_toggle"); gui_i(x->x_grid);
+    gui_s("x-steps"); gui_f(x->x_xstep);
+    gui_s("y-steps"); gui_f(x->x_ystep);
+    gui_s("x-lines"); gui_i(x->x_xlines);
+    gui_s("y-lines"); gui_i(x->x_ylines);
+
+    gui_end_array();
+    gui_end_vmess();
+
 }
 
 static void grid_select(t_gobj *z, t_glist *glist, int selected)
@@ -390,8 +420,9 @@ static void grid_dialog(t_grid *x, t_symbol *s, int argc, t_atom *argv)
     x->x_ystep = argv[9].a_w.w_float;
     x->x_xlines = argv[10].a_w.w_float;
     x->x_ylines = argv[11].a_w.w_float;
-    grid_draw_erase(x, x->x_glist);
-    grid_draw_new(x, x->x_glist);
+    //grid_draw_erase(x, x->x_glist);
+    //grid_draw_new(x, x->x_glist);
+    grid_draw_configure(x, x->x_glist);
 }
 
 static void grid_delete(t_gobj *z, t_glist *glist)
@@ -418,6 +449,26 @@ static void grid_displace(t_gobj *z, t_glist *glist, int dx, int dy)
     }
 }
 
+static void grid_displace_wtag(t_gobj *z, t_glist *glist, int dx, int dy)
+{
+    t_grid *x = (t_grid *)z;
+    int xold = text_xpix(&x->x_obj, glist);
+    int yold = text_ypix(&x->x_obj, glist);
+
+    // post( "grid_displace dx=%d dy=%d", dx, dy );
+
+    x->x_obj.te_xpix += dx;
+    x->x_current += dx;
+    x->x_obj.te_ypix += dy;
+    x->y_current += dy;
+    if (xold != text_xpix(&x->x_obj, glist) || yold != text_ypix(&x->x_obj, glist))
+    {
+	//grid_draw_move(x, x->x_glist);
+        canvas_fixlinesfor(glist, (t_text *)z);
+    }
+}
+
+
 static void grid_motion(t_grid *x, t_floatarg dx, t_floatarg dy)
 {
     int xold = x->x_current;
@@ -508,8 +559,9 @@ static void grid_new_color(t_grid *x, t_floatarg color1,
         sprintf(col3,"%X",(int) color3);
     sprintf( x->x_bgcolor, "#%s%s%s", col1, col2, col3);
 
-    grid_draw_erase(x, x->x_glist);
-    grid_draw_new(x, x->x_glist);
+    //grid_draw_erase(x, x->x_glist);
+    //grid_draw_new(x, x->x_glist);
+    grid_draw_configure(x, x->x_glist);
 }
 
 static void grid_values(t_grid* x, t_floatarg xvalue, t_floatarg yvalue)
@@ -724,6 +776,7 @@ static t_grid *grid_new(t_symbol *s, int argc, t_atom *argv)
 static void grid_free(t_grid *x)
 {
     pd_unbind(&x->x_obj.ob_pd, x->x_name);
+    gfxstub_deleteforkey(x);
 }
 
 void grid_setup(void)
@@ -759,6 +812,7 @@ void grid_setup(void)
     grid_widgetbehavior.w_deletefn =     grid_delete;
     grid_widgetbehavior.w_visfn =        grid_vis;
     grid_widgetbehavior.w_clickfn =      grid_click;
+    grid_widgetbehavior.w_displacefnwtag = grid_displace_wtag;
 
 #if PD_MINOR_VERSION >= 37
     class_setpropertiesfn(grid_class, grid_properties);
diff --git a/pd/nw/dialog_external.html b/pd/nw/dialog_external.html
new file mode 100644
index 000000000..63b31d643
--- /dev/null
+++ b/pd/nw/dialog_external.html
@@ -0,0 +1,204 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <link id="page_style" rel="stylesheet" type="text/css" href="css/default.css">
+  </head>
+  <body class="dialog_body">
+    <div class="container">
+    <form> 
+      <fieldset> 
+        <legend></legend> 
+      </fieldset> 
+    <div class="submit_buttons">
+      <button type="button"
+              onClick="ok();"
+              id="ok_button"
+              data-i18n="[title]iem.prop.ok_tt">
+        <span data-i18n="iem.prop.ok"></span>
+      </button>
+      <button type="button"
+              onClick="apply();"
+              id="apply_button"
+              data-i18n="[title]iem.prop.apply_tt">
+        <span data-i18n="iem.prop.apply"></span>
+      </button>
+      <button type="button"
+              onClick="cancel(true);"
+              data-i18n="[title]iem.prop.cancel_tt">
+        <span data-i18n="iem.prop.cancel"></span>
+      </button>
+    </div>
+  </form> 
+  </div>      
+  <script>
+"use strict";
+var gui = require("nw.gui");
+var pdgui = require("./pdgui.js");
+
+// For translations
+var l = pdgui.get_local_string;
+
+pdgui.skin.apply(window);
+
+var pd_object_callback;
+var old_properties; // Original values in case the user wants to cancel changes
+var new_properties; // Updated values
+
+// Grab focus for one of the buttons
+function focus_button(id) {
+    document.getElementById(id).focus();
+}
+
+function send_props_to_pd(attr_array) {
+    var values = [];
+    // Todo: parse symbols the same way we do for iemguis before
+    // sending them back to Pd. This might require some fanagling on
+    // the pd side of things...
+    attr_array.forEach(function(elem) {
+        values.push(elem.value);
+    });
+    pdgui.pdsend(pd_object_callback, "dialog", values.join(" "));
+}
+
+function apply() {
+    focus_button("apply_button");
+    send_props_to_pd(new_properties); 
+}
+
+function cancel() {
+    send_props_to_pd(old_properties);
+    pdgui.pdsend(pd_object_callback, "cancel");
+}
+
+function ok() {
+    // Focus button so that the "change" event triggers
+    // for the form before sending values to Pd
+    focus_button("ok_button");
+    send_props_to_pd(new_properties);
+    pdgui.pdsend(pd_object_callback, "cancel");
+}
+
+// turn a ["name", value, etc.] array
+// into an array of objects where each object contains
+// name: name for the input element
+// type: type of element (number, string, toggle)
+// label: string label (basically the name with underscores turned to spaces)
+// value: a value for the input
+function parse_attrs(attrs) {
+    var ret = [],
+        elem;
+    attrs.forEach(function(token, i) {
+        if (i % 2 === 0) {
+            elem = {};
+            token = token.split("_");
+            if (token.length > 1) {
+                elem.type = token[token.length - 1];
+                if (elem.type !== "symbol" &&
+                    elem.type !== "toggle") {
+                    // no suffix defaults to "number"
+                    elem.type = "number";
+                } else {
+                    // remove the type suffix
+                    token = token.slice(0, -1);
+                }
+            } else {
+                elem.type = "number";
+            }
+            elem.name = token.join("_");
+            elem.label = token.join(" ");
+        } else {
+            elem.value = token;
+            // now push the object onto the array
+            ret.push(elem);
+        }
+    });
+    return ret;
+}
+
+// This gets called from the nw_create_window function in index.html
+// It provides us with our window id from the C side.  Once we have it
+// we can create the menu and register event callbacks
+function register_window_id(gfxstub, args) {
+    var external_name = args.name,
+        array_of_objects;
+    pd_object_callback = gfxstub;
+    array_of_objects = parse_attrs(args.attributes);
+    // Store our array for later use
+    new_properties = array_of_objects;
+    // Also make a copy in the case that the user cancels this dialog
+    old_properties = JSON.parse(JSON.stringify(array_of_objects));    
+
+    add_events(gfxstub);
+    // not sure that we need this for properties windows
+    //pdgui.canvas_map(gfxstub);
+    translate_form();
+
+    build_form(external_name, array_of_objects);
+
+    // We don't turn on rendering of the "container" div until
+    // We've finished displaying all the spans and populating the
+    // labels and form elements.  That makes it more efficient and
+    // snappier, at least on older machines.
+    document.getElementsByClassName("container")[0]
+        .style.setProperty("display", "inline");
+}
+
+// Stop-gap translator
+function translate_form() {
+    var elements = document.querySelectorAll("[data-i18n]"),
+        data,
+        i;
+    for (i = 0; i < elements.length; i++) {
+        data = elements[i].dataset.i18n;
+        if (data.slice(0, 7) === "[title]") {
+            elements[i].title = l(data.slice(7));
+        } else {
+            elements[i].textContent = l(data);
+        }
+    }
+}
+
+function get_input_type(t) {
+    return t === "symbol" ? "text" :
+           t === "number" ? "text" : 
+           t === "toggle" ? "checkbox":
+                            "text";
+}
+
+function build_form(external_name, array_of_objects) {
+    var fieldset = document.querySelector("fieldset");
+    document.querySelector("legend").textContent = external_name;
+    array_of_objects.forEach(function(elem) {
+        var input_elem = document.createElement("input"),
+            label = document.createElement("label");
+        input_elem.type = get_input_type(elem.type);
+        if (input_elem.type === "checkbox") {
+            input_elem.checked = elem.value !== 0;
+            input_elem.onchange = function() {
+                elem.value = input_elem.checked ? 1 : 0;
+            };
+        } else {
+            input_elem.value = elem.value;
+            input_elem.onchange = function() {
+                elem.value = input_elem.value;
+            }
+        }
+        label.textContent = elem.label;
+        label.appendChild(input_elem);
+        fieldset.appendChild(label);
+        // stop-gap until we make this prettier through css: insert a break
+        fieldset.appendChild(document.createElement("br"));
+    });
+}
+
+function add_events(name) {
+    // closing the Window
+    gui.Window.get().on("close", function () {
+        // this needs to do whatever the "cancel" button does
+        cancel();
+    });
+    pdgui.dialog_bindings(name);
+}
+  </script>
+  </body>
+</html>
diff --git a/pd/nw/pdgui.js b/pd/nw/pdgui.js
index fd08f341e..9f3d3d930 100644
--- a/pd/nw/pdgui.js
+++ b/pd/nw/pdgui.js
@@ -3596,7 +3596,7 @@ function gui_scope_displace(cid, tag, dx, dy) {
 
 // unauthorized/grid
 
-function make_grid_data(w, h, x_l, y_l) {
+function get_grid_data(w, h, x_l, y_l) {
     var d, x, y, offset;
     d = [];
     offset = Math.floor(w / x_l);
@@ -3609,7 +3609,7 @@ function make_grid_data(w, h, x_l, y_l) {
     }
     offset = Math.floor(h / y_l);
     if (offset > 0) {
-        for (y = 0; y < w; y += offset) {
+        for (y = 0; y < h; y += offset) {
             d = d.concat(["M", 0, y, w, y]); // horizontal line
         }
     } else {
@@ -3618,23 +3618,17 @@ function make_grid_data(w, h, x_l, y_l) {
     return d.join(" ");
 }
 
-function gui_grid_draw_bg(cid, tag, w, h, bg_color, has_grid, x_l, y_l) {
+function gui_configure_grid(cid, tag, w, h, bg_color, has_grid, x_l, y_l) {
     var g = get_gobj(cid, tag),
-        border, // for inheriting gui preset colors
-        bg,
-        out_0,
-        out_1,
-        grid,
+    // configure each element in the grid
         grid_d_string,
-        point,
         point_size = 5;
-    bg = create_item(cid, "rect", {
+    configure_item(g.querySelector(".bg"), {
         width: w,
         height: h,
         fill: bg_color,
-        class: "bg"
     });
-    border = create_item(cid, "path", {
+    configure_item(g.querySelector(".border"), {
         d: ["M", 0, 0, w, 0,
             "M", 0, h, w, h,
             "M", 0, 0, 0, h,
@@ -3645,18 +3639,16 @@ function gui_grid_draw_bg(cid, tag, w, h, bg_color, has_grid, x_l, y_l) {
         fill: "none",
         stroke: "black",
         "stroke-width": 1,
-        class: "border" // now we can inherit the css border styles
     });
-    out_0 = create_item(cid, "rect", {
+    configure_item(g.querySelector(".out_0"), {
         y: h + 1,
         width: 7,
         height: 1,
         fill: "none",
         stroke: "black",
         "stroke-width": 1,
-        class: "out_0"
     });
-    out_1 = create_item(cid, "rect", {
+    configure_item(g.querySelector(".out_1"), {
         x: w - 7,
         y: h + 1,
         width: 7,
@@ -3664,26 +3656,45 @@ function gui_grid_draw_bg(cid, tag, w, h, bg_color, has_grid, x_l, y_l) {
         fill: "none",
         stroke: "black",
         "stroke-width": 1,
-        class: "out_1"
     });
-    if (has_grid === 1) {
-        grid_d_string = make_grid_data(w, h, x_l, y_l);
-        grid = create_item(cid, "path", {
-            d: grid_d_string,
-            stroke: "white",
-            "stroke-width": 1,
-            class: "grid"
-        });
-    }
-    point = create_item(cid, "rect", {
+
+    grid_d_string = !!has_grid ? get_grid_data(w, h, x_l, y_l) : "";
+    configure_item(g.querySelector(".grid"), {
+        d: grid_d_string,
+        stroke: "white",
+        "stroke-width": 1,
+    });
+
+    configure_item(g.querySelector(".point"), {
         style: "visibility: none;",
         width: 5,
         height: 5,
         fill: "#ff0000",
         stroke: "black",
         "stroke-width": 1,
-        class: "point"
     });
+}
+
+function gui_grid_new(cid, tag, x, y, is_toplevel) {
+    var g = gui_gobj_new(cid, tag, "obj", x, y, is_toplevel),
+        bg = create_item(cid, "rect", {
+            class: "bg"
+        }),
+        border = create_item(cid, "path", {
+            class: "border" // now we can inherit the css border styles
+        }),
+        out_0 = create_item(cid, "rect", {
+            class: "out_0"
+        }),
+        out_1 = create_item(cid, "rect", {
+            class: "out_1"
+        }),
+        grid = create_item(cid, "path", {
+            class: "grid"
+        }),
+        point = create_item(cid, "rect", {
+            class: "point"
+        });
     g.appendChild(bg);
     g.appendChild(out_0);
     g.appendChild(out_1);
@@ -3692,6 +3703,7 @@ function gui_grid_draw_bg(cid, tag, w, h, bg_color, has_grid, x_l, y_l) {
     g.appendChild(border);
 }
 
+
 function gui_grid_point(cid, tag, x, y) {
     var g = get_gobj(cid, tag),
         p = g.querySelector(".point");
@@ -4331,6 +4343,15 @@ function gui_font_dialog(cid, gfxstub, font_size) {
         attrs);
 }
 
+function gui_external_dialog(did, external_name, attr_array) {
+    create_window(did, "external", 265, 450,
+        popup_coords[2], popup_coords[3],
+        {
+            name: external_name,
+            attributes: attr_array
+        });
+}
+
 // Global settings
 
 function gui_pd_dsp(state) {
-- 
GitLab