From ce48a4caf05a4061657e0d02c5421feb3dbb6b0a Mon Sep 17 00:00:00 2001 From: Jonathan Wilkes <jon.w.wilkes@gmail.com> Date: Fri, 13 May 2016 15:56:57 -0400 Subject: [PATCH] first try at getting App and "Edit" menu to behave correctly on OSX --- pd/nw/pd_canvas.js | 53 ++++++++-- pd/nw/pd_menus.js | 238 ++++++++++++++++++++++++++++++--------------- 2 files changed, 203 insertions(+), 88 deletions(-) diff --git a/pd/nw/pd_canvas.js b/pd/nw/pd_canvas.js index 8aa41aa0a..0015d849b 100644 --- a/pd/nw/pd_canvas.js +++ b/pd/nw/pd_canvas.js @@ -618,12 +618,14 @@ var canvas_events = (function() { // Copy event document.addEventListener("copy", function(evt) { - // This event doesn't currently get called because the - // nw menubar receives the event and doesn't propagate - // to the DOM. But if we add the ability to toggle menubar - // display, we might need to rely on this listener. pdgui.post("copy detected by DOM listener"); - pdgui.pdsend(name, "copy"); + // On OSX, this event gets triggered when we're editing + // inside an object/message box. So we only forward the + // copy message to Pd if we're in a "normal" canvas state + pdgui.post("copy detected by DOM listener"); + if (canvas_events.get_state() === "normal") { + pdgui.pdsend(name, "copy"); + } }); // Listen to paste event using the half-baked Clipboard API from HTML5 @@ -658,12 +660,19 @@ var canvas_events = (function() { // 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 + // 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. + // Yet another caveat: we only want to check the external buffer + // and/or send a "paste" event to Pd if the canvas is in a "normal" + // state. + if (canvas_events.get_state() !== "normal") { + return; + } + if (might_be_a_pd_file(clipboard_data) && clipboard_data !== pdgui.get_last_clipboard_data()) { if (permission_to_paste_from_external_clipboard()) { @@ -952,13 +961,18 @@ function create_popup_menu(name) { } function nw_undo_menu(undo_text, redo_text) { - if (undo_text === "no") { + // Disabling undo/redo menu buttons on OSX will + // turn off any DOM events associated with that + // key command. So we can't disable them here-- + // otherwise it would turn off text-editing + // undo/redo. + if (process.platform !== "darwin" && undo_text === "no") { canvas_menu.edit.undo.enabled = false; } else { canvas_menu.edit.undo.enabled = true; canvas_menu.edit.undo.label = l("menu.undo") + " " + undo_text; } - if (redo_text === "no") { + if (process.platform !== "darwin" && redo_text === "no") { canvas_menu.edit.redo.enabled = false; } else { canvas_menu.edit.redo.enabled = true; @@ -994,11 +1008,16 @@ function instantiate_live_box() { var canvas_menu = {}; function set_edit_menu_modals(state) { + // OSX needs to keep these enabled, otherwise the events won't trigger + if (process.platform === "darwin") { + state = true; + } canvas_menu.edit.undo.enabled = state; canvas_menu.edit.redo.enabled = state; canvas_menu.edit.cut.enabled = state; canvas_menu.edit.copy.enabled = state; canvas_menu.edit.paste.enabled = state; + canvas_menu.edit.selectall.enabled = state; } function set_editmode_checkbox(state) { @@ -1100,11 +1119,25 @@ function nw_create_patch_window_menus(gui, w, name) { // Edit menu minit(m.edit.undo, { enabled: true, - click: function () { pdgui.pdsend(name, "undo"); } + click: function () { + if (process.platform === "darwin" && + canvas_events.get_state() === "text") { + document.execCommand("undo", false, null); + } else { + pdgui.pdsend(name, "undo"); + } + } }); minit(m.edit.redo, { enabled: true, - click: function () { pdgui.pdsend(name, "redo"); } + click: function () { + if (process.platform === "darwin" && + canvas_events.get_state() === "text") { + document.execCommand("redo", false, null); + } else { + pdgui.pdsend(name, "redo"); + } + } }); minit(m.edit.cut, { enabled: true, diff --git a/pd/nw/pd_menus.js b/pd/nw/pd_menus.js index bf03cb8fc..c1dfaccc9 100644 --- a/pd/nw/pd_menus.js +++ b/pd/nw/pd_menus.js @@ -36,6 +36,16 @@ function create_menu(gui, type) { // Window menu window_menu = new gui.Menu({ type: "menubar" }); + // On OSX, we need to start with the built-in mac menu in order to + // get the application menu to show up correctly. Unfortunately, this + // will also spawn a built-in "Edit" and "Window" menu. Even more + // unfortunately, we must use the built-in "Edit" menu-- without it + // there is no way to get <command-v> shortcut to trigger the + // DOM "paste" event. + if (osx) { + window_menu.createMacBuiltin("purr-data"); + } + // File menu file_menu = new gui.Menu(); @@ -101,46 +111,94 @@ function create_menu(gui, type) { })); // Edit menu - edit_menu = new gui.Menu(); - - // Edit sub-entries m.edit = {}; - if (canvas_menu) { - edit_menu.append(m.edit.undo = new gui.MenuItem({ + // For OSX, we have to use the built-in "Edit" menu-- I haven't + // found any other way to get "paste" event to trickle down to + // the DOM. As a consequence, we must fetch the relevant menu + // items that were created above with createMacBuiltin and assign + // them to the relevant variables below + if (osx) { + edit_menu = window_menu.items[1].submenu; + m.edit.undo = window_menu.items[1].submenu.items[0]; + // Remove the default Undo, since we can't seem to bind + // a click function to it + window_menu.items[1].submenu.remove(m.edit.undo); + + // Now create a new one and insert it at the top + edit_menu.insert(m.edit.undo = new gui.MenuItem({ label: l("menu.undo"), - tooltip: l("menu.undo_tt") - })); - edit_menu.append(m.edit.redo = new gui.MenuItem({ + tooltip: l("menu.undo_tt"), + key: "z", + modifiers: cmd_or_ctrl + }), 0); + + m.edit.redo = window_menu.items[1].submenu.items[1]; + // Remove the default Undo, since we can't seem to bind + // a click function to it + window_menu.items[1].submenu.remove(m.edit.redo); + + // Now create a new one and insert it at the top + edit_menu.insert(m.edit.redo = new gui.MenuItem({ label: l("menu.redo"), - tooltip: l("menu.redo_tt") - })); - edit_menu.append(new gui.MenuItem({ type: "separator" })); - edit_menu.append(m.edit.cut = new gui.MenuItem({ - label: l("menu.cut"), - key: "x", + tooltip: l("menu.redo_tt"), + key: "z", + modifiers: "shift+" + cmd_or_ctrl + }), 1); + + // Note: window_menu.items[1].submenu.items[2] is the separator + m.edit.cut = window_menu.items[1].submenu.items[3]; + m.edit.copy = window_menu.items[1].submenu.items[4]; + m.edit.paste = window_menu.items[1].submenu.items[5]; + // There's no "Delete" item for GNU/Linux or Windows-- + // not sure yet what to do with it. + m.edit.delete = window_menu.items[1].submenu.items[6]; + m.edit.selectall= window_menu.items[1].submenu.items[7]; + } else { + edit_menu = new gui.Menu(); + // Edit sub-entries + if (canvas_menu) { + edit_menu.append(m.edit.undo = new gui.MenuItem({ + label: l("menu.undo"), + tooltip: l("menu.undo_tt") + })); + edit_menu.append(m.edit.redo = new gui.MenuItem({ + label: l("menu.redo"), + tooltip: l("menu.redo_tt") + })); + edit_menu.append(new gui.MenuItem({ type: "separator" })); + edit_menu.append(m.edit.cut = new gui.MenuItem({ + label: l("menu.cut"), + key: "x", + modifiers: cmd_or_ctrl, + tooltip: l("menu.cut_tt") + })); + } + edit_menu.append(m.edit.copy = new gui.MenuItem({ + label: l("menu.copy"), + key: "c", modifiers: cmd_or_ctrl, - tooltip: l("menu.cut_tt") - })); + tooltip: l("menu.copy_tt") + })); + if (canvas_menu) { + // The nwjs menubar keybindings don't propagate down + // to the DOM. Here, we need the DOM to receive the + // "paste" event in case we want to paste a Pd file + // from an external buffer. So unfortunately we can't + // do the keybindings here. The side-effect is that + // "Ctrl-V" isn't displayed in the menu item. + edit_menu.append(m.edit.paste = new gui.MenuItem({ + label: l("menu.paste"), + //key: "v", + //modifiers: cmd_or_ctrl, + tooltip: l("menu.paste_tt") + })); + } } - edit_menu.append(m.edit.copy = new gui.MenuItem({ - label: l("menu.copy"), - key: "c", - modifiers: cmd_or_ctrl, - tooltip: l("menu.copy_tt") - })); + + // We need "duplicate" for canvas_menu and for OSX, where it's not + // part of the builtin Edit menu... + if (canvas_menu) { - // The nwjs menubar keybindings don't propagate down - // to the DOM. Here, we need the DOM to receive the - // "paste" event in case we want to paste a Pd file - // from an external buffer. So unfortunately we can't - // do the keybindings here. The side-effect is that - // "Ctrl-V" isn't displayed in the menu item. - edit_menu.append(m.edit.paste = new gui.MenuItem({ - label: l("menu.paste"), - //key: "v", - //modifiers: cmd_or_ctrl, - tooltip: l("menu.paste_tt") - })); edit_menu.append(m.edit.duplicate = new gui.MenuItem({ label: l("menu.duplicate"), key: "d", @@ -148,12 +206,17 @@ function create_menu(gui, type) { tooltip: l("menu.duplicate_tt") })); } - edit_menu.append(m.edit.selectall = new gui.MenuItem({ - label: l("menu.selectall"), - key: "a", - modifiers: cmd_or_ctrl, - tooltip: l("menu.selectall_tt") - })); + + // OSX already has "Select All" in the builtin Edit menu... + if (!osx) { + edit_menu.append(m.edit.selectall = new gui.MenuItem({ + label: l("menu.selectall"), + key: "a", + modifiers: cmd_or_ctrl, + tooltip: l("menu.selectall_tt") + })); + } + if (canvas_menu) { // Unfortunately nw.js doesn't allow // key: "Return" or key: "Enter", so we @@ -374,8 +437,12 @@ function create_menu(gui, type) { // Windows menu... call it "winman" (i.e., window management) // to avoid confusion - winman_menu = new gui.Menu(); - + if (osx) { + // on OSX, createMacBuiltin creates a window menu + winman_menu = window_menu.items[2].submenu; + } else { + winman_menu = new gui.Menu(); + } // Win sub-entries m.win = {}; winman_menu.append(m.win.nextwin = new gui.MenuItem({ @@ -479,46 +546,61 @@ function create_menu(gui, type) { })); // Add submenus to window menu - - // On OSX, we need to start with the built-in mac menu in order to - // get the application menu to show up correctly. Unfortunately, this - // will also spawn a built-in "Edit" and "Window" menu. Even more - // unfortunately, we must use the built-in "Edit" menu-- without it - // there is no way to get <command-v> shortcut to trigger the - // DOM "paste" event. if (osx) { - window_menu.createMacBuiltin("purr-data"); - } - window_menu.append(new gui.MenuItem({ - label: l("menu.file"), - submenu: file_menu - })); - window_menu.append(new gui.MenuItem({ - label: l("menu.edit"), - submenu: edit_menu - })); - window_menu.append(new gui.MenuItem({ - label: l("menu.view"), - submenu: view_menu - })); - if (canvas_menu) { + window_menu.insert(new gui.MenuItem({ + label: l("menu.file"), + submenu: file_menu + }), 1); + // Edit menu created from mac builtin above + window_menu.insert(new gui.MenuItem({ + label: l("menu.view"), + submenu: view_menu + }), 3); + window_menu.insert(new gui.MenuItem({ + label: l("menu.put"), + submenu: put_menu + }), 4); + // "Window" menu created from mac builtin above + window_menu.insert(new gui.MenuItem({ + label: l("menu.media"), + submenu: media_menu + }), 5); + window_menu.append(new gui.MenuItem({ + label: l("menu.help"), + submenu: help_menu + })); + } else { + window_menu.append(new gui.MenuItem({ + label: l("menu.file"), + submenu: file_menu + })); + window_menu.append(new gui.MenuItem({ + label: l("menu.edit"), + submenu: edit_menu + })); window_menu.append(new gui.MenuItem({ - label: l("menu.put"), - submenu: put_menu + label: l("menu.view"), + submenu: view_menu + })); + if (canvas_menu) { + window_menu.append(new gui.MenuItem({ + label: l("menu.put"), + submenu: put_menu + })); + } + window_menu.append(new gui.MenuItem({ + label: l("menu.windows"), + submenu: winman_menu + })); + window_menu.append(new gui.MenuItem({ + label: l("menu.media"), + submenu: media_menu + })); + window_menu.append(new gui.MenuItem({ + label: l("menu.help"), + submenu: help_menu })); } - window_menu.append(new gui.MenuItem({ - label: l("menu.windows"), - submenu: winman_menu - })); - window_menu.append(new gui.MenuItem({ - label: l("menu.media"), - submenu: media_menu - })); - window_menu.append(new gui.MenuItem({ - label: l("menu.help"), - submenu: help_menu - })); // Assign to window gui.Window.get().menu = window_menu; -- GitLab