diff --git a/pd/nw/dialog_text.html b/pd/nw/dialog_text.html
new file mode 100644
index 0000000000000000000000000000000000000000..6e7b95a7e920f38f87d504d63c880e885c9e5b5d
--- /dev/null
+++ b/pd/nw/dialog_text.html
@@ -0,0 +1,180 @@
+<!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">
+      <textarea id="text" style="width: 100%; box-sizing: border-box; height: 85vh; resize: none;"> 
+      </textarea>
+    </div>
+    <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>
+    <dialog id="save_before_quit">
+      <h4>Do you want to save the changes you made before closing the window?
+      </h4>
+      <div class="submit_buttons">
+        <button type="button"
+                onClick="ok()"
+                id="dialog_yes_button"
+                data-i18n="[title]canvas.save_dialog.yes_tt">
+          <span data-i18n="canvas.save_dialog.yes"></span>
+        </button>
+        <button type="button"
+                onClick="cancel()"
+                id="dialog_no_button"
+                data-i18n="[title]canvas.save_dialog.no_tt">
+          <span data-i18n="canvas.save_dialog.no"></span>
+        </button>
+        <button type="button"
+                onClick="close_modal_dialog()"
+                id="dialog_cancel_button"
+                data-i18n="[title]canvas.save_dialog.cancel_tt">
+          <span data-i18n="canvas.save_dialog.cancel"></span>
+        </button>
+      </div>
+    </dialog>
+  <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 dirty = false;
+
+function apply() {
+    var text_array = document.getElementById("text").value.split("\n");
+    pdgui.post("applying...");
+    // clear out Pd's binbuf for our text object
+    pdgui.pdsend(pd_object_callback, "clear");
+    text_array.forEach(function (line) {
+        line = line.replace(",", " \\, ");
+        line = line.replace(";", " \\; ");
+        line = line.replace("$", " \\$ ");
+        pdgui.pdsend(pd_object_callback, "addline", line);
+    });
+    dirty = 0;
+}
+
+function cancel() {
+    //window.close(true);
+    close_window();
+}
+
+function ok() {
+    apply();
+    cancel();
+}
+
+// Close the modal dialog that appears after clicking the window "x"
+// with a dirty textarea
+function close_modal_dialog() {
+    document.getElementById("save_before_quit").close();
+}
+
+function textarea_clear() {
+    document.getElementById("text").value = "";
+    dirty = true;
+}
+
+function textarea_append(line) {
+    document.getElementById("text").value += line;
+    dirty = true;
+}
+
+function set_dirty(state) {
+    dirty = state;
+}
+
+// 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, text_string) {
+    var head, tail, templates, data, data_separator;
+    pd_object_callback = gfxstub;
+    add_events(gfxstub);
+
+    translate_form();
+    pdgui.pdsend(gfxstub, "map");
+//    populate_form(text_string); // 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 close_window() {
+    pdgui.pdsend(pd_object_callback, "close");
+    pdgui.remove_dialogwin(pd_object_callback);
+    gui.Window.get().close(true);
+}
+
+function close_from_pd(signoff) {
+    if (signoff) {
+        pdgui.pdsend(pd_object_callback, "signoff");
+    }
+    pdgui.remove_dialogwin(pd_object_callback);
+    gui.Window.get().close(true);
+}
+
+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();
+        // blur the textarea to trigger an onchange if it was modified
+        document.getElementById("text").blur();
+        if (dirty) {
+            document.getElementById("save_before_quit").showModal();
+        } else {
+            close_window();
+        }
+    });
+    document.getElementById("text").onchange = function() {
+        dirty = true;
+    }
+    // Right now the bindings are limited to "Enter = Ok" and "Escape = Cancel".
+    // We leave off those bindings since Enter starts a new line in the textarea
+    //pdgui.dialog_bindings(name);
+}
+  </script>
+  </body>
+</html>
diff --git a/pd/nw/pdgui.js b/pd/nw/pdgui.js
index fbc16f8ebe3a5d8e4f56386a92975916ec2807cb..06cf709a847e08e965f3421b74aabd4082672af1 100644
--- a/pd/nw/pdgui.js
+++ b/pd/nw/pdgui.js
@@ -3727,6 +3727,42 @@ function gui_data_dialog(did, data_string) {
         data_string);
 }
 
+function gui_text_dialog_clear(did) {
+    if (dialogwin[did]) {
+        dialogwin[did].window.textarea_clear();
+    }
+}
+
+function gui_text_dialog_append(did, line) {
+    if (dialogwin[did]) {
+        dialogwin[did].window.textarea_append(line);
+    }
+}
+
+function gui_text_dialog_set_dirty(did, state) {
+    if (dialogwin[did]) {
+        dialogwin[did].window.set_dirty(state !== 0);
+    }
+}
+
+function gui_text_dialog(did, width, height, font_size) {
+    dialogwin[did] = create_window(did, "text", width, height,
+        popup_coords[2], popup_coords[3],
+        font_size);
+}
+
+function gui_text_dialog_raise(did) {
+    if (dialogwin[did]) {
+        dialogwin[did].focus();
+    }
+}
+
+function gui_text_dialog_close_from_pd(did, signoff) {
+    if (dialogwin[did]) {
+        dialogwin[did].window.close_from_pd(signoff !== 0);
+    }
+}
+
 function gui_remove_gfxstub(did) {
     if (dialogwin[did] !== undefined && dialogwin[did] !== null) {
         dialogwin[did].window.close(true);
diff --git a/pd/src/x_text.c b/pd/src/x_text.c
index 4e98c6dce18bbdad8cb7fd6aaef458343f3186be..d61ef6ab5af13e3829eb99c8cf9094a2afa5c011 100644
--- a/pd/src/x_text.c
+++ b/pd/src/x_text.c
@@ -61,46 +61,78 @@ static void textbuf_senditup(t_textbuf *x)
 {
     int i, ntxt;
     char *txt;
+    /* I don't think the Pd Vanilla interface can handle cases
+       where a single line is greater than MAXPDSTRING, at least
+       coming from the GUI to Pd. So instead of the %.*s specifier
+       we just use a character array of MAXPDSTRING size. I suppose
+       that means we'll fail on the Pd side, while Pd Vanilla would
+       fail when the GUI tries to forward that line back to Pd.
+    */
+    char buf[MAXPDSTRING];
     if (!x->b_guiconnect)
         return;
     binbuf_gettext(x->b_binbuf, &txt, &ntxt);
-    sys_vgui("pdtk_textwindow_clear .x%lx\n", x);
+    //sys_vgui("pdtk_textwindow_clear .x%lx\n", x);
+    gui_vmess("gui_text_dialog_clear", "x", x);
     for (i = 0; i < ntxt; )
     {
         char *j = strchr(txt+i, '\n');
         if (!j) j = txt + ntxt;
-        sys_vgui("pdtk_textwindow_append .x%lx {%.*s\n}\n",
-            x, j-txt-i, txt+i);
+        //sys_vgui("pdtk_textwindow_append .x%lx {%.*s\n}\n",
+        //    x, j-txt-i, txt+i);
+        if (j - txt - i >= MAXPDSTRING)
+        {
+            pd_error(x, "text: can't display lines greater than %d characters",
+                MAXPDSTRING);
+            break;
+        }
+        sprintf(buf, "%.*s\n", j-txt-i, txt+i);
+        gui_vmess("gui_text_dialog_append", "xs",
+            x, buf);
         i = (j-txt)+1;
     }
-    sys_vgui("pdtk_textwindow_setdirty .x%lx 0\n", x);
+    //sys_vgui("pdtk_textwindow_setdirty .x%lx 0\n", x);
+    gui_vmess("gui_text_dialog_set_dirty", "xi", x, 0);
     t_freebytes(txt, ntxt);
 }
 
+static void textbuf_map(t_textbuf *x)
+{
+    if (x->b_guiconnect)
+        textbuf_senditup(x);
+}
+
 static void textbuf_open(t_textbuf *x)
 {
     if (x->b_guiconnect)
     {
-        sys_vgui("wm deiconify .x%lx\n", x);
-        sys_vgui("raise .x%lx\n", x);
-        sys_vgui("focus .x%lx.text\n", x);
+        //sys_vgui("wm deiconify .x%lx\n", x);
+        //sys_vgui("raise .x%lx\n", x);
+        //sys_vgui("focus .x%lx.text\n", x);
+        gui_vmess("gui_text_dialog_raise", "x", x);
     }
     else
     {
         char buf[40];
-        sys_vgui("pdtk_textwindow_open .x%lx %dx%d {%s: %s} %d\n",
-            x, 600, 340, "myname", "text",
-                 sys_hostfontsize(glist_getfont(x->b_canvas)));//,
+        //sys_vgui("pdtk_textwindow_open .x%lx %dx%d {%s: %s} %d\n",
+        //    x, 600, 340, "myname", "text",
+        //         sys_hostfontsize(glist_getfont(x->b_canvas)));//,
                     //glist_getzoom(x->b_canvas)));
-        sprintf(buf, ".x%lx", (unsigned long)x);
+        sprintf(buf, "x%lx", (unsigned long)x);
         x->b_guiconnect = guiconnect_new(&x->b_ob.ob_pd, gensym(buf));
-        textbuf_senditup(x);
+        gui_vmess("gui_text_dialog", "xiii",
+            x,
+            600,
+            340,
+            sys_hostfontsize(glist_getfont(x->b_canvas))); 
+        //textbuf_senditup(x);
     }
 }
 
 static void textbuf_close(t_textbuf *x)
 {
-    sys_vgui("pdtk_textwindow_doclose .x%lx\n", x);
+    //sys_vgui("pdtk_textwindow_doclose .x%lx\n", x);
+    gui_vmess("gui_text_dialog_close_from_pd", "x", x, 1);
     if (x->b_guiconnect)
     {
         guiconnect_notarget(x->b_guiconnect, 1000);
@@ -197,7 +229,8 @@ static void textbuf_free(t_textbuf *x)
     binbuf_free(x->b_binbuf);
     if (x->b_guiconnect)
     {
-        sys_vgui("destroy .x%lx\n", x);
+        //sys_vgui("destroy .x%lx\n", x);
+        gui_vmess("gui_text_dialog_close_from_pd", "xi", x, 0);
         guiconnect_notarget(x->b_guiconnect, 1000);
     }
         /* just in case we're still bound to #A from loading... */
@@ -1889,6 +1922,8 @@ void x_qlist_setup(void )
     text_define_class = class_new(gensym("text define"),
         (t_newmethod)text_define_new,
         (t_method)text_define_free, sizeof(t_text_define), 0, A_GIMME, 0);
+    class_addmethod(text_define_class, (t_method)textbuf_map,
+        gensym("map"), 0);
     class_addmethod(text_define_class, (t_method)textbuf_open,
         gensym("click"), 0);
     class_addmethod(text_define_class, (t_method)textbuf_close,