diff --git a/pd/nw/locales/de/translation.json b/pd/nw/locales/de/translation.json
index f275b2538bafc1a5086283792b40279d3c5ad527..0dc5423ab061e1139b73bb28c72cbf5a75fc405d 100644
--- a/pd/nw/locales/de/translation.json
+++ b/pd/nw/locales/de/translation.json
@@ -165,6 +165,8 @@
     "zoomout_tt": "Verkleinere die Anzeige des Patches",
     "zoomreset": "Originalgröße",
     "zoomreset_tt": "Setze die Anzeige des Patches auf die Originalgröße zurück",
+    "zoomoptimal": "Optimale Größe",
+    "zoomoptimal_tt": "Wählt die optimale Größe, mit der der gesamte Patch angezeigt werden kann (soweit möglich)",
     "fullscreen": "Vollbild",
 
     "put": "Hinzufügen",
diff --git a/pd/nw/locales/en/translation.json b/pd/nw/locales/en/translation.json
index e5cfb44ec8a4c4794de0fd58dacc3c5e06651ac4..7f4cf14fa6f8afc83354e79dacbcf5e1b2d10461 100644
--- a/pd/nw/locales/en/translation.json
+++ b/pd/nw/locales/en/translation.json
@@ -165,6 +165,8 @@
     "zoomout_tt": "Make the patch visually smaller",
     "zoomreset": "Reset Zoom",
     "zoomreset_tt": "Reset the zoom to the original level",
+    "zoomoptimal": "Optimal Zoom",
+    "zoomoptimal_tt": "Change the zoom to the optimal level which makes the entire patch visible (as far as possible)",
     "fullscreen": "Fullscreen",
 
     "put": "Put",
diff --git a/pd/nw/pd_canvas.js b/pd/nw/pd_canvas.js
index 2f0b2b170389d7450988006eff07ac70c4ba6041..2f697bebe94fea0e4cf7d0965f7b2b12189243b5 100644
--- a/pd/nw/pd_canvas.js
+++ b/pd/nw/pd_canvas.js
@@ -1352,10 +1352,18 @@ function nw_create_patch_window_menus(gui, w, name) {
             pdgui.gui_canvas_get_scroll(name);
         }
     });
+    minit(m.view.optimalzoom, {
+        enabled: true,
+        click: function () {
+            pdgui.gui_canvas_optimal_zoom(name);
+            pdgui.gui_canvas_get_scroll(name);
+        }
+    });
     minit(m.view.zoomreset, {
         enabled: true,
         click: function () {
             gui.Window.get().zoomLevel = 0;
+            pdgui.gui_canvas_get_scroll(name);
         }
     });
     minit(m.view.fullscreen, {
diff --git a/pd/nw/pd_menus.js b/pd/nw/pd_menus.js
index 2121a94d5f371405fa7ce1745bcf4fff1ea723a3..0f87785895fff4e17c7c50265ba592ca02156187 100644
--- a/pd/nw/pd_menus.js
+++ b/pd/nw/pd_menus.js
@@ -325,6 +325,14 @@ function create_menu(gui, type) {
         modifiers: cmd_or_ctrl,
         tooltip: l("menu.zoomreset_tt")
     }));
+    if (canvas_menu) {
+	view_menu.append(m.view.optimalzoom = new gui.MenuItem({
+            label: l("menu.zoomoptimal"),
+            key: "0",
+            modifiers: cmd_or_ctrl + "+alt",
+            tooltip: l("menu.zoomoptimal_tt")
+	}));
+    }
     view_menu.append(new gui.MenuItem({ type: "separator" }));
     view_menu.append(m.view.fullscreen = new gui.MenuItem({
         label: l("menu.fullscreen"),
@@ -557,11 +565,11 @@ function create_menu(gui, type) {
             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 created from mac builtin above
         window_menu.append(new gui.MenuItem({
             label: l("menu.help"),
             submenu: help_menu
@@ -585,14 +593,14 @@ function create_menu(gui, type) {
                 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.windows"),
+            submenu: winman_menu
+        }));
         window_menu.append(new gui.MenuItem({
             label: l("menu.help"),
             submenu: help_menu
diff --git a/pd/nw/pdgui.js b/pd/nw/pdgui.js
index 09244de1fc77db09573b31da760769c28d572ce3..8b6235abce5d693058ed1d552c0863c31d9c9864 100644
--- a/pd/nw/pdgui.js
+++ b/pd/nw/pdgui.js
@@ -4371,17 +4371,11 @@ function gui_undo_menu(cid, undo_text, redo_text) {
     }
 }
 
-function do_getscroll(cid) {
-    var bbox, width, height, min_width, min_height, x, y,
-        svg;
-    // Since we're throttling these getscroll calls, they can happen after
-    // the patch has been closed. We remove the cid from the patchwin
-    // object on close, so we can just check to see if our Window object has
-    // been set to null, and if so just return.
-    // This is an awfully bad pattern. The whole scroll-checking mechanism
-    // needs to be rethought, but in the meantime this should prevent any
-    // errors wrt the rendering context disappearing.
-    if (!patchwin[cid]) { return; }
+function canvas_params(cid)
+{
+    // calculate the canvas parameters (svg bounding box and window geometry)
+    // for do_getscroll and do_optimalzoom
+    var bbox, width, height, min_width, min_height, x, y, svg;
     svg = get_item(cid, "patchsvg");
     bbox = svg.getBBox();
     // We try to do Pd-extended style canvas origins. That is, coord (0, 0)
@@ -4419,6 +4413,21 @@ function do_getscroll(cid) {
     min_height |= 0;
     x |= 0;
     y |= 0;
+    return { svg: svg, x: x, y: y, w: width, h: height,
+	     mw: min_width, mh: min_height };
+}
+
+function do_getscroll(cid) {
+    // Since we're throttling these getscroll calls, they can happen after
+    // the patch has been closed. We remove the cid from the patchwin
+    // object on close, so we can just check to see if our Window object has
+    // been set to null, and if so just return.
+    // This is an awfully bad pattern. The whole scroll-checking mechanism
+    // needs to be rethought, but in the meantime this should prevent any
+    // errors wrt the rendering context disappearing.
+    if (!patchwin[cid]) { return; }
+    var { svg: svg, x: x, y: y, w: width, h: height,
+	  mw: min_width, mh: min_height } = canvas_params(cid);
     if (width < min_width) {
         width = min_width;
     }
@@ -4458,6 +4467,46 @@ function gui_canvas_get_scroll(cid) {
 
 exports.gui_canvas_get_scroll = gui_canvas_get_scroll;
 
+function do_optimalzoom(cid) {
+    // determine an optimal zoom level that makes the entire patch fit within
+    // the window
+    if (!patchwin[cid]) { return; }
+    var { x: x, y: y, w: width, h: height, mw: min_width, mh: min_height } =
+	canvas_params(cid);
+    // Calculate the optimal horizontal and vertical zoom values,
+    // using floor to always round down to the nearest integer. Note
+    // that these may well be negative, if the viewport is too small
+    // for the patch at the current zoom level. XXXREVIEW: We assume a
+    // zoom factor of 1.2 here; this works for me on Linux, but I'm
+    // not sure how portable it is. -ag
+    var zx = 0, zy = 0;
+    if (width>0) zx = Math.floor(Math.log(min_width/width)/Math.log(1.2));
+    if (height>0) zy = Math.floor(Math.log(min_height/height)/Math.log(1.2));
+    // Optimal zoom is the minimum of the horizontal and vertical zoom
+    // values. This gives us the offset to the current zoom level. We
+    // then need to clamp the resulting new zoom level to the valid
+    // zoom level range of -8..+7.
+    var actz = patchwin[cid].zoomLevel, z = actz+Math.min(zx, zy);
+    if (z < -8) z = -8; if (z > 7) z = 7;
+    //post("bbox: "+width+"x"+height+"+"+x+"+"+y+" window size: "+min_width+"x"+min_height+" current zoom level: "+actz+" optimal zoom level: "+z);
+    if (z != actz) {
+	patchwin[cid].zoomLevel = z;
+    }
+}
+
+var optimalzoom_var = {};
+
+// We use a setTimeout here as with do_getscroll above, but we have to
+// use a smaller value here, so that we're done before a subsequent
+// call to do_getscroll updates the viewport. XXXREVIEW: Hopefully
+// 100 msec are enough for do_optimalzoom to finish.
+function gui_canvas_optimal_zoom(cid) {
+    clearTimeout(optimalzoom_var[cid]);
+    optimalzoom_var[cid] = setTimeout(do_optimalzoom, 150, cid);
+}
+
+exports.gui_canvas_optimal_zoom = gui_canvas_optimal_zoom;
+
 // handling the selection
 function gui_lower(cid, tag) {
     var svg = patchwin[cid].window.document.getElementById("patchsvg"),