From 8b1a5261bbe14f986ebb1117556a4b8544acd39b Mon Sep 17 00:00:00 2001
From: Guillem <guillembartrina@gmail.com>
Date: Wed, 5 Aug 2020 16:19:00 +0200
Subject: [PATCH] encapsulate feature - squashed

---
 externals/pd-lua                  |   2 +-
 pd/nw/index.js                    |   1 +
 pd/nw/locales/en/translation.json |   2 +
 pd/nw/pd_canvas.js                |   4 +
 pd/nw/pd_menus.js                 |   6 +
 pd/nw/pd_shortcuts.js             |   1 +
 pd/src/g_editor.c                 | 266 ++++++++++++++++++++++++++++++
 7 files changed, 281 insertions(+), 1 deletion(-)

diff --git a/externals/pd-lua b/externals/pd-lua
index b767c65b6..8eaaaa3c3 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 8c777871f..1a173c91e 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 e7a851019..20b33e60c 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 e7bc1ae69..c3374b9e1 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 cfbc48f25..79e031410 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 4e140856c..a0f2bfd60 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 15dfc84f0..e2e2c65ff 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,
-- 
GitLab