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,