From 7301e935c56ab1511c775ffd2dbbb8da3b6b3a44 Mon Sep 17 00:00:00 2001
From: Guillem <guillembartrina@gmail.com>
Date: Fri, 4 Sep 2020 13:13:35 +0200
Subject: [PATCH] add mechanism that visually marks abstraction objects that
 are edited but unsaved, and all their ancestors

---
 pd/nw/css/default.css |  8 ++++++++
 pd/nw/pdgui.js        | 10 ++++++++++
 pd/src/g_canvas.c     | 26 ++++++++++++++++++++++++++
 pd/src/g_canvas.h     |  3 +++
 pd/src/g_editor.c     | 13 +++++++++++++
 pd/src/g_text.c       | 10 ++++++++++
 6 files changed, 70 insertions(+)

diff --git a/pd/nw/css/default.css b/pd/nw/css/default.css
index 5abd5f15e..396a40fad 100644
--- a/pd/nw/css/default.css
+++ b/pd/nw/css/default.css
@@ -401,6 +401,14 @@ text {
     stroke: #ccc;
 }
 
+.obj .border.dirty {
+    stroke: #ff0000;
+}
+
+.obj .border.subdirty {
+    stroke: #ff8800;
+}
+
 .comment .border {
     fill: none;
 }
diff --git a/pd/nw/pdgui.js b/pd/nw/pdgui.js
index d172ee900..465593cea 100644
--- a/pd/nw/pdgui.js
+++ b/pd/nw/pdgui.js
@@ -2659,6 +2659,16 @@ function gui_gobj_deselect(cid, tag) {
     });
 }
 
+function gui_gobj_dirty(cid, tag, state) {
+    gui(cid).get_gobj(tag, function(e) {
+        var border = e.querySelector(".border");
+        border.classList.remove("dirty");
+        border.classList.remove("subdirty");
+        if(state === 1) border.classList.add("dirty");
+        else if(state === 2) border.classList.add("subdirty");
+    });
+}
+
 function gui_canvas_emphasize(cid) {
     gui(cid).get_elem("patchsvg", function(e) {
         // raise the window
diff --git a/pd/src/g_canvas.c b/pd/src/g_canvas.c
index e29d802d7..f1a8279e2 100644
--- a/pd/src/g_canvas.c
+++ b/pd/src/g_canvas.c
@@ -463,6 +463,8 @@ t_canvas *canvas_new(void *dummy, t_symbol *sel, int argc, t_atom *argv)
     }
     else x->gl_env = 0;
 
+    x->gl_subdirties = 0;
+
     if (yloc < GLIST_DEFCANVASYLOC)
         yloc = GLIST_DEFCANVASYLOC;
     if (xloc < 0)
@@ -757,6 +759,27 @@ void canvas_reflecttitle(t_canvas *x)
         namebuf, canvas_getdir(x)->s_name, x->gl_dirty);
 }
 
+/* --------------------- */
+
+/* climbs up to the root canvas while enabling or disabling visual markings for dirtiness
+    of traversed canvases */
+void canvas_dirtyclimb(t_canvas *x, int n)
+{
+    if (x->gl_owner)
+    {
+        gobj_dirty(&x->gl_gobj, x->gl_owner,
+            (n ? 1 : (x->gl_subdirties ? 2 : 0)));
+        x = x->gl_owner;
+        while(x->gl_owner)
+        {
+            x->gl_subdirties += ((unsigned)n ? 1 : -1);
+            if(!x->gl_dirty)
+                gobj_dirty(&x->gl_gobj, x->gl_owner, (x->gl_subdirties ? 2 : 0));
+            x = x->gl_owner;
+        }
+    }
+}
+
     /* mark a glist dirty or clean */
 void canvas_dirty(t_canvas *x, t_floatarg n)
 {
@@ -768,6 +791,9 @@ void canvas_dirty(t_canvas *x, t_floatarg n)
         x2->gl_dirty = n;
         if (x2->gl_havewindow)
             canvas_reflecttitle(x2);
+
+        /* set dirtiness visual markings */
+        canvas_dirtyclimb(x2, (unsigned)n);
     }
 }
 
diff --git a/pd/src/g_canvas.h b/pd/src/g_canvas.h
index 964d8fab8..35669c276 100644
--- a/pd/src/g_canvas.h
+++ b/pd/src/g_canvas.h
@@ -232,6 +232,8 @@ 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 */
+
+    int gl_subdirties;     /* number of descending dirty abstractions */
 };
 
 #define gl_gobj gl_obj.te_g
@@ -436,6 +438,7 @@ EXTERN void gobj_save(t_gobj *x, t_binbuf *b);
 EXTERN void gobj_properties(t_gobj *x, struct _glist *glist);
 EXTERN void gobj_save(t_gobj *x, t_binbuf *b);
 EXTERN int gobj_shouldvis(t_gobj *x, struct _glist *glist);
+EXTERN void gobj_dirty(t_gobj *x, t_glist *g, int state);
 
 /* -------------------- functions on glists --------------------- */
 EXTERN t_glist *glist_new( void);
diff --git a/pd/src/g_editor.c b/pd/src/g_editor.c
index 15dfc84f0..088890360 100644
--- a/pd/src/g_editor.c
+++ b/pd/src/g_editor.c
@@ -1288,6 +1288,7 @@ void canvas_undo_paste(t_canvas *x, void *z, int action)
     }
 }
 
+void canvas_dirtyclimb(t_canvas *x, int n);
 int clone_match(t_pd *z, t_symbol *name, t_symbol *dir);
 
     /* recursively check for abstractions to reload as result of a save. 
@@ -1313,6 +1314,11 @@ static void glist_doreload(t_glist *gl, t_symbol *name, t_symbol *dir,
             canvas_isabstraction((t_canvas *)g) &&
                 ((t_canvas *)g)->gl_name == name &&
                     canvas_getdir((t_canvas *)g) == dir);
+
+        /* remove dirtiness visual markings */
+        if(remakeit && ((t_canvas *)g)->gl_dirty)
+            canvas_dirtyclimb((t_canvas *)g, 0);
+
             /* also remake it if it's a "clone" with that name */
         if (pd_class(&g->g_pd) == clone_class &&
             clone_match(&g->g_pd, name, dir))
@@ -5846,6 +5852,13 @@ static void gobj_emphasize(t_glist *g, t_gobj *x)
     gui_vmess("gui_gobj_emphasize", "xs", g, rtext_gettag(y));
 }
 
+    /* tell the gui to mark a gobj as dirty (change border color) */
+void gobj_dirty(t_gobj *x, t_glist *g, int state)
+{
+    t_rtext *y = glist_findrtext(g, (t_text *)x);
+    gui_vmess("gui_gobj_dirty", "xsi", g, rtext_gettag(y), state);
+}
+
 static int glist_dofinderror(t_glist *gl, void *error_object)
 {
     t_gobj *g;
diff --git a/pd/src/g_text.c b/pd/src/g_text.c
index 2a1ef4879..6c875164c 100644
--- a/pd/src/g_text.c
+++ b/pd/src/g_text.c
@@ -2121,6 +2121,16 @@ static void text_vis(t_gobj *z, t_glist *glist, int vis)
                 text_drawborder(x, glist, rtext_gettag(y),
                     rtext_width(y), rtext_height(y), 1);
                 rtext_draw(y);
+
+                /* check whether we have to tell the gui to mark
+                    (border color) the gobj as dirty or not */
+                if(pd_class(&x->te_pd) == canvas_class)
+                {
+                    if (((t_canvas *)x)->gl_dirty)
+                        gobj_dirty(x, glist, 1);
+                    else if (((t_canvas *)x)->gl_subdirties)
+                        gobj_dirty(x, glist, 2);
+                }
             }
         }
         else
-- 
GitLab