diff --git a/externals/pd-lua b/externals/pd-lua index b767c65b6607a1b2b80798a5ff4fb3e3c1d752a5..8eaaaa3c39af8968248fab5659d4a4d2da4f1ed7 160000 --- a/externals/pd-lua +++ b/externals/pd-lua @@ -1 +1 @@ -Subproject commit b767c65b6607a1b2b80798a5ff4fb3e3c1d752a5 +Subproject commit 8eaaaa3c39af8968248fab5659d4a4d2da4f1ed7 diff --git a/pd/nw/index.js b/pd/nw/index.js index 8c777871fede74fd90718c7b19d7aaba3885f72c..1a173c91e36f6b477a5851eaec6b225d9b868ee2 100644 --- a/pd/nw/index.js +++ b/pd/nw/index.js @@ -584,6 +584,7 @@ function nw_create_pd_window_menus(gui, w) { minit(m.edit.reselect, { enabled: false }); } if (osx) { + minit(m.edit.encapsulate, { enabled: false }); minit(m.edit.tidyup, { enabled: false }); minit(m.edit.font, { enabled: false }); minit(m.edit.cordinspector, { enabled: false }); diff --git a/pd/nw/locales/en/translation.json b/pd/nw/locales/en/translation.json index e7a8510191e040baa874336b085caaf65711450b..20b33e60c02c34a670afa83f95fe96a16ea0c30c 100644 --- a/pd/nw/locales/en/translation.json +++ b/pd/nw/locales/en/translation.json @@ -144,6 +144,8 @@ "reselect_tt": "Restore the previous selection", "find": "Find", "find_tt": "Find text in the console output", + "encapsulate": "Encapsulate", + "encapsulate_tt": "Encapsulate automatically the current selection into a subpatch", "tidyup": "Tidy Up", "tidyup_tt": "Line up the selected objects in straight rows and columns", "clear_console": "Clear Console", diff --git a/pd/nw/pd_canvas.js b/pd/nw/pd_canvas.js index e7bc1ae692329ec6c02e0550b80cdee0ed6baf10..c3374b9e1c97de61883565b30888002578d49ba2 100644 --- a/pd/nw/pd_canvas.js +++ b/pd/nw/pd_canvas.js @@ -1626,6 +1626,10 @@ function nw_create_patch_window_menus(gui, w, name) { pdgui.pdsend(name, "reselect"); } }); + minit(m.edit.encapsulate, { + enabled: true, + click: function() { pdgui.pdsend(name, "encapsulate"); } + }); minit(m.edit.tidyup, { enabled: true, click: function() { pdgui.pdsend(name, "tidy"); } diff --git a/pd/nw/pd_menus.js b/pd/nw/pd_menus.js index cfbc48f25c561eb3a760906db1ce9a31eb4e7700..79e031410c527e0e21d64c1247574ad8dc01f15a 100644 --- a/pd/nw/pd_menus.js +++ b/pd/nw/pd_menus.js @@ -278,6 +278,12 @@ function create_menu(gui, type) { })); edit_menu.append(new gui.MenuItem({ type: "separator" })); if (canvas_menu) { + edit_menu.append(m.edit.encapsulate = new gui.MenuItem({ + label: l("menu.encapsulate"), + key: shortcuts.menu.encapsulate.key, + modifiers: shortcuts.menu.encapsulate.modifiers, + tooltip: l("menu.encapsulate_tt") + })); edit_menu.append(m.edit.tidyup = new gui.MenuItem({ label: l("menu.tidyup"), key: shortcuts.menu.tidyup.key, diff --git a/pd/nw/pd_shortcuts.js b/pd/nw/pd_shortcuts.js index 4e140856cd55862a785b271757532fbdfa95d13a..a0f2bfd6055a56953769a165fc6a32d11bc6c0a4 100644 --- a/pd/nw/pd_shortcuts.js +++ b/pd/nw/pd_shortcuts.js @@ -24,6 +24,7 @@ exports.menu = { "reselect": { key: String.fromCharCode(10), modifiers: cmd_or_ctrl }, "clear_console": { key: "l", modifiers: cmd_or_ctrl + "+shift" }, + "encapsulate": { key: "e", modifiers: cmd_or_ctrl + "+shift" }, "tidyup": { key: "y", modifiers: cmd_or_ctrl }, "cordinspector": { key: "r", modifiers: cmd_or_ctrl + "+shift" }, "find": { key: "f", modifiers: cmd_or_ctrl }, diff --git a/pd/src/g_editor.c b/pd/src/g_editor.c index 15dfc84f01240e40f99a3d87cfda7f54eba0e0eb..e2e2c65ff6db5927d52c089940668473ee20579a 100644 --- a/pd/src/g_editor.c +++ b/pd/src/g_editor.c @@ -6280,6 +6280,270 @@ static void canvas_cut(t_canvas *x) } } +/* ------------------------- encapsulate ------------------------ */ + +typedef struct _xletholder +{ + t_gobj *xlh_addr; + int xlh_seln; + int xlh_xletn; + int xlh_type; + int xlh_x; + int xlh_y; + t_binbuf *xlh_conn; + struct _xletholder *xlh_next; +} t_xletholder; + +#define X_PLAOFF 40 +#define Y_PLAOFF 80 +#define XLETSGAP 10 + +static void canvas_dofancycopy(t_canvas *x, t_binbuf **object, t_binbuf **connections) +{ + t_gobj *y; + t_linetraverser t; + t_outconnect *oc; + t_binbuf *b = binbuf_new(); + int maxx, maxy, minx, miny, numobj = 0; + maxx = maxy = 0; + minx = miny = INT_MAX; + + binbuf_addv(b, "ssiiiisi;", gensym("#N"), gensym("canvas"), + 0, 0, 450, 300, gensym("subpatch"), 0); + + /* compute global rect */ + for (y = x->gl_list; y; y = y->g_next) + { + if (glist_isselected(x, y)) + { + int tminx, tmaxx, tminy, tmaxy; + gobj_getrect(y, x, &tminx, &tminy, &tmaxx, &tmaxy); + if(tminx < minx) minx = tminx; + if(tmaxx > maxx) maxx = tmaxx; + if(tminy < miny) miny = tminy; + if(tmaxy > maxy) maxy = tmaxy; + } + } + + /* save selected objects into binbbuf */ + for (y = x->gl_list; y; y = y->g_next) + { + if (glist_isselected(x, y)) + { + gobj_displace(y, x, X_PLAOFF-minx, Y_PLAOFF-miny); + gobj_save(y, b); + gobj_displace(y, x, minx-X_PLAOFF, miny-Y_PLAOFF); + numobj++; + } + } + + /* traverse all the lines */ + t_xletholder *inlets = 0, *outlets = 0, *lastoutlet = 0; + linetraverser_start(&t, x); + while (oc = linetraverser_next(&t)) + { + int s1 = glist_isselected(x, &t.tr_ob->ob_g); + int s2 = glist_isselected(x, &t.tr_ob2->ob_g); + /* if inner connection */ + if (s1 && s2) + { + binbuf_addv(b, "ssiiii;", gensym("#X"), gensym("connect"), + glist_selectionindex(x, &t.tr_ob->ob_g, 1), t.tr_outno, + glist_selectionindex(x, &t.tr_ob2->ob_g, 1), t.tr_inno); + } + /* if outer outlet connection */ + else if(s1 && !s2) + { + /* if we continue in the same outlet, we add the object and inlet number to the list and skip */ + if (lastoutlet && lastoutlet->xlh_addr == t.tr_outlet) + { + binbuf_addv(lastoutlet->xlh_conn, "ii", glist_selectionindex(x, &t.tr_ob2->ob_g, 0), t.tr_inno); + continue; + } + + /* create and fill outlet handling structure */ + t_xletholder *newoutlet = (t_xletholder *)getbytes(sizeof(t_xletholder)); + newoutlet->xlh_addr = t.tr_outlet; + newoutlet->xlh_seln = glist_selectionindex(x, &t.tr_ob->ob_g, 1); + newoutlet->xlh_xletn = t.tr_outno; + newoutlet->xlh_type = obj_issignaloutlet(t.tr_ob, t.tr_outno); + newoutlet->xlh_x = t.tr_lx1; + newoutlet->xlh_y = t.tr_ly1; + newoutlet->xlh_conn = binbuf_new(); + binbuf_addv(newoutlet->xlh_conn, "ii", glist_selectionindex(x, &t.tr_ob2->ob_g, 0), t.tr_inno); + /* insert structure regarding its coords */ + t_xletholder *prev = 0, *curr = outlets; + while(curr && (t.tr_lx1 > curr->xlh_x || (t.tr_lx1 == curr->xlh_x && t.tr_ly1 > curr->xlh_y))) + { + prev = curr; + curr = curr->xlh_next; + } + if(prev) prev->xlh_next = newoutlet; + else outlets = newoutlet; + newoutlet->xlh_next = curr; + + lastoutlet = newoutlet; + } + /* if outer inlet connection */ + else if(!s1 && s2) + { + /* check if inlet is already visited */ + int alr = 0; + t_xletholder *it; + for(it = inlets; it && !alr; ) + { + alr = ((t.tr_inno ? t.tr_inlet : t.tr_ob2) == it->xlh_addr); + if(!alr) it = it->xlh_next; + } + + int type = obj_issignaloutlet(t.tr_ob, t.tr_outno); + /* if so, we add the object and outlet number to the list and skip */ + if(alr) + { + binbuf_addv(it->xlh_conn, "ii", glist_selectionindex(x, &t.tr_ob->ob_g, 0), t.tr_outno); + if(it->xlh_type >= 0 && it->xlh_type != type) it->xlh_type = -1; + continue; + } + + /* create and fill inlet handling structure */ + t_xletholder *newinlet = (t_xletholder *)getbytes(sizeof(t_xletholder)); + newinlet->xlh_addr = (t.tr_inno ? t.tr_inlet : t.tr_ob2); + newinlet->xlh_seln = glist_selectionindex(x, &t.tr_ob2->ob_g, 1); + newinlet->xlh_xletn = t.tr_inno; + newinlet->xlh_type = type; + newinlet->xlh_x = t.tr_lx2; + newinlet->xlh_y = t.tr_ly2; + newinlet->xlh_conn = binbuf_new(); + binbuf_addv(newinlet->xlh_conn, "ii", glist_selectionindex(x, &t.tr_ob->ob_g, 0), t.tr_outno); + /* insert structure regarding its coords */ + t_xletholder *prev = 0, *curr = inlets; + while(curr && (t.tr_lx2 > curr->xlh_x || (t.tr_lx2 == curr->xlh_x && t.tr_ly2 > curr->xlh_y))) + { + prev = curr; + curr = curr->xlh_next; + } + if(prev) prev->xlh_next = newinlet; + else inlets = newinlet; + newinlet->xlh_next = curr; + } + } + + /* store outer connections in a binbuf */ + t_binbuf *conns = binbuf_new(); + int subnum = glist_selectionindex(x, 0, 0), nin = 0, nout = 0, xplac = 0; + while(inlets) + { + if(inlets->xlh_type >= 0) + { + binbuf_addv(b, "ssiis;", gensym("#X"), gensym("obj"), + X_PLAOFF/3 + xplac, Y_PLAOFF/3, (inlets->xlh_type ? gensym("inlet~") : gensym("inlet"))); + + xplac += (inlets->xlh_type ? 46 : 40) + XLETSGAP; //hardcoded + } + else + { + binbuf_addv(b, "ssiiss;", gensym("#X"), gensym("obj"), + X_PLAOFF/3 + xplac, Y_PLAOFF/3, gensym("inlet~"), gensym("fwd")); + + xplac += 64 + XLETSGAP; //hardcoded + + binbuf_addv(b, "ssiiii;", gensym("#X"), gensym("connect"), + numobj+nin, 1, + inlets->xlh_seln, inlets->xlh_xletn); + } + + binbuf_addv(b, "ssiiii;", gensym("#X"), gensym("connect"), + numobj+nin, 0, + inlets->xlh_seln, inlets->xlh_xletn); + + + int nvec = binbuf_getnatom(inlets->xlh_conn); + t_atom *vec = binbuf_getvec(inlets->xlh_conn); + for(int i = 0; i < nvec/2; i++) + { + binbuf_addv(conns, "ssffii;", gensym("#X"), gensym("connect"), + atom_getfloat(vec+(2*i)), atom_getfloat(vec+(2*i+1)), subnum, nin); + } + binbuf_free(inlets->xlh_conn); + t_xletholder *tmp = inlets->xlh_next; + freebytes(inlets, sizeof(t_xletholder)); + inlets = tmp; + nin++; + } + xplac = 0; + while(outlets) + { + binbuf_addv(b, "ssiis;", gensym("#X"), gensym("obj"), + X_PLAOFF/3 + xplac, (maxy-miny) + Y_PLAOFF*5/3, (outlets->xlh_type ? gensym("outlet~") : gensym("outlet"))); + + xplac += (outlets->xlh_type ? 52 : 46) + XLETSGAP; //hardcoded + + binbuf_addv(b, "ssiiii;", gensym("#X"), gensym("connect"), + outlets->xlh_seln, outlets->xlh_xletn, + numobj+nin+nout, 0); + + int nvec = binbuf_getnatom(outlets->xlh_conn); + t_atom *vec = binbuf_getvec(outlets->xlh_conn); + for(int i = 0; i < nvec/2; i++) + { + binbuf_addv(conns, "ssiiff;", gensym("#X"), gensym("connect"), + subnum, nout, atom_getfloat(vec+(2*i)), atom_getfloat(vec+(2*i+1))); + } + binbuf_free(outlets->xlh_conn); + t_xletholder *tmp = outlets->xlh_next; + freebytes(outlets, sizeof(t_xletholder)); + outlets = tmp; + nout++; + } + + int m = (nin > nout ? nin : nout); + int width = (IOWIDTH * m) * 2 - IOWIDTH; + + binbuf_addv(b, "ssiiss;", gensym("#X"), gensym("restore"), + (minx+maxx)/2 - width/2, (miny+maxy)/2, gensym("pd"), gensym("[name]")); + + *object = b; + *connections = conns; +} + +static void canvas_encapsulate(t_canvas *x) +{ + /* check preconditions */ + // num selected objects > 0 + if (!x->gl_editor || !x->gl_editor->e_selection) + return; + + canvas_undo_add(x, UNDO_SEQUENCE_START, "encapsulate", 0); + /* cut selected objects using special copy method, based on canvas_cut */ + t_binbuf *object, *connections; + int centx, centy; + canvas_undo_add(x, UNDO_CUT, "cut", canvas_undo_set_cut(x, UCUT_CUT)); + canvas_dofancycopy(x, &object, &connections); + canvas_doclear(x); + glob_preset_node_list_check_loc_and_update(); + scrollbar_update(x); + + /* subcanvas creation */ + canvas_dopaste(x, object); + /* trace connections between unselectd objects and patch object */ + pd_bind(&x->gl_pd, gensym("#X")); + binbuf_eval(connections, 0, 0, 0); + pd_unbind(&x->gl_pd, gensym("#X")); + + canvas_undo_add(x, UNDO_CREATE, "create", canvas_undo_set_create(x)); + + binbuf_free(object); + binbuf_free(connections); + + /* select subpatch object */ + t_gobj *sub = glist_nth(x, glist_getindex(x, 0)-1); + gobj_activate(sub, x, (0b1 << 31) | (0x0003 << 16) | 0x0009); + + canvas_undo_add(x, UNDO_SEQUENCE_END, "encapsulate", 0); +} + +/* ------------------------------------------------- */ + static int paste_onset; static t_canvas *paste_canvas; @@ -7773,6 +8037,8 @@ void g_editor_setup(void) gensym("redo"), A_NULL); class_addmethod(canvas_class, (t_method)canvas_tidy, gensym("tidy"), A_NULL); + class_addmethod(canvas_class, (t_method)canvas_encapsulate, + gensym("encapsulate"), A_NULL); //class_addmethod(canvas_class, (t_method)canvas_texteditor, // gensym("texteditor"), A_NULL); class_addmethod(canvas_class, (t_method)canvas_editmode, diff --git a/scripts/regression_tests.pd b/scripts/regression_tests.pd index af06729fe90d15f2662ec9080e59b6473be54d4d..4b8a8c20c7713b30c986cb19b34e0e4d0c16751c 100644 --- a/scripts/regression_tests.pd +++ b/scripts/regression_tests.pd @@ -1,7 +1,7 @@ -#N canvas 245 72 750 572 12; +#N canvas -9 -9 771 392 12; #X obj 465 281 r \$0-result; -#X obj 212 239 bng 15 250 50 0 empty empty Run_all 17 7 0 10 -262144 --1 -1; +#X obj 212 239 bng 15 250 50 0 empty empty Run_all 17 7 0 10 #fcfcfc +#000000 #000000; #X obj 56 25 r init; #X obj 345 191 route dollarzero; #X obj 198 281 rtest msg_dollarzero; @@ -65,6 +65,7 @@ is handy for some binbuf tests.; #X obj 198 2361 rtest inlet~_no_fwd; #X obj 198 2416 rtest inlet~_fwd_large_message; #X obj 198 2471 rtest pow~_negative_numbers; +#X obj 198 2526 rtest encapsulate; #X connect 0 0 27 0; #X connect 1 0 4 0; #X connect 2 0 42 0; @@ -115,3 +116,4 @@ is handy for some binbuf tests.; #X connect 56 0 57 0; #X connect 57 0 58 0; #X connect 58 0 59 0; +#X connect 59 0 60 0; diff --git a/scripts/regression_tests/encapsulate.pd b/scripts/regression_tests/encapsulate.pd new file mode 100644 index 0000000000000000000000000000000000000000..7e3fe921e3879876ec60b6e76d3422ce258d77bf --- /dev/null +++ b/scripts/regression_tests/encapsulate.pd @@ -0,0 +1,126 @@ +#N canvas 472 0 948 963 10; +#X obj 794 292 s pd-encapsulate_test.pd; +#X obj 55 22 inlet; +#X obj 65 674 outlet; +#X obj 174 61 t b b; +#X obj 101 318 float; +#X obj 101 273 until; +#X obj 193 318 bang; +#X msg 101 238 bang; +#X obj 86 90 bng 15 250 50 0 empty empty empty 17 7 0 10 -262144 -1 +-1; +#X msg 794 96 editmode 1; +#X msg 794 180 encapsulate; +#X obj 651 42 t b b b b b b b b b; +#X msg 169 271 0; +#X msg 794 208 obj_addtobuf pd test; +#X msg 794 236 obj_buftotext; +#X msg 794 264 noselect; +#X obj 102 404 list cat; +#X obj 102 361 moses 5; +#X obj 103 489 unpack f f f f f; +#X obj 103 447 spigot; +#X obj 410 236 float 1; +#X obj 298 567 ==; +#X obj 331 567 ==; +#X obj 367 567 ==; +#X obj 401 567 ==; +#X obj 437 567 ==; +#X obj 491 251 float 0; +#X msg 512 209 clear; +#X obj 371 168 t b b b; +#X obj 474 168 t b b b; +#X obj 404 611 &&; +#X obj 452 611 + 0; +#X obj 386 645 float; +#X obj 606 312 float; +#X obj 606 267 until; +#X obj 698 312 bang; +#X msg 606 232 bang; +#X msg 674 265 0; +#X obj 607 398 list cat; +#X obj 607 355 moses 5; +#X obj 608 483 unpack f f f f f; +#X obj 608 441 spigot; +#X obj 653 312 + 1; +#X msg 794 124 mouse 100 280 1 0; +#X msg 794 152 mouseup 250 380 0; +#X obj 148 318 + 1; +#X obj 65 621 list append encapsulate feature error; +#X connect 1 0 3 0; +#X connect 3 1 11 0; +#X connect 4 0 17 0; +#X connect 4 0 45 0; +#X connect 5 0 4 0; +#X connect 6 0 5 1; +#X connect 7 0 5 0; +#X connect 8 0 3 0; +#X connect 9 0 0 0; +#X connect 10 0 0 0; +#X connect 11 0 28 0; +#X connect 11 1 29 0; +#X connect 11 2 15 0; +#X connect 11 3 14 0; +#X connect 11 4 13 0; +#X connect 11 5 10 0; +#X connect 11 6 44 0; +#X connect 11 7 43 0; +#X connect 11 8 9 0; +#X connect 12 0 4 1; +#X connect 13 0 0 0; +#X connect 14 0 0 0; +#X connect 15 0 0 0; +#X connect 16 0 19 0; +#X connect 17 0 16 0; +#X connect 17 1 6 0; +#X connect 18 0 21 0; +#X connect 18 1 22 0; +#X connect 18 2 23 0; +#X connect 18 3 24 0; +#X connect 18 4 25 0; +#X connect 19 0 18 0; +#X connect 20 0 30 1; +#X connect 20 0 41 1; +#X connect 20 0 19 1; +#X connect 21 0 30 0; +#X connect 22 0 30 0; +#X connect 23 0 30 0; +#X connect 24 0 30 0; +#X connect 25 0 30 0; +#X connect 26 0 41 1; +#X connect 26 0 19 1; +#X connect 27 0 38 1; +#X connect 27 0 16 1; +#X connect 28 0 32 0; +#X connect 28 1 38 0; +#X connect 28 1 16 0; +#X connect 28 2 20 0; +#X connect 29 0 36 0; +#X connect 29 0 7 0; +#X connect 29 1 26 0; +#X connect 29 2 27 0; +#X connect 29 2 12 0; +#X connect 30 0 31 0; +#X connect 30 0 32 0; +#X connect 31 0 30 1; +#X connect 32 0 46 0; +#X connect 33 0 39 0; +#X connect 33 0 42 0; +#X connect 34 0 33 0; +#X connect 35 0 34 1; +#X connect 36 0 34 0; +#X connect 37 0 33 1; +#X connect 38 0 41 0; +#X connect 39 0 38 0; +#X connect 39 1 35 0; +#X connect 40 0 21 1; +#X connect 40 1 22 1; +#X connect 40 2 23 1; +#X connect 40 3 24 1; +#X connect 40 4 25 1; +#X connect 41 0 40 0; +#X connect 42 0 33 1; +#X connect 43 0 0 0; +#X connect 44 0 0 0; +#X connect 45 0 4 1; +#X connect 46 0 2 0;