diff --git a/pd/nw/dialog_prefs.html b/pd/nw/dialog_prefs.html index 717f9ac07d98109c5aa18e0176135bc2b642612e..4f6184cca02c161a488121cc1d3306a3e9c00c43 100644 --- a/pd/nw/dialog_prefs.html +++ b/pd/nw/dialog_prefs.html @@ -292,6 +292,11 @@ <option data-i18n="prefs.gui.presets.strongbad" value="strongbad"> </option> </select> + <br/> + <label data-i18n="[title]prefs.gui.zoom.save_zoom_tt"> + <input type="checkbox" id="save_zoom" name="save_zoom"> + <span data-i18n="prefs.gui.zoom.save_zoom"></span> + </label> </div> </div> @@ -386,6 +391,10 @@ function get_gui_preset() { return document.getElementById("gui_preset").selectedOptions[0].value; } +function get_save_zoom() { + return +document.getElementById("save_zoom").checked; +} + // startup config data // XXXTODO: We should maybe include these in the dialog, but the startup tab @@ -592,8 +601,9 @@ function apply(save_prefs) { midi_use_alsa ? get_attr("midi-outdev-names", attrs).length : 0 ); - // Send the name of the gui preset to Pd - pdgui.pdsend("pd gui-preset", get_gui_preset()); + // Send the gui prefs (currently just the name of the gui preset and the + // status of the save-zoom toggle) to Pd + pdgui.pdsend("pd gui-prefs", get_gui_preset(), get_save_zoom()); // Send the startup config data to Pd pdgui.pdsend.apply(null, ["pd path-dialog", startup_use_stdpath, startup_verbose].concat(get_path_array())); @@ -832,7 +842,7 @@ function midi_prefs_callback(attrs) { pdgui.resize_window(pd_object_callback); } -function gui_prefs_callback(name) { +function gui_prefs_callback(name, save_zoom) { var s = document.getElementById("gui_preset"), i; for (i = 0; i < s.options.length; i++) { @@ -841,6 +851,7 @@ function gui_prefs_callback(name) { break; } } + document.getElementById("save_zoom").checked = !!save_zoom; } // startup settings diff --git a/pd/nw/locales/de/translation.json b/pd/nw/locales/de/translation.json index 82448d0743d85e802ae1dbca1a5e654f1ded1355..d9d820c8a6aceae4690605513d4c7286ed6e0aeb 100644 --- a/pd/nw/locales/de/translation.json +++ b/pd/nw/locales/de/translation.json @@ -392,6 +392,10 @@ "strongbad": "Strongbad", "extended": "Extended", "c64": "C64" + }, + "zoom": { + "save_zoom": "Speichern/Laden der Vergrößerung im Patch", + "save_zoom_tt": "Speichere die aktuelle Vergrößerung mit dem Patch und stelle diese beim Laden des Patches wieder her" } }, "audio": { diff --git a/pd/nw/locales/en/translation.json b/pd/nw/locales/en/translation.json index c4dbc6525cf71f29ff0daebacface002c9522550..14b758bc87b522bc63c27b264624f37cf77ea01c 100644 --- a/pd/nw/locales/en/translation.json +++ b/pd/nw/locales/en/translation.json @@ -392,6 +392,10 @@ "strongbad": "Strongbad", "extended": "Extended", "c64": "C64" + }, + "zoom": { + "save_zoom": "save/load zoom level with patch", + "save_zoom_tt": "Save the current zoom level with the patch and restore it when reloading the patch" } }, "audio": { diff --git a/pd/nw/pd_canvas.js b/pd/nw/pd_canvas.js index d04d05f37a7134d949ca962ad61e9f41fa7013e0..77defb3647f0f34ab59d00a66a9b199a489b8399 100644 --- a/pd/nw/pd_canvas.js +++ b/pd/nw/pd_canvas.js @@ -788,6 +788,8 @@ function register_window_id(cid, attr_array) { // Trigger a "focus" event so that OSX updates the menu for this window nw_window_focus_callback(cid); canvas_events.normal(); + // Initialize the zoom level to the value retrieved from the patch, if any. + nw.Window.get().zoomLevel = attr_array.zoom; pdgui.canvas_map(cid); // side-effect: triggers gui_canvas_get_scroll set_editmode_checkbox(attr_array.editmode !== 0 ? true : false); // For now, there is no way for the cord inspector to be turned on by @@ -1219,6 +1221,7 @@ function nw_create_patch_window_menus(gui, w, name) { var z = gui.Window.get().zoomLevel; if (z < 8) { z++; } gui.Window.get().zoomLevel = z; + pdgui.pdsend(name, "zoom", z); pdgui.gui_canvas_get_scroll(name); } }); @@ -1228,6 +1231,7 @@ function nw_create_patch_window_menus(gui, w, name) { var z = gui.Window.get().zoomLevel; if (z > -7) { z--; } gui.Window.get().zoomLevel = z; + pdgui.pdsend(name, "zoom", z); pdgui.gui_canvas_get_scroll(name); } }); @@ -1256,6 +1260,7 @@ function nw_create_patch_window_menus(gui, w, name) { enabled: true, click: function () { gui.Window.get().zoomLevel = 0; + pdgui.pdsend(name, "zoom", 0); pdgui.gui_canvas_get_scroll(name); } }); diff --git a/pd/nw/pdgui.js b/pd/nw/pdgui.js index 23087bb349356137231f749b2ce13a0ca7a0989a..82c3ca2cebcd7c01acadc541e057316708281e44 100644 --- a/pd/nw/pdgui.js +++ b/pd/nw/pdgui.js @@ -1303,7 +1303,7 @@ function create_window(cid, type, width, height, xpos, ypos, attr_array) { } // create a new canvas -function gui_canvas_new(cid, width, height, geometry, editmode, name, dir, dirty_flag, cargs) { +function gui_canvas_new(cid, width, height, geometry, zoom, editmode, name, dir, dirty_flag, cargs) { // hack for buggy tcl popups... should go away for node-webkit //reset_ctrl_on_popup_window @@ -1353,6 +1353,7 @@ function gui_canvas_new(cid, width, height, geometry, editmode, name, dir, dirty dir: dir, dirty: dirty_flag, args: cargs, + zoom: zoom, editmode: editmode }); } @@ -4567,9 +4568,9 @@ function gui_midi_properties(gfxstub, sys_indevs, sys_outdevs, } } -function gui_gui_properties(dummy, name) { +function gui_gui_properties(dummy, name, save_zoom) { if (dialogwin["prefs"] !== null) { - dialogwin["prefs"].window.gui_prefs_callback(name); + dialogwin["prefs"].window.gui_prefs_callback(name, save_zoom); } } @@ -4920,6 +4921,7 @@ function do_optimalzoom(cid, hflag, vflag) { //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; + pdsend(cid, "zoom", z); } } diff --git a/pd/src/g_canvas.c b/pd/src/g_canvas.c index 83f872af0005f6e72c2fb1c894a85710f51dc84c..aec7efd3ed82efb42e11ad296b53ed457bbd92a1 100644 --- a/pd/src/g_canvas.c +++ b/pd/src/g_canvas.c @@ -14,6 +14,7 @@ to be different but are now unified except for some fossilized names.) */ #include "g_canvas.h" #include "g_all_guis.h" #include <string.h> +#include <math.h> extern int do_not_redraw; extern void canvas_drawconnection(t_canvas *x, int lx1, int ly1, int lx2, int ly2, t_int tag, int issignal); @@ -350,6 +351,22 @@ t_symbol *canvas_field_templatesym; /* for "canvas" data type */ t_word *canvas_field_vec; /* for "canvas" data type */ t_gpointer *canvas_field_gp; /* parent for "canvas" data type */ +static int calculate_zoom(t_float zoom_hack) +{ + // This gives back the zoom level stored in the patch (cf. zoom_hack + // in g_readwrite.c). Make sure to round this value to handle any rounding + // errors due to the limited float precision in Pd's binbuf writer. + int zoom = round(zoom_hack*32); + // This is a 5 bit number in 2's complement, so we need to extend the sign. + zoom = zoom << 27 >> 27; + // To be on the safe side, we finally clamp the result to the range -7..8 + // which are the zoom levels supported by Purr Data right now. + if (zoom < -7) zoom = -7; + if (zoom > 8) zoom = 8; + //post("read zoom level: %d", zoom); + return zoom; +} + /* make a new glist. It will either be a "root" canvas or else it appears as a "text" object in another window (canvas_getcurrent() tells us which.) */ @@ -370,6 +387,8 @@ t_canvas *canvas_new(void *dummy, t_symbol *sel, int argc, t_atom *argv) int vis = 0, width = GLIST_DEFCANVASWIDTH, height = GLIST_DEFCANVASHEIGHT; int xloc = 0, yloc = GLIST_DEFCANVASYLOC; int font = (owner ? owner->gl_font : sys_defaultfont); + int zoom = 0; + extern int sys_zoom; glist_init(x); //x->gl_magic_glass = magicGlass_new(x); @@ -380,20 +399,28 @@ t_canvas *canvas_new(void *dummy, t_symbol *sel, int argc, t_atom *argv) if (argc == 5) /* toplevel: x, y, w, h, font */ { + t_float zoom_hack = atom_getfloatarg(3, argc, argv); xloc = atom_getintarg(0, argc, argv); yloc = atom_getintarg(1, argc, argv); width = atom_getintarg(2, argc, argv); height = atom_getintarg(3, argc, argv); font = atom_getintarg(4, argc, argv); + zoom_hack -= height; + if (sys_zoom && zoom_hack > 0) + zoom = calculate_zoom(zoom_hack); } else if (argc == 6) /* subwindow: x, y, w, h, name, vis */ { + t_float zoom_hack = atom_getfloatarg(3, argc, argv); xloc = atom_getintarg(0, argc, argv); yloc = atom_getintarg(1, argc, argv); width = atom_getintarg(2, argc, argv); height = atom_getintarg(3, argc, argv); s = atom_getsymbolarg(4, argc, argv); vis = atom_getintarg(5, argc, argv); + zoom_hack -= height; + if (sys_zoom && zoom_hack > 0) + zoom = calculate_zoom(zoom_hack); } /* (otherwise assume we're being created from the menu.) */ @@ -448,6 +475,7 @@ t_canvas *canvas_new(void *dummy, t_symbol *sel, int argc, t_atom *argv) x->gl_willvis = vis; x->gl_edit = !strncmp(x->gl_name->s_name, "Untitled", 8); x->gl_font = sys_nearestfontsize(font); + x->gl_zoom = zoom; pd_pushsym(&x->gl_pd); //dpsaha@vt.edu gop resize (refactored by mathieu) @@ -548,6 +576,7 @@ t_glist *glist_addglist(t_glist *g, t_symbol *sym, x->gl_pixheight = py2 - py1; x->gl_font = (canvas_getcurrent() ? canvas_getcurrent()->gl_font : sys_defaultfont); + x->gl_zoom = 0; x->gl_screenx1 = x->gl_screeny1 = 0; x->gl_screenx2 = 450; x->gl_screeny2 = 300; diff --git a/pd/src/g_canvas.h b/pd/src/g_canvas.h index 1c29534bf0e110d3f76249d13a83aa462b61dad9..768aeb18a7085f3e47357ce8aca5ce07813c0f52 100644 --- a/pd/src/g_canvas.h +++ b/pd/src/g_canvas.h @@ -200,6 +200,7 @@ struct _glist t_editor *gl_editor; /* editor structure when visible */ t_symbol *gl_name; /* symbol bound here */ int gl_font; /* nominal font size in points, e.g., 10 */ + int gl_zoom; /* current zoom level (-7..8) */ struct _glist *gl_next; /* link in list of toplevels */ t_canvasenvironment *gl_env; /* root canvases and abstractions only */ unsigned int gl_havewindow:1; /* true if we own a window */ diff --git a/pd/src/g_editor.c b/pd/src/g_editor.c index 598f245ad519f1510877f07726f540b8e1e643ba..1dd4e7a98aff32f9383cb4a4b3b4646ad4f2024b 100644 --- a/pd/src/g_editor.c +++ b/pd/src/g_editor.c @@ -2471,11 +2471,12 @@ void canvas_vis(t_canvas *x, t_floatarg f) //fprintf(stderr,"new\n"); canvas_create_editor(x); canvas_args_to_string(argsbuf, x); - gui_vmess("gui_canvas_new", "xiisissis", + gui_vmess("gui_canvas_new", "xiisiissis", x, (int)(x->gl_screenx2 - x->gl_screenx1), (int)(x->gl_screeny2 - x->gl_screeny1), geobuf, + x->gl_zoom, x->gl_edit, x->gl_name->s_name, canvas_getdir(x)->s_name, @@ -7394,6 +7395,12 @@ static void canvas_font(t_canvas *x, t_floatarg font, t_floatarg oldfont, canvas_dofont(x2, font, realresize, realresize); } +static void canvas_zoom(t_canvas *x, t_floatarg zoom) +{ + x->gl_zoom = zoom; + //post("store zoom level: %d", x->gl_zoom); +} + void glist_getnextxy(t_glist *gl, int *xpix, int *ypix) { if (canvas_last_glist == gl) @@ -7592,6 +7599,8 @@ void g_editor_setup(void) gensym("menufont"), A_NULL); class_addmethod(canvas_class, (t_method)canvas_font, gensym("font"), A_FLOAT, A_FLOAT, A_FLOAT, A_FLOAT, A_NULL); + class_addmethod(canvas_class, (t_method)canvas_zoom, + gensym("zoom"), A_FLOAT, A_NULL); class_addmethod(canvas_class, (t_method)canvas_find, gensym("find"), A_SYMBOL, A_FLOAT, A_NULL); class_addmethod(canvas_class, (t_method)canvas_find_again, diff --git a/pd/src/g_readwrite.c b/pd/src/g_readwrite.c index a3b4ba824887da90c4e7447b8c9551e3a810f4dc..024be4c76f83874e5316045024f8a76b5a4f6fdb 100644 --- a/pd/src/g_readwrite.c +++ b/pd/src/g_readwrite.c @@ -646,6 +646,28 @@ static void glist_write(t_glist *x, t_symbol *filename, t_symbol *format) /* ------ routines to save and restore canvases (patches) recursively. ----*/ +static double zoom_hack(int x, int zoom) +{ + // AG: This employs an interesting little hack made possible by a loophole + // in Pd's patch parser (binbuf_eval), which will happily read a float value + // where an int is expected, and cast it to an int anyway. Applied to the #N + // header of a canvas, this lets us store the zoom level of a patch in the + // fractional part of a window geometry argument x, so that we can restore + // it when the patch is reloaded. Since vanilla and other Pd flavors won't + // even notice the extra data, the same patch can still be loaded by any + // other Pd version without any problems. + + // Encoding of the zoom level: Purr Data's zoom levels go from -7 to 8 so + // that we can encode them as 4 bit numbers in two's complement. However, it + // makes sense to leave 1 extra bit for future extensions, so our encoding + // actually uses nonnegative 5 bit integer multiples of 2^-5 = 0.03125 in a + // way that leaves the integer part of the height parameter intact. Note + // that using 2's complement here has the advantage that a zero zoom level + // maps to a zero fraction so that we don't need an extra bit to detect + // whether there's a zoom level when reading the patch. + return x + (zoom & 31) * 0.03125; +} + /* save to a binbuf, called recursively; cf. canvas_savetofile() which saves the document, and is only called on root canvases. */ static void canvas_saveto(t_canvas *x, t_binbuf *b) @@ -653,6 +675,7 @@ static void canvas_saveto(t_canvas *x, t_binbuf *b) t_gobj *y; t_linetraverser t; t_outconnect *oc; + extern int sys_zoom; /* subpatch */ if (x->gl_owner && !x->gl_env) { @@ -671,25 +694,53 @@ static void canvas_saveto(t_canvas *x, t_binbuf *b) patchsym = atom_getsymbolarg(name_index, binbuf_getnatom(bz), binbuf_getvec(bz)); binbuf_free(bz); - binbuf_addv(b, "ssiiiisi;", gensym("#N"), gensym("canvas"), - (int)(x->gl_screenx1), - (int)(x->gl_screeny1), - (int)(x->gl_screenx2 - x->gl_screenx1), - (int)(x->gl_screeny2 - x->gl_screeny1), - (patchsym != &s_ ? patchsym: gensym("(subpatch)")), - x->gl_mapped); + if (sys_zoom && x->gl_zoom != 0) { + // This uses the hack described above to store the zoom factor in + // the fractional part of the windows height parameter. Note that + // any of the other canvas geometry parameters would do just as + // well, as they are all measured in pixels and thus unlikely to + // become fractional in the future. + binbuf_addv(b, "ssiiifsi;", gensym("#N"), gensym("canvas"), + (int)(x->gl_screenx1), + (int)(x->gl_screeny1), + (int)(x->gl_screenx2 - x->gl_screenx1), + zoom_hack(x->gl_screeny2 - x->gl_screeny1, x->gl_zoom), + (patchsym != &s_ ? patchsym: gensym("(subpatch)")), + x->gl_mapped); + } else { + // This is the standard (vanilla) code. This is also used in the + // case of a zero zoom level, so unzoomed patches will always + // produce a standard vanilla patch file when saved. + binbuf_addv(b, "ssiiiisi;", gensym("#N"), gensym("canvas"), + (int)(x->gl_screenx1), + (int)(x->gl_screeny1), + (int)(x->gl_screenx2 - x->gl_screenx1), + (int)(x->gl_screeny2 - x->gl_screeny1), + (patchsym != &s_ ? patchsym: gensym("(subpatch)")), + x->gl_mapped); + } /* Not sure what the following commented line was doing... */ //t_text *xt = (t_text *)x; } /* root or abstraction */ else { - binbuf_addv(b, "ssiiiii;", gensym("#N"), gensym("canvas"), - (int)(x->gl_screenx1), - (int)(x->gl_screeny1), - (int)(x->gl_screenx2 - x->gl_screenx1), - (int)(x->gl_screeny2 - x->gl_screeny1), - (int)x->gl_font); + // See above. + if (sys_zoom && x->gl_zoom != 0) { + binbuf_addv(b, "ssiiifi;", gensym("#N"), gensym("canvas"), + (int)(x->gl_screenx1), + (int)(x->gl_screeny1), + (int)(x->gl_screenx2 - x->gl_screenx1), + zoom_hack(x->gl_screeny2 - x->gl_screeny1, x->gl_zoom), + (int)x->gl_font); + } else { + binbuf_addv(b, "ssiiiii;", gensym("#N"), gensym("canvas"), + (int)(x->gl_screenx1), + (int)(x->gl_screeny1), + (int)(x->gl_screenx2 - x->gl_screenx1), + (int)(x->gl_screeny2 - x->gl_screeny1), + (int)x->gl_font); + } canvas_savedeclarationsto(x, b); } for (y = x->gl_list; y; y = y->g_next) diff --git a/pd/src/m_glob.c b/pd/src/m_glob.c index 3f5e448fb0d2623899b7e2028e5a5b96675e48bc..1f2b49bbff9a19718e87201fc7ea5aae765cf760 100644 --- a/pd/src/m_glob.c +++ b/pd/src/m_glob.c @@ -75,16 +75,18 @@ static void glob_perf(t_pd *dummy, float f) sys_perf = (f != 0); } +extern int sys_zoom; extern t_symbol *sys_gui_preset; -static void glob_gui_preset(t_pd *dummy, t_symbol *s) +static void glob_gui_prefs(t_pd *dummy, t_symbol *s, float f) { sys_gui_preset = s; + sys_zoom = !!(int)f; } -/* just the gui-preset for now */ +/* just the gui-preset and the save-zoom toggle for now */ static void glob_gui_properties(t_pd *dummy) { - gui_vmess("gui_gui_properties", "xs", 0, sys_gui_preset->s_name); + gui_vmess("gui_gui_properties", "xsi", 0, sys_gui_preset->s_name, sys_zoom); } // ths one lives inside g_editor so that it can access the clipboard @@ -167,8 +169,8 @@ void glob_init(void) gensym("perf"), A_FLOAT, 0); class_addmethod(glob_pdobject, (t_method)glob_clipboard_text, gensym("clipboardtext"), A_FLOAT, 0); - class_addmethod(glob_pdobject, (t_method)glob_gui_preset, - gensym("gui-preset"), A_SYMBOL, 0); + class_addmethod(glob_pdobject, (t_method)glob_gui_prefs, + gensym("gui-prefs"), A_SYMBOL, A_FLOAT, 0); class_addmethod(glob_pdobject, (t_method)glob_gui_properties, gensym("gui-properties"), 0); class_addmethod(glob_pdobject, (t_method)glob_recent_files, diff --git a/pd/src/s_file.c b/pd/src/s_file.c index 3c4873760bca30ef2ebd81cc62e690efcbcc55e4..44a168d149170cf4d6fd36825272f92cfe3a475f 100644 --- a/pd/src/s_file.c +++ b/pd/src/s_file.c @@ -40,7 +40,7 @@ #define snprintf sprintf_s #endif -int sys_defeatrt; +int sys_defeatrt, sys_zoom; t_symbol *sys_flags = &s_; void sys_doflags( void); @@ -618,6 +618,8 @@ void sys_loadpreferences( void) } if (sys_getpreference("defeatrt", prefbuf, MAXPDSTRING)) sscanf(prefbuf, "%d", &sys_defeatrt); + if (sys_getpreference("savezoom", prefbuf, MAXPDSTRING)) + sscanf(prefbuf, "%d", &sys_zoom); if (sys_getpreference("guipreset", prefbuf, MAXPDSTRING)) { char preset_buf[MAXPDSTRING]; @@ -755,6 +757,8 @@ void glob_savepreferences(t_pd *dummy) sys_putpreference("nloadlib", buf1); sprintf(buf1, "%d", sys_defeatrt); sys_putpreference("defeatrt", buf1); + sprintf(buf1, "%d", sys_zoom); + sys_putpreference("savezoom", buf1); sys_putpreference("guipreset", sys_gui_preset->s_name); sys_putpreference("flags", (sys_flags ? sys_flags->s_name : ""));