From 8ce9159d509041a36df087b5b7c37768f4b4004f Mon Sep 17 00:00:00 2001
From: Guillem <guillembartrina@gmail.com>
Date: Sat, 8 Aug 2020 18:28:08 +0200
Subject: [PATCH] private patch abstractions [ab]. first approach

---
 externals/pd-lua     |   2 +-
 pd/src/g_canvas.c    | 401 ++++++++++++++++++++++++++++++++++++++++++-
 pd/src/g_canvas.h    |  22 +++
 pd/src/g_editor.c    |  47 +++++
 pd/src/g_readwrite.c |  53 ++++--
 5 files changed, 511 insertions(+), 14 deletions(-)

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/src/g_canvas.c b/pd/src/g_canvas.c
index e29d802d7..ad5956a5b 100644
--- a/pd/src/g_canvas.c
+++ b/pd/src/g_canvas.c
@@ -74,6 +74,8 @@ static t_symbol *canvas_newdirectory = &s_;
 static int canvas_newargc;
 static t_atom *canvas_newargv;
 
+static t_ab_definition *canvas_newabsource = 0;
+
     /* maintain the list of visible toplevels for the GUI's "windows" menu */
 void canvas_updatewindowlist( void)
 {
@@ -136,6 +138,12 @@ void glob_setfilename(void *dummy, t_symbol *filesym, t_symbol *dirsym)
     canvas_newdirectory = dirsym;
 }
 
+/* set the source for the next canvas, it will be an ab instance */
+void canvas_setabsource(t_ab_definition *abdef)
+{
+    canvas_newabsource = abdef;
+}
+
 t_canvas *canvas_getcurrent(void)
 {
     return ((t_canvas *)pd_findbyclass(&s__X, canvas_class));
@@ -463,6 +471,16 @@ t_canvas *canvas_new(void *dummy, t_symbol *sel, int argc, t_atom *argv)
     }
     else x->gl_env = 0;
 
+    x->gl_abdefs = 0;
+    if(canvas_newabsource) /* if absource is set means that this canvas is 
+                                going to be an ab instance */
+    {
+        x->gl_isab = 1;
+        x->gl_absource = canvas_newabsource;
+        canvas_newabsource = 0;
+    }
+    else x->gl_isab = 0;
+
     if (yloc < GLIST_DEFCANVASYLOC)
         yloc = GLIST_DEFCANVASYLOC;
     if (xloc < 0)
@@ -704,6 +722,14 @@ t_symbol *canvas_makebindsym(t_symbol *s)
     return (gensym(buf));
 }
 
+t_symbol *canvas_makebindsym_ab(t_symbol *s)
+{
+    char buf[MAXPDSTRING];
+    snprintf(buf, MAXPDSTRING-1, "ab-%s", s->s_name);
+    buf[MAXPDSTRING-1] = 0;
+    return (gensym(buf));
+}
+
 int garray_getname(t_garray *x, t_symbol **namep);
 
 void canvas_args_to_string(char *namebuf, t_canvas *x)
@@ -992,6 +1018,7 @@ int glist_getfont(t_glist *x)
 }
 
 extern void canvas_group_free(t_pd *x);
+static int canvas_deregister_ab(t_canvas *x, t_ab_definition *a);
 
 void canvas_free(t_canvas *x)
 {
@@ -1033,6 +1060,23 @@ void canvas_free(t_canvas *x)
         canvas_takeofflist(x);
     if (x->gl_svg)                   /* for groups, free the data */
         canvas_group_free(x->gl_svg);
+
+    /* free stored ab definitions */
+    t_ab_definition *d = x->gl_abdefs, *daux;
+    while(d)
+    {
+        daux = d->ad_next;
+        binbuf_free(d->ad_source);
+        freebytes(d, sizeof(t_ab_definition));
+        d = daux;
+    }
+    
+    /* freeing an ab instance */
+    if(x->gl_isab)
+    {
+        x->gl_absource->ad_numinstances--;
+        canvas_deregister_ab(x->gl_owner, x->gl_absource);
+    }
 }
 
 /* ----------------- lines ---------- */
@@ -1566,13 +1610,17 @@ static int canvas_should_bind(t_canvas *x)
 
 static void canvas_bind(t_canvas *x)
 {
-    if (canvas_should_bind(x))
+    if (x->gl_isab) /* if it is an ab instance, we bind it to symbol 'ab-<name>' */
+        pd_bind(&x->gl_pd, canvas_makebindsym_ab(x->gl_name));
+    else if (canvas_should_bind(x))
         pd_bind(&x->gl_pd, canvas_makebindsym(x->gl_name));
 }
 
 static void canvas_unbind(t_canvas *x)
 {
-    if (canvas_should_bind(x))
+    if (x->gl_isab)
+        pd_unbind(&x->gl_pd, canvas_makebindsym_ab(x->gl_name));
+    else if (canvas_should_bind(x))
         pd_unbind(&x->gl_pd, canvas_makebindsym(x->gl_name));
 }
 
@@ -1855,6 +1903,347 @@ void canvas_redrawallfortemplatecanvas(t_canvas *x, int action)
     canvas_redrawallfortemplate(0, action);
 }
 
+/* ------------------------------- ab ------------------------ */
+
+static char ab_templatecanvas[] = "#N canvas;\n";
+
+/* create an ab instance from its source */
+static t_pd *do_create_ab(t_ab_definition *abdef, int argc, t_atom *argv)
+{
+
+    canvas_setargs(argc, argv);
+    int dspstate = canvas_suspend_dsp();
+    glob_setfilename(0, abdef->ad_name, gensym("[ab]"));
+
+    canvas_setabsource(abdef); // set the ab source
+    binbuf_eval(abdef->ad_source, 0, 0, 0);
+    canvas_initbang((t_canvas *)(s__X.s_thing));
+
+    glob_setfilename(0, &s_, &s_);
+    canvas_resume_dsp(dspstate);
+    canvas_popabstraction((t_canvas *)(s__X.s_thing));
+    canvas_setargs(0, 0);
+
+    return(newest);
+}
+
+/* get root canvas crossing ab boundaries, where ab definitions are stored */
+static t_canvas *canvas_getrootfor_ab(t_canvas *x)
+{
+    if (!x->gl_owner || (canvas_isabstraction(x) && !x->gl_isab))
+        return (x);
+    else 
+        return (canvas_getrootfor_ab(x->gl_owner));
+}
+
+/* check if the dependency graph has a cycle, assuming an new edge between parent and current nodes */
+static int ab_check_cycle(t_ab_definition *current, t_ab_definition *parent)
+{
+    if(current == parent)
+        return (1);
+    else
+    {
+        int i, cycle = 0;
+        for(i = 0; !cycle && i < current->ad_numdep; i++)
+        {
+            cycle = ab_check_cycle(current->ad_dep[i], parent);
+        }
+        return (cycle);
+    }
+}
+
+static int definingabs = 0;
+
+/* try to register a new dependency into the dependency graph,
+    returns 0 if a dependency issue is found */
+static int canvas_register_ab(t_canvas *x, t_ab_definition *a)
+{
+    t_canvas *r = canvas_getrootfor_ab(x), *c = x;
+
+    while(c != r)
+    {
+        if(c->gl_isab)
+        {
+            t_ab_definition *f = c->gl_absource;
+
+            int i, found = 0;
+            for(i = 0; !found && i < a->ad_numdep; i++)
+                found = (a->ad_dep[i] == f);
+
+            if(!found)
+            {
+                if(!ab_check_cycle(f, a))
+                {
+                    t_ab_definition **ad = 
+                            (t_ab_definition **)getbytes(sizeof(t_ab_definition *) * (a->ad_numdep + 1));
+                    int *adr = (int *)getbytes(sizeof(int) * (a->ad_numdep + 1));
+                    memcpy(ad, a->ad_dep, sizeof(t_ab_definition *) * a->ad_numdep);
+                    memcpy(adr, a->ad_deprefs, sizeof(int) * a->ad_numdep);
+                    freebytes(a->ad_dep, sizeof(t_ab_definition *) * a->ad_numdep);
+                    freebytes(a->ad_deprefs, sizeof(int) * a->ad_numdep);
+                    ad[a->ad_numdep] = f;
+                    adr[a->ad_numdep] = 1;
+                    a->ad_numdep++;
+                    a->ad_dep = ad;
+                    a->ad_deprefs = adr;
+                }
+                else return (0);
+            }
+            else
+            {
+                a->ad_deprefs[i-1]++;
+            }
+        }
+        c = c->gl_owner;
+    }
+    return (1);
+}
+
+static int canvas_deregister_ab(t_canvas *x, t_ab_definition *a)
+{
+    t_canvas *r = canvas_getrootfor_ab(x), *c = x;
+
+    while(c != r)
+    {
+        if(c->gl_isab)
+        {
+            t_ab_definition *f = c->gl_absource;
+
+            int i, found = 0;
+            for(i = 0; !found && i < a->ad_numdep; i++)
+                found = (a->ad_dep[i] == f);
+
+            if(found)
+            {
+                a->ad_deprefs[i-1]--;
+
+                if(!a->ad_deprefs[i-1])
+                {
+                    t_ab_definition **ad =
+                            (t_ab_definition **)getbytes(sizeof(t_ab_definition *) * (a->ad_numdep - 1));
+                    int *adr = (int *)getbytes(sizeof(int) * (a->ad_numdep - 1));
+                    memcpy(ad, a->ad_dep, sizeof(t_ab_definition *) * (i-1));
+                    memcpy(ad+(i-1), a->ad_dep+i, sizeof(t_ab_definition *) * (a->ad_numdep - i));
+                    memcpy(adr, a->ad_deprefs, sizeof(int) * (i-1));
+                    memcpy(adr+(i-1), a->ad_deprefs+i, sizeof(int) * (a->ad_numdep - i));
+                    freebytes(a->ad_dep, sizeof(t_ab_definition *) * a->ad_numdep);
+                    freebytes(a->ad_deprefs, sizeof(int) * a->ad_numdep);
+                    a->ad_numdep--;
+                    a->ad_dep = ad;
+                    a->ad_deprefs = adr;
+                }
+            }
+            else bug("canvas_deregister_ab");
+        }
+        c = c->gl_owner;
+    }
+    return (1);
+}
+
+/* reloads ab instances */
+void canvas_reload_ab(t_canvas *x)
+{
+    t_canvas *c = canvas_getrootfor_ab(x);
+    int dspwas = canvas_suspend_dsp();
+    glist_amreloadingabstractions = 1;
+    canvas_reload_ab_rec(c, x->gl_absource, &x->gl_obj);
+    glist_amreloadingabstractions = 0;
+    canvas_resume_dsp(dspwas);
+    canvas_dirty(c, 1);
+}
+
+/* tries to find an ab definition given its name */
+static t_ab_definition *canvas_find_ab(t_canvas *x, t_symbol *name)
+{
+    t_canvas *c = canvas_getrootfor_ab(x);
+    t_ab_definition* d;
+    for (d = c->gl_abdefs; d; d = d->ad_next)
+    {
+        if (d->ad_name == name)
+            return d;
+    }
+    return 0;
+}
+
+/* adds a new ab definition. returns the definition if it has been added, 0 otherwise */ 
+static t_ab_definition *canvas_add_ab(t_canvas *x, t_symbol *name, t_binbuf *source)
+{
+    if(!canvas_find_ab(x, name))
+    {
+        t_canvas *c = canvas_getrootfor_ab(x);
+        t_ab_definition *abdef = (t_ab_definition *)getbytes(sizeof(t_ab_definition));
+
+        abdef->ad_name = name;
+        abdef->ad_source = source;
+        abdef->ad_numinstances = 0;
+        abdef->ad_numdep = 0;
+        abdef->ad_dep = (t_ab_definition **)getbytes(0);
+        abdef->ad_deprefs = (int *)getbytes(0);
+        abdef->ad_visflag = 0;
+
+        abdef->ad_next = c->gl_abdefs;
+        c->gl_abdefs = abdef;
+        return (abdef);
+    }
+    return (0);    
+}
+
+/* given the ab definitions list, returns its topological ordering */
+
+static int currvisflag = 0;
+
+static void ab_topological_sort_rec(t_ab_definition *a, t_ab_definition **stack, int *head)
+{
+    a->ad_visflag = currvisflag;
+
+    int i;
+    for(i = 0; i < a->ad_numdep; i++)
+    {
+        if(a->ad_dep[i]->ad_visflag != currvisflag)
+            ab_topological_sort_rec(a->ad_dep[i], stack, head);
+    }
+
+    stack[*head] = a;
+    (*head)++;
+}
+
+static void ab_topological_sort(t_ab_definition *abdefs, t_ab_definition **stack, int *head)
+{
+    currvisflag++;
+    t_ab_definition *abdef;
+    for(abdef = abdefs; abdef; abdef = abdef->ad_next)
+    {
+        if(abdef->ad_visflag != currvisflag)
+            ab_topological_sort_rec(abdef, stack, head);
+    }
+}
+
+/* saves all the ab definition within the scope into the b binbuf,
+    they are sorted topollogially before saving in order to get exactly the 
+    same state (ab objects that can't be instantiated due dependencies) when reloading the file */
+void canvas_saveabdefinitionsto(t_canvas *x, t_binbuf *b)
+{
+    if(!x->gl_abdefs)
+        return;
+
+    int numabdefs = 0;
+    t_ab_definition *abdef;
+    for(abdef = x->gl_abdefs; abdef; abdef = abdef->ad_next)
+        numabdefs++;
+
+    t_ab_definition **stack =
+        (t_ab_definition **)getbytes(sizeof(t_ab_definition *) * numabdefs);
+    int head = 0;
+    ab_topological_sort(x->gl_abdefs, stack, &head);
+
+    binbuf_addv(b, "ssi;", gensym("#X"), gensym("frame_ab"), 1);
+    int i;
+    for(i = 0; i < head; i++)
+    {
+        if(stack[i]->ad_numinstances)
+        {
+            binbuf_add(b, binbuf_getnatom(stack[i]->ad_source), binbuf_getvec(stack[i]->ad_source));
+            binbuf_addv(b, "sss", gensym("#X"), gensym("define_ab"), stack[i]->ad_name);
+
+            int j;
+            for(j = 0; j < stack[i]->ad_numdep; j++)
+                binbuf_addv(b, "s", stack[i]->ad_dep[j]->ad_name);
+            binbuf_addsemi(b);
+        }
+    }
+    binbuf_addv(b, "ssi;", gensym("#X"), gensym("frame_ab"), 0);
+
+    freebytes(stack, sizeof(t_ab_definition *) * numabdefs);
+}
+
+/* saves last canvas as an ab definition */
+static void canvas_define_ab(t_canvas *x, t_symbol *s, int argc, t_atom *argv)
+{
+    canvas_pop(x, 0);
+
+    t_canvas *c = canvas_getcurrent();
+    t_symbol *name = argv[0].a_w.w_symbol;
+    t_binbuf *source = binbuf_new();
+    x->gl_env = 0xF1A6; //to save it as a root canvas
+    mess1(&((t_text *)x)->te_pd, gensym("saveto"), source);
+    x->gl_env = 0;
+    
+    t_ab_definition *n;
+    if(!(n = canvas_add_ab(c, name, source)))
+    {
+        error("canvas_define_ab: ab definition for '%s' already exists, skipping", 
+                name->s_name);
+    }
+    else
+    {
+        if(argc > 1)
+        {
+            freebytes(n->ad_dep, 0);
+            freebytes(n->ad_deprefs, 0);
+            n->ad_numdep = argc-1;
+            n->ad_dep =
+                (t_ab_definition **)getbytes(sizeof(t_ab_definition *) * n->ad_numdep);
+            n->ad_deprefs = (int *)getbytes(sizeof(int) * n->ad_numdep);
+
+            int i;
+            for(i = 0; i < n->ad_numdep; i++) n->ad_deprefs[i] = 0;
+            for(i = 1; i < argc; i++)
+            {
+                t_symbol *abname = argv[i].a_w.w_symbol;
+                t_ab_definition *absource = canvas_find_ab(c, abname);
+                if(!absource) { bug("canvas_define_ab"); return; }
+                n->ad_dep[i-1] = absource;
+            }
+        }
+    }
+    
+    pd_free(x);
+}
+
+static void canvas_frame_ab(t_canvas *x, t_float val)
+{
+    definingabs = val;
+}
+
+/* creator for "ab" objects */
+static void *ab_new(t_symbol *s, int argc, t_atom *argv)
+{
+    if(definingabs)
+        return (0);
+
+    t_canvas *c = canvas_getcurrent();
+
+    if (!argc || argv[0].a_type != A_SYMBOL)
+    {
+        error("ab_new: not recognized syntax. Usage: ab <name>");
+        newest = 0;
+    }
+    else
+    {
+        t_symbol *name = argv[0].a_w.w_symbol;
+        t_ab_definition *source;
+
+        if(!(source = canvas_find_ab(c, name)))
+        {
+            t_binbuf *b = binbuf_new();
+            binbuf_text(b, ab_templatecanvas, strlen(ab_templatecanvas));
+            source = canvas_add_ab(c, name, b);
+        }
+
+        if(canvas_register_ab(c, source))
+        {
+            newest = do_create_ab(source, argc-1, argv+1);
+            source->ad_numinstances++;
+        }
+        else
+        {
+            error("ab_new: can't insantiate ab within itself");
+            newest = 0;
+        }
+    }
+    return (newest);
+}
+
 /* ------------------------------- declare ------------------------ */
 
 /* put "declare" objects in a patch to tell it about the environment in
@@ -2695,6 +3084,14 @@ void g_canvas_setup(void)
     class_addcreator((t_newmethod)table_new, gensym("table"),
         A_DEFSYM, A_DEFFLOAT, 0);
 
+/*---------------------------- ab ------------------- */
+
+    class_addcreator((t_newmethod)ab_new, gensym("ab"), A_GIMME, 0);
+    class_addmethod(canvas_class, (t_method)canvas_define_ab,
+        gensym("define_ab"), A_GIMME, 0);
+    class_addmethod(canvas_class, (t_method)canvas_frame_ab,
+        gensym("frame_ab"), A_FLOAT, 0);
+
 /*---------------------------- declare ------------------- */
     declare_class = class_new(gensym("declare"), (t_newmethod)declare_new,
         (t_method)declare_free, sizeof(t_declare), CLASS_NOINLET, A_GIMME, 0);
diff --git a/pd/src/g_canvas.h b/pd/src/g_canvas.h
index 964d8fab8..5d86945f9 100644
--- a/pd/src/g_canvas.h
+++ b/pd/src/g_canvas.h
@@ -164,6 +164,23 @@ typedef struct _tick    /* where to put ticks on x or y axes */
     int k_lperb;        /* little ticks per big; 0 if no ticks to draw */
 } t_tick;
 
+/* the t_ab_definition structure holds an ab definiton and all the attributes we need
+    to handle them */
+typedef struct _ab_definition
+{
+    t_symbol *ad_name;      /* id for the ab definition */
+    t_binbuf *ad_source;    /* binbuf where source is stored */
+    int ad_numinstances;    /* the num of instances of this abstraction */
+    struct _ab_definition *ad_next; /* next ab definition */
+    
+    /* dependency graph stuff */
+    int ad_numdep;      /* number of other ab definitions that it depends on */
+    struct _ab_definition **ad_dep;     /* the actual ab defintitions */
+    int *ad_deprefs;
+    int ad_visflag;     /* visited flag for topological sort algorithm */
+} t_ab_definition;
+
+
 /* the t_glist structure, which describes a list of elements that live on an
 area of a window.
 
@@ -232,6 +249,11 @@ struct _glist
     t_symbol *gl_templatesym; /* for "canvas" data type */
     t_word *gl_vec;            /* for "canvas" data type */
     t_gpointer gl_gp;            /* parent for "canvas" data type */
+
+    unsigned int gl_isab:1;         /* is an ab instance */
+    t_ab_definition *gl_absource;   /* ab definition pointer, 
+                                        in the case it is an ab instance */
+    t_ab_definition *gl_abdefs;     /* stored ab definitions */
 };
 
 #define gl_gobj gl_obj.te_g
diff --git a/pd/src/g_editor.c b/pd/src/g_editor.c
index 15dfc84f0..8811cfb0a 100644
--- a/pd/src/g_editor.c
+++ b/pd/src/g_editor.c
@@ -1401,6 +1401,53 @@ void canvas_reload(t_symbol *name, t_symbol *dir, t_gobj *except)
     canvas_resume_dsp(dspwas);
 }
 
+/* recursive ab reload method */
+void canvas_reload_ab_rec(t_canvas *x, t_ab_definition *a, t_gobj *e)
+{
+    t_gobj *g;
+    int i, nobj = glist_getindex(x, 0);
+    int hadwindow = x->gl_havewindow, found = 0;
+
+    for (g = x->gl_list, i = 0; g && i < nobj; i++)
+    {
+        if(g != e && pd_class(&g->g_pd) == canvas_class && canvas_isabstraction((t_canvas *)g)
+            && ((t_canvas *)g)->gl_isab && ((t_canvas *)g)->gl_absource == a)
+        {
+            canvas_create_editor(x);
+
+            if (!x->gl_havewindow) //necessary?
+            {
+                canvas_vis(glist_getcanvas(x), 1);
+            }
+            if (!found)
+            {
+                glist_noselect(x);
+                found = 1;
+            }
+            glist_select(x, g);
+        }
+        g = g->g_next;
+    }
+    if (found)
+    {
+        canvas_cut(x);
+        canvas_undo_undo(x);
+        glist_noselect(x);
+    }
+
+    for (g = x->gl_list, i = 0; g && i < nobj; i++)
+    {
+        if(pd_class(&g->g_pd) == canvas_class
+            && (!canvas_isabstraction((t_canvas *)g) 
+                    || (((t_canvas *)g)->gl_isab 
+                        && (((t_canvas *)g)->gl_absource != a))))
+            canvas_reload_ab_rec((t_canvas *)g, a, e);
+        g = g->g_next;
+    }
+    if (!hadwindow && x->gl_havewindow)
+        canvas_vis(glist_getcanvas(x), 0);
+}
+
 /* --------- 6. apply  ----------- */
 
 typedef struct _undo_apply        
diff --git a/pd/src/g_readwrite.c b/pd/src/g_readwrite.c
index 672d536bb..2a37cedf7 100644
--- a/pd/src/g_readwrite.c
+++ b/pd/src/g_readwrite.c
@@ -103,6 +103,7 @@ void canvas_saved(t_glist *x, t_symbol *s, int argc, t_atom *argv)
 }
 
 void canvas_savedeclarationsto(t_canvas *x, t_binbuf *b);
+void canvas_saveabdefinitionsto(t_canvas *x, t_binbuf *b);
 
     /* the following routines read "scalars" from a file into a canvas. */
 
@@ -830,6 +831,9 @@ static void canvas_saveto(t_canvas *x, t_binbuf *b)
         }
         canvas_savedeclarationsto(x, b);
     }
+
+    canvas_saveabdefinitionsto(x, b);
+
     for (y = x->gl_list; y; y = y->g_next)
         gobj_save(y, b);
 
@@ -976,26 +980,53 @@ static void canvas_savetofile(t_canvas *x, t_symbol *filename, t_symbol *dir,
     binbuf_free(b);
 }
 
+void canvas_reload_ab(t_canvas *x);
+
+/* updates the shared ab definition and reloads all the instances */
+static void canvas_save_ab(t_canvas *x, t_floatarg fdestroy)
+{
+    if(!x->gl_absource) bug("canvas_save_ab");
+
+    t_binbuf *b = binbuf_new();
+    canvas_savetemplatesto(x, b, 1);
+    canvas_saveto(x, b);
+
+    binbuf_free(x->gl_absource->ad_source);
+    x->gl_absource->ad_source = b;
+
+    canvas_dirty(x, 0);
+    canvas_reload_ab(x);
+
+    if (fdestroy != 0) //necessary?
+        vmess(&x->gl_pd, gensym("menuclose"), "f", 1.);
+}
+
 static void canvas_menusaveas(t_canvas *x, t_floatarg fdestroy)
 {
     t_canvas *x2 = canvas_getrootfor(x);
-    gui_vmess("gui_canvas_saveas", "xssi",
-        x2,
-        (strncmp(x2->gl_name->s_name, "Untitled", 8) ?
-            x2->gl_name->s_name : "title"),
-        canvas_getdir(x2)->s_name,
-        fdestroy != 0);
+    if(!x->gl_isab)
+        gui_vmess("gui_canvas_saveas", "xssi",
+            x2,
+            (strncmp(x2->gl_name->s_name, "Untitled", 8) ?
+                x2->gl_name->s_name : "title"),
+            canvas_getdir(x2)->s_name,
+            fdestroy != 0);
+    else canvas_save_ab(x2, fdestroy);
 }
 
 static void canvas_menusave(t_canvas *x, t_floatarg fdestroy)
 {
     t_canvas *x2 = canvas_getrootfor(x);
     char *name = x2->gl_name->s_name;
-    if (*name && strncmp(name, "Untitled", 8)
-            && (strlen(name) < 4 || strcmp(name + strlen(name)-4, ".pat")
-                || strcmp(name + strlen(name)-4, ".mxt")))
-            canvas_savetofile(x2, x2->gl_name, canvas_getdir(x2), fdestroy);
-    else canvas_menusaveas(x2, fdestroy);
+    if(!x->gl_isab)
+    {
+        if (*name && strncmp(name, "Untitled", 8)
+                && (strlen(name) < 4 || strcmp(name + strlen(name)-4, ".pat")
+                    || strcmp(name + strlen(name)-4, ".mxt")))
+                canvas_savetofile(x2, x2->gl_name, canvas_getdir(x2), fdestroy);
+        else canvas_menusaveas(x2, fdestroy);
+    }
+    else canvas_save_ab(x2, fdestroy);
 }
 
 static void canvas_menuprint(t_canvas *x)
-- 
GitLab