diff --git a/pd/nw/pd_canvas.js b/pd/nw/pd_canvas.js index 10823f3bd55d7fd6786301ab1444b8c848a750ac..409ec79bd6a63600f0c8fe17b0d1b893397c22ea 100644 --- a/pd/nw/pd_canvas.js +++ b/pd/nw/pd_canvas.js @@ -52,6 +52,21 @@ function encode_for_dialog(s) { return s; } +// Super-simplistic guess at whether the string from the clipboard +// starts with Pd code. This is just meant as a convenience so that +// stuff in the copy buffer that obviously isn't Pd code doesn't get +// in the way when editing. +function might_be_a_pd_file(stuff_from_clipboard) { + var text = stuff_from_clipboard.trim(), + one = text.charAt(0), + two = text.charAt(1); + return (one === "#" && (two === "N" || two === "X")); +} + +function permission_to_paste_from_external_clipboard() { + return global.confirm("Warning: you are about to paste Pd code that came from somewhere outside of Pd. Do you want to continue?"); +} + function nw_window_focus_callback() { // on OSX, update the menu on focus if (process.platform === "darwin") { @@ -218,26 +233,35 @@ var canvas_events = (function() { // Which don't fire a keypress for some odd reason case 65: - if (cmd_or_ctrl_key(evt)) { + if (cmd_or_ctrl_key(evt)) { // ctrl-a pdgui.pdsend(name, "selectall"); hack = 0; // not sure what to report here... } break; case 88: - if (cmd_or_ctrl_key(evt)) { + if (cmd_or_ctrl_key(evt)) { // ctrl-x pdgui.pdsend(name, "cut"); hack = 0; // not sure what to report here... } break; case 67: - if (cmd_or_ctrl_key(evt)) { + if (cmd_or_ctrl_key(evt)) { // ctrl-c pdgui.pdsend(name, "copy"); hack = 0; // not sure what to report here... } break; case 86: - if (cmd_or_ctrl_key(evt)) { - pdgui.pdsend(name, "paste"); + if (cmd_or_ctrl_key(evt)) { // ctrl-v + // Crazy workaround: instead of sending the + // "paste" message to Pd here, we wait for the + // "paste" listener to pick it up. That way it + // can check to see if there's anything in the + // paste buffer, and if so forward it to Pd. + + // We could also use "cut" and "copy" handlers + // above for symmetry, but we're not currently + // doing anything with those buffers. + //pdgui.pdsend(name, "paste"); hack = 0; // not sure what to report here... } break; @@ -480,6 +504,78 @@ var canvas_events = (function() { evt.preventDefault(); }); + // Listen to paste event using the half-baked Clipboard API from HTML5 + document.addEventListener("paste", function(evt) { + var clipboard_data = evt.clipboardData.getData("text"), + line, + lines, + i, + pd_message; + // Precarious, overly complicated and prone to bugs... + // Basically, if a Pd user copies some Pd source file from another + // application, we give them a single paste operation to paste the + // code directly into a window (empty or otherwise). We supply a + // warning prompt to let the user know this is what's happening, so + // they could cancel if that's not what they wanted. + + // After the prompt, the user can no longer paste that particular + // string from the OS clipboard buffer. All paste actions + // will instead apply to whatever has been copied or cut from within + // a Pd patch. To paste from the OS clipboard again, the user + // must cut/copy a _different_ snippet of Pd source file than the + // one they previously tried to paste. + + // A temporary workaround to this confusing behavior would be to give + // external code-pasting its own menu button. Another possibility is + // to let copy/cut actions within the patch actually get written to + // the OS clipboard. The latter would involve a lot more work (e.g., + // sending FUDI messages from Pd to set the OS clipboard, etc.) + + // Also, we check below to make sure the OS clipboard is holding + // text that could conceivably be Pd source code. If not then the + // user won't get bothered with a prompt at all, and normal Pd + // paste behavior will follow. + + // Finally, from a usability standpoint the main drawback is that + // you can't try to paste the same Pd source code more than once. + // For users who want to pasting lots of source code this could be + // a frustration, but Pd's normal copy/paste behavior remains + // intuitive and in line with the way other apps tend to work. + + if (might_be_a_pd_file(clipboard_data) && + clipboard_data !== pdgui.get_last_clipboard_data()) { + if (permission_to_paste_from_external_clipboard()) { + // clear the buffer + pdgui.pdsend(name, "copyfromexternalbuffer"); + pd_message = ""; + lines = clipboard_data.split("\n"); + for (i = 0; i < lines.length; i++) { + line = lines[i]; + // process pd_message if it ends with a semicolon that + // isn't preceded by a backslash + if (line.slice(-1) === ";" && + (line.length < 2 || line.slice(-2, -1) !== "\\")) { + if (pd_message === "") { + pd_message = line; + } else { + pd_message = pd_message + " " + line; + } + pdgui.pdsend(name, "copyfromexternalbuffer", pd_message); + pd_message = ""; + } else { + pd_message = pd_message + " " + line; + pd_message = pd_message.replace(/\n/g, ""); + } + } + // This isn't needed, but pd-l2ork did it for some reason... + pdgui.pdsend(name, "copyfromexternalbuffer"); + } + pdgui.set_last_clipboard_data(clipboard_data); + } + // Send a canvas "paste" message to Pd + pdgui.pdsend(name, "paste"); + }); + // The following is commented out because we have to set the // event listener inside nw_create_pd_window_menus due to a // bug with nwworkingdir diff --git a/pd/nw/pdgui.js b/pd/nw/pdgui.js index b9b2277cb175068b639e8708a287d81692160b56..d88c0d58bee961d13b62debd1e8e0649db1f49b9 100644 --- a/pd/nw/pdgui.js +++ b/pd/nw/pdgui.js @@ -2,6 +2,7 @@ var pwd; var gui_dir; +var last_clipboard_data; exports.set_pwd = function(pwd_string) { pwd = pwd_string; @@ -27,6 +28,14 @@ exports.get_pd_opendir = function() { } } +exports.set_last_clipboard_data = function(data) { + last_clipboard_data = data; +} + +exports.get_last_clipboard_data = function() { + return last_clipboard_data; +} + // Modules var fs = require("fs"); // for fs.existsSync @@ -571,7 +580,6 @@ function menu_send(name) { } function gui_set_editmode(cid, state) { -post("set the mode"); patchwin[cid].window.set_editmode_checkbox(state !== 0 ? true : false); } diff --git a/pd/src/g_editor.c b/pd/src/g_editor.c index 8b60ce3951163e8f4da9803aefee9f84562116ca..a1ea298cfeb58ed3356b9fefd305aa433fe4d383 100644 --- a/pd/src/g_editor.c +++ b/pd/src/g_editor.c @@ -6130,12 +6130,27 @@ static void canvas_copy(t_canvas *x) binbuf_free(copy_binbuf); clipboard_istext = 0; //fprintf(stderr, "canvas_copy\n"); - sys_vgui("pdtk_canvas_reset_last_clipboard\n"); + /* We're not replacing the following sys_vgui call because nw.js's + paste mechanism works a bit differently and doesn't require this. + But if I missed some functionality this-- as well as the rest of the + insanely complicated externalbuffer logic-- should be revisited. */ + + //sys_vgui("pdtk_canvas_reset_last_clipboard\n"); copy_binbuf = canvas_docopy(x); if (!x->gl_editor->e_selection) - sys_vgui("pdtk_canvas_update_edit_menu .x%lx 0\n", x); + { + /* Ok, this makes no sense-- if we return above when there's no + e_selection, then how could the following possibly be true? */ + + //sys_vgui("pdtk_canvas_update_edit_menu .x%lx 0\n", x); + } else - sys_vgui("pdtk_canvas_update_edit_menu .x%lx 1\n", x); + { + /* Still not exactly sure what this is doing. If it's just + disabling menu items related to the clipboard I think we can + do without it. */ + //sys_vgui("pdtk_canvas_update_edit_menu .x%lx 1\n", x); + } paste_xyoffset = 1; if (x->gl_editor->e_textedfor) {