From c593395ff914086c0ea842f5316904890a753fca Mon Sep 17 00:00:00 2001
From: Jonathan Wilkes <jon.w.wilkes@gmail.com>
Date: Wed, 31 Aug 2016 23:58:45 -0400
Subject: [PATCH] first draft of scalar properties dialog

---
 pd/nw/dialog_data.html | 358 +++++++++++++++++++++++++++++++++++++++++
 pd/nw/pdgui.js         |   6 +
 pd/src/g_scalar.c      |   8 +-
 3 files changed, 367 insertions(+), 5 deletions(-)
 create mode 100644 pd/nw/dialog_data.html

diff --git a/pd/nw/dialog_data.html b/pd/nw/dialog_data.html
new file mode 100644
index 000000000..007bcd591
--- /dev/null
+++ b/pd/nw/dialog_data.html
@@ -0,0 +1,358 @@
+<!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 id="data_container"> 
+        <legend id="legend"></legend> 
+      </fieldset> 
+
+    <div class="submit_buttons">
+      <button type="button" onClick="ok()" data-i18n="[title]iem.prop.ok_tt">
+        <span data-i18n="iem.prop.ok"></span>
+      </button>
+      <button type="button" onClick="cancel()" 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 template_string;
+var canvas;
+
+function apply() {
+    var data_string = "",
+        scalar_inputs = document.querySelectorAll("input.scalar_value"),
+        vector_textarea = document.querySelector("textarea"),
+        i,
+        datum;
+    pdgui.post("we're applying changes! textarea is " + vector_textarea);
+    // start with the template header
+    data_string += template_string;
+    // add the data separator
+    data_string += "\n;\n;\n"
+    // now fetch the template name from the label as the first token for
+    // line with the scalar data
+    data_string += document.getElementById("legend").textContent;
+    for (i = 0; i < scalar_inputs.length; i++) {
+        data_string += " ";
+        if (scalar_inputs[i].title === "float") {
+            data_string += Number(scalar_inputs[i].value);
+        } else {
+            data_string += scalar_inputs[i].value;
+        }
+    }
+    // add terminating semicolon and newline for the scalar value line
+    data_string += ";\n";
+    // now tack on any vector data we may have
+    if (vector_textarea) {
+        data_string += vector_textarea.textContent;
+        // strip off the trailing semicolon. Otherwise Pd will crash...
+        data_string = data_string.slice(0, -1);
+    }
+
+    // trim the string... otherwise we could append an extra semicolon and
+    // crash Pd! So brittle...
+    data_string = data_string.trim();
+
+    data_string.split("\n").forEach(function (line) {
+        pdgui.pdsend(pd_object_callback, "data", line);
+    });
+
+    pdgui.post("outgoing data string is:");
+    pdgui.post(data_string);
+
+    pdgui.pdsend(pd_object_callback, "end");
+}
+
+function cancel() {
+    //window.close(true);
+    pdgui.pdsend(pd_object_callback, "cancel");
+}
+
+function ok() {
+    apply();
+    cancel();
+}
+
+function change_size() {
+    pdgui.post("changing the size");
+    apply();
+}
+
+function parse_template_string(t_string) {
+    // slice off the leading "data;" line
+    t_string = t_string.slice(t_string.indexOf("\n") + 1);
+    var ret = [];
+    t_string.split("\n;\n").forEach(function (t) {
+        var t_object = {},
+            t_lines = t.split("\n");
+        // template name from "template foo;"
+        t_object.template = t_lines.shift().split(" ")[1].slice(0, -1); 
+        t_object.fields = [];
+        t_lines.forEach(function (line) {
+            // remove trailing ";"
+            line = line.slice(0, -1);
+            var tokens = line.split(" "),
+                field = {};
+            if (tokens[0] === "float" || tokens[0] === "symbol") {
+                field["type"] = tokens[0];
+                field["var"] = tokens[1];
+                t_object.fields.push(field);
+            }
+        });
+        t_lines.forEach(function (line) {
+            // remove trailing ";"
+            line = line.slice(0, -1);
+            var tokens = line.split(" "),
+                field = {};
+            if (tokens[0] === "array" ||
+                tokens[0] === "canvas" ||
+                tokens[0] === "list") {
+                field["type"] = tokens[0];
+                field["var"] = tokens[1];
+                field["template"] = tokens[2];
+                t_object.fields.push(field);
+            }
+        });
+        ret.push(t_object);
+    });
+    return ret;
+}
+function add_break(container) {
+        var br = document.createElement("br");
+        br.style.setProperty("clear", "both");
+        container.appendChild(br);
+}
+
+function toggle_vector_editing() {
+    var state = document.getElementById("vector_edit_checkbox").checked;
+pdgui.post("checked is " + state);
+    document.getElementById("vector_textarea").disabled = state ? false : true;
+}
+
+function add_textarea_input() {
+    var label = document.createElement("label"),
+        textarea = document.createElement("textarea"),
+        check_label = document.createElement("label"),
+        check = document.createElement("input"),
+        outer_container = document.getElementById("data_container"),
+        inner_container = document.createElement("div"),
+        br = document.createElement("br");
+
+    label.textContent = "vector fields";
+
+    label.style.setProperty("display", "block");
+    label.style.setProperty("text-align", "left");
+    textarea.style.setProperty("display", "block");
+
+    textarea.setAttribute("id", "vector_textarea");
+    textarea.setAttribute("rows", "4");
+    textarea.setAttribute("col", "5");
+    textarea.style.setProperty("width", "11.3em");
+    textarea.disabled = true;
+
+    check.type = "checkbox";
+    check.id = "vector_edit_checkbox";
+    check.onclick = toggle_vector_editing;
+    check_label.appendChild(check);
+    check_label.appendChild(document.createTextNode("edit vector data"));
+
+    add_break(outer_container);
+
+    inner_container.appendChild(label);
+    inner_container.appendChild(textarea);
+    outer_container.appendChild(inner_container);
+    outer_container.appendChild(check_label);
+}
+
+function add_text_input(field, left_column, first_row) {
+    var label = document.createElement("label"),
+        text_input = document.createElement("input"),
+        outer_container = document.getElementById("data_container"),
+        inner_container = document.createElement("div"),
+        br;
+    inner_container.style.setProperty("float", "left");
+    // For floats, we do two inputs per line with a right margin for
+    // the left column. For symbols, the input takes up the whole line
+    if (left_column && field.type !== "symbol") {
+        inner_container.style.setProperty("margin-right", "1em");
+    }
+    label.textContent = field["var"];
+    text_input.type = "text";
+    text_input.classList.add("scalar_value");
+    text_input.title = field["type"];
+
+    // Set styles-- should be done in css, but quick-and-dirty for now
+    label.style.setProperty("display", "block");
+    // This is in opposition to iemgui dialog. But the iemgui dialog should
+    // be changed to have labels at the top of the input. It makes everything
+    // line up better...
+    label.style.setProperty("text-align", "left");
+    text_input.style.setProperty("display", "block");
+    // Also in opposition to iemgui dialogs
+    text_input.style.setProperty("width",
+        field.type === "float" ? "5em" : "11.3em");
+    // Some bottom margin
+    inner_container.style.setProperty("margin-bottom", "8px");
+
+    // Break before left column or symbol input, except for the first row
+    if (!first_row) {
+        if (field.type === "symbol" || left_column) {
+            add_break(outer_container);
+        }
+    }
+    inner_container.appendChild(label);
+    inner_container.appendChild(text_input);
+    outer_container.appendChild(inner_container);
+}
+
+function build_form(template_string) {
+    var t_array = parse_template_string(template_string),
+        t,
+        i, j;
+    // For now we just build the form from the main template
+    // for the scalar. If there are any array, canvas, or list
+    // fields we just chuck their contents in som multi-line text
+    // widgets.
+    t = t_array[0];
+
+    document.getElementById("legend").textContent = t.template;
+    for (i = 0, j = 0; i < t.fields.length; i++) {
+        if (t.fields[i].type === "symbol" ||
+            t.fields[i].type === "float") {
+            add_text_input(t.fields[i], j % 2 === 0, i === 0);
+            j = (t.fields[i].type === "float") ? j + 1 : 0;
+        }
+    }
+    // Now for array, canvas, and text fields. These get a single textarea
+    // for now because it's non-trivial to parse (much less workably display)
+    // nested arrays
+    for (i = 0; i < t.fields.length; i++) {
+        if (t.fields[i].type === "array" ||
+            t.fields[i].type === "canvas" ||
+            t.fields[i].type === "list") {
+
+            add_textarea_input();
+            break;
+        }
+    }
+    pdgui.post("template_array is " + t_array.toString());
+}
+
+function parse_data_string(d_string) {
+    // trim off leading/trailing spaces and newlines, then split on newlines
+    var lines = d_string.trim().split("\n"),
+        scalar_values = lines[0].slice(0, -1).split(" "),
+        // for now we're not trying to parse the vector data. Nested arrays
+        // make it non-trivial to parse this
+        vector_values = lines.slice(1).join("\n"),
+        ret_obj = {};
+    ret_obj.name = scalar_values.shift();
+    ret_obj.scalar = scalar_values;
+    ret_obj.vector_string = vector_values;
+    return ret_obj;
+}
+
+function populate_form(data_string) {
+    var d_array = parse_data_string(data_string),
+        inputs = document.querySelectorAll("input.scalar_value"),
+        textareas = document.querySelectorAll("textarea"),
+        i;
+    for (i = 0; i < inputs.length; i++) {
+        // skip the leading template name
+        inputs[i].value = d_array.scalar[i].toString();
+    }
+    // For now, we throw all the vector fields into a single textarea
+    if (textareas.length) {
+        textareas[0].textContent = d_array.vector_string;
+    }
+}
+
+// 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, data_string) {
+    var head, tail, templates, data, data_separator;
+    pd_object_callback = gfxstub;
+    add_events(gfxstub);
+    // slice off the head of the message. This is where the templates
+    // for the data-- plus any templates for (nested) arrays-- are kept.
+    // We will keep the head intact for sending back to Pd since the user
+    // won't be able to change any of that data.
+    data_separator = "\n;\n;";
+    head = data_string.slice(0, data_string.indexOf(data_separator));
+
+    // Note: we need to keep a copy of the incoming data_string so that
+    // the user can revert if need be.
+
+    tail = data_string.slice(data_string.indexOf(data_separator) +
+        data_separator.length);
+
+    //pdgui.post("head of data is " + head);
+    template_string = head;
+    translate_form();
+    build_form(head); // Create form elements from the data template
+    populate_form(tail); // Fill the form we created with the actual data
+
+    // 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_attr(name, attrs) {
+    return attrs[attrs.indexOf(name) + 1];
+}
+
+function get_elem(name) {
+    return document.getElementById(name);
+}
+
+function add_events(name) {
+    // closing the Window
+    gui.Window.get().on("close", function () {
+        // this needs to do whatever the "cancel" button does
+        //pdgui.pdsend(name, "menuclose 0");
+        //cancel();
+        pdgui.remove_dialogwin(pd_object_callback);
+        gui.Window.get().close(true);
+    });
+    pdgui.dialog_bindings(name);
+}
+  </script>
+  </body>
+</html>
diff --git a/pd/nw/pdgui.js b/pd/nw/pdgui.js
index b25463f89..fbc16f8eb 100644
--- a/pd/nw/pdgui.js
+++ b/pd/nw/pdgui.js
@@ -3721,6 +3721,12 @@ function gui_canvas_dialog(did, attr_arrays) {
         attr_arrays);
 }
 
+function gui_data_dialog(did, data_string) {
+    dialogwin[did] = create_window(did, "data", 250, 300,
+        popup_coords[2], popup_coords[3],
+        data_string);
+}
+
 function gui_remove_gfxstub(did) {
     if (dialogwin[did] !== undefined && dialogwin[did] !== null) {
         dialogwin[did].window.close(true);
diff --git a/pd/src/g_scalar.c b/pd/src/g_scalar.c
index 28b7b3c99..160c71ecb 100644
--- a/pd/src/g_scalar.c
+++ b/pd/src/g_scalar.c
@@ -1231,7 +1231,7 @@ static void scalar_menuopen(t_scalar *x)
 static void scalar_properties(t_gobj *z, struct _glist *owner)
 {
     t_scalar *x = (t_scalar *)z;
-    char *buf, buf2[80];
+    char *buf, *gfx_tag;
     int bufsize;
     t_binbuf *b;
     glist_noselect(owner);
@@ -1241,10 +1241,8 @@ static void scalar_properties(t_gobj *z, struct _glist *owner)
     binbuf_free(b);
     buf = t_resizebytes(buf, bufsize, bufsize+1);
     buf[bufsize] = 0;
-    sprintf(buf2, "pdtk_data_dialog %%s {");
-    gfxstub_new((t_pd *)owner, x, buf2);
-    sys_gui(buf);
-    sys_gui("}\n");
+    gfx_tag = gfxstub_new2((t_pd *)owner, x);
+    gui_vmess("gui_data_dialog", "ss", gfx_tag, buf);
     t_freebytes(buf, bufsize+1);
 }
 
-- 
GitLab