diff --git a/pd/nw/locales/de/translation.json b/pd/nw/locales/de/translation.json index 69265bfb63f1c1b15d03fda5505a0f7f4b0606cb..798fc5cb9852ab30480f068447d0c7e3eb5868ab 100644 --- a/pd/nw/locales/de/translation.json +++ b/pd/nw/locales/de/translation.json @@ -297,6 +297,10 @@ "none": "Keinen", "none_tt": "Ersetze keinen der Subpatches" }, + "warning": { + "unsaved_tt": "Es gibt ein ungesichertes Exemplar dieser Abstraktion", + "multipleunsaved_tt": "Es gibt ein weiteres ungesichertes Exemplar dieser Abstraktion" + }, "find": { "placeholder": "Suche im Patch", "search": "Suche", diff --git a/pd/nw/locales/en/translation.json b/pd/nw/locales/en/translation.json index 5a161dce5dfecc453737810408be61c45e06f961..2a24c5494589e9f1628308087fe25ff875d08527 100644 --- a/pd/nw/locales/en/translation.json +++ b/pd/nw/locales/en/translation.json @@ -297,6 +297,10 @@ "none": "None", "none_tt": "Do not replace any subpatch" }, + "warning": { + "unsaved_tt": "There is an unsaved edited instance of this abstraction", + "multipleunsaved_tt": "There is another unsaved edited instance of this abstraction" + }, "find": { "placeholder": "Search in Canvas", "search": "Search", diff --git a/pd/nw/locales/fr/translation.json b/pd/nw/locales/fr/translation.json index 8a34087870ad86fb7a26f84036bff88fba8ce153..6e476f33beca113d02f129c80f7236862c11a8fc 100644 --- a/pd/nw/locales/fr/translation.json +++ b/pd/nw/locales/fr/translation.json @@ -297,6 +297,10 @@ "none": "Aucun", "none_tt": "Ne remplacer aucun sous-patch" }, + "warning": { + "unsaved_tt": "Il existe une instance modifiée non enregistrée de cette abstraction", + "multipleunsaved_tt": "Il existe une autre instance modifiée non enregistrée de cette abstraction" + }, "find": { "placeholder": "Chercher dans le Canevas", "search": "Chercher", diff --git a/pd/nw/pd_canvas.html b/pd/nw/pd_canvas.html index 7ba063a2740a95b6116765e9385176ee7b61b4ac..d04950e96f1ecf1e27559a98cadda8b79b3aaa51 100644 --- a/pd/nw/pd_canvas.html +++ b/pd/nw/pd_canvas.html @@ -102,6 +102,9 @@ </button> </div> </dialog> + <div style="position: fixed; right: 2%; top: 2%; "> + <strong id="canvas_warning" style="display: none; text-align: right; -webkit-user-select: none; cursor: pointer;">!</strong> + </div> <div id="hscroll" style="position: fixed; left: 0px; bottom: 0px; border-radius: 0px; width: 10px; height: 5px; visibility: hidden;"></div> <div id="vscroll" style="position: fixed; right: 0px; top: 0px; border-radius: 0px; width: 5px; height: 10px; visibility: hidden;"></div> <script type="text/javascript" src="./pd_canvas.js"></script> diff --git a/pd/nw/pd_canvas.js b/pd/nw/pd_canvas.js index 60278d79f958af3e413dea8b8bc2e5f525c029e6..a0bfb1a2e49a248c2d19ed3884943002f3f463dd 100644 --- a/pd/nw/pd_canvas.js +++ b/pd/nw/pd_canvas.js @@ -1336,6 +1336,9 @@ function register_window_id(cid, attr_array) { // we check the title_queue to see if our title now contains an asterisk // (which is the visual cue for "dirty") + // Enable/disable the warning for multiple dirty instances + pdgui.gui_canvas_warning(cid, attr_array.warid); + // Two possibilities for handling this better: // have a representation of canvas attys in pdgui.js (editmode, dirty, etc.) // or diff --git a/pd/nw/pdgui.js b/pd/nw/pdgui.js index a23dc55d083141ea6d5e67f5c36ee8bf9f376450..5ef0918ddd06a96d9aaa4d5482f0fd74b7fc9064 100644 --- a/pd/nw/pdgui.js +++ b/pd/nw/pdgui.js @@ -1760,7 +1760,7 @@ function create_window(cid, type, width, height, xpos, ypos, attr_array) { } // create a new canvas -function gui_canvas_new(cid, width, height, geometry, grid, zoom, editmode, name, dir, dirty_flag, hide_scroll, hide_menu, has_toplevel_scalars, cargs) { +function gui_canvas_new(cid, width, height, geometry, grid, zoom, editmode, name, dir, dirty_flag, warid, hide_scroll, hide_menu, has_toplevel_scalars, cargs) { // hack for buggy tcl popups... should go away for node-webkit //reset_ctrl_on_popup_window @@ -1817,6 +1817,7 @@ function gui_canvas_new(cid, width, height, geometry, grid, zoom, editmode, name name: name, dir: dir, dirty: dirty_flag, + warid: warid, args: cargs, zoom: zoom, editmode: editmode, @@ -2860,6 +2861,35 @@ function gui_gobj_dirty(cid, tag, state) { }); } +function gui_canvas_warning(cid, warid) { + var warning = get_item(cid, "canvas_warning"); + switch(warid) + { + case 0: + warning.style.setProperty("display", "none"); + break; + case 1: + warning.title = lang.get_local_string("canvas.warning.unsaved_tt"); + warning.onclick = function(){ pdsend(cid, "showdirty"); } + warning.style.setProperty("color", "coral"); + warning.style.setProperty("font-size", "x-large"); + warning.style.setProperty("display", "inline"); + break; + case 2: + warning.title = lang.get_local_string("canvas.warning.multipleunsaved_tt"); + warning.onclick = function(){ pdsend(cid, "showdirty"); } + warning.style.setProperty("color", "red"); + warning.style.setProperty("font-size", "xx-large"); + warning.style.setProperty("display", "inline"); + break; + + default: + break; + } +} + +exports.gui_canvas_warning = gui_canvas_warning; + 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 c88379b5cfee55671c85b168add2bbb881ae39c6..8cabb3506b34b0b5175ddeca4ed106f7df352003 100644 --- a/pd/src/g_canvas.c +++ b/pd/src/g_canvas.c @@ -396,6 +396,8 @@ static int calculate_zoom(t_float zoom_hack) return zoom; } +int canvas_dirty_broadcast_all(t_symbol *name, t_symbol *dir, int mess); + /* 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.) */ @@ -472,6 +474,7 @@ t_canvas *canvas_new(void *dummy, t_symbol *sel, int argc, t_atom *argv) else x->gl_env = 0; x->gl_subdirties = 0; + x->gl_dirties = 0; if (yloc < GLIST_DEFCANVASYLOC) yloc = GLIST_DEFCANVASYLOC; @@ -522,6 +525,14 @@ t_canvas *canvas_new(void *dummy, t_symbol *sel, int argc, t_atom *argv) canvas_field_vec = NULL; canvas_field_gp = NULL; + /* in the case it is an abstraction (gl_env is not null) + get the number of dirty instances of this same abstraction */ + if(x->gl_env) + { + x->gl_dirties = canvas_dirty_broadcast_all(x->gl_name, + canvas_getdir(x), 0); + } + return(x); } @@ -792,6 +803,100 @@ void canvas_dirtyclimb(t_canvas *x, int n) } } +/* the following functions are used to broadcast messages to all instances of + a specific abstraction (either file-based or ab). + the state of these instances change accoring to the message sent. */ + +void clone_iterate(t_pd *z, t_canvas_iterator it, void* data); +int clone_match(t_pd *z, t_symbol *name, t_symbol *dir); + +static void canvas_dirty_common(t_canvas *x, int mess) +{ + if(mess == 2) + { + if(x->gl_dirty) + { + if(!x->gl_havewindow) canvas_vis(x, 1); + gui_vmess("gui_canvas_emphasize", "x", x); + } + } + else + { + x->gl_dirties += mess; + if(x->gl_havewindow) + canvas_warning(x, (x->gl_dirties > 1 ? + (x->gl_dirty ? 2 : 1) + : (x->gl_dirties ? !x->gl_dirty : 0))); + } +} + +/* packed data passing structure for canvas_dirty_broadcast */ +typedef struct _dirty_broadcast_data +{ + t_symbol *name; + t_symbol *dir; + int mess; + int *res; /* return value */ +} t_dirty_broadcast_data; + +static void canvas_dirty_deliver_packed(t_canvas *x, t_dirty_broadcast_data *data) +{ + *data->res += (x->gl_dirty > 0); + canvas_dirty_common(x, data->mess); +} + +static int canvas_dirty_broadcast_packed(t_canvas *x, t_dirty_broadcast_data *data); + +static int canvas_dirty_broadcast(t_canvas *x, t_symbol *name, t_symbol *dir, int mess) +{ + int res = 0; + t_gobj *g; + for (g = x->gl_list; g; g = g->g_next) + { + if(pd_class(&g->g_pd) == canvas_class) + { + if(canvas_isabstraction((t_canvas *)g) + && ((t_canvas *)g)->gl_name == name + && canvas_getdir((t_canvas *)g) == dir) + { + res += (((t_canvas *)g)->gl_dirty > 0); + canvas_dirty_common((t_canvas *)g, mess); + } + else + res += canvas_dirty_broadcast((t_canvas *)g, name, dir, mess); + } + else if(pd_class(&g->g_pd) == clone_class) + { + int cres = 0; + t_dirty_broadcast_data data; + data.name = name; data.dir = dir; data.mess = mess; data.res = &cres; + if(clone_match(&g->g_pd, name, dir)) + { + clone_iterate(&g->g_pd, canvas_dirty_deliver_packed, &data); + } + else + { + clone_iterate(&g->g_pd, canvas_dirty_broadcast_packed, &data); + } + res += cres; + } + } + return (res); +} + +static int canvas_dirty_broadcast_packed(t_canvas *x, t_dirty_broadcast_data *data) +{ + *data->res = canvas_dirty_broadcast(x, data->name, data->dir, data->mess); +} + +int canvas_dirty_broadcast_all(t_symbol *name, t_symbol *dir, int mess) +{ + int res = 0; + t_canvas *x; + for (x = pd_this->pd_canvaslist; x; x = x->gl_next) + res += canvas_dirty_broadcast(x, name, dir, mess); + return (res); +} /* mark a glist dirty or clean */ void canvas_dirty(t_canvas *x, t_floatarg n) { @@ -806,6 +911,15 @@ void canvas_dirty(t_canvas *x, t_floatarg n) /* set dirtiness visual markings */ canvas_dirtyclimb(x2, (unsigned)n); + /* in the case it is an abstraction, we tell all other + instances that there is eiher one more dirty instance or + one less dirty instance */ + if(canvas_isabstraction(x2) + && (x2->gl_owner || x2->gl_isclone)) + { + canvas_dirty_broadcast_all(x2->gl_name, canvas_getdir(x2), + (x2->gl_dirty ? 1 : -1)); + } } } @@ -1034,6 +1148,15 @@ extern void canvas_group_free(t_pd *x); void canvas_free(t_canvas *x) { //fprintf(stderr,"canvas_free %zx\n", (t_uint)x); + + /* in the case it is a dirty abstraction, we tell all other + instances that there is one less dirty instance */ + if(canvas_isabstraction(x) && x->gl_dirty + && (x->gl_owner || x->gl_isclone)) + { + canvas_dirty_broadcast_all(x->gl_name, canvas_getdir(x), -1); + } + t_gobj *y; int dspstate = canvas_suspend_dsp(); @@ -1893,6 +2016,13 @@ void canvas_redrawallfortemplatecanvas(t_canvas *x, int action) canvas_redrawallfortemplate(0, action); } +/* --------- */ + +static void canvas_showdirty(t_canvas *x) +{ + canvas_dirty_broadcast_all(x->gl_name, canvas_getdir(x), 2); +} + /* ------------------------------- declare ------------------------ */ /* put "declare" objects in a patch to tell it about the environment in @@ -2765,6 +2895,8 @@ void g_canvas_setup(void) class_addcreator((t_newmethod)table_new, gensym("table"), A_DEFSYM, A_DEFFLOAT, 0); + class_addmethod(canvas_class, (t_method)canvas_showdirty, + gensym("showdirty"), 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 a5d7ed1166c0a9cb192c3c809af20e649e8e49aa..44ee4e4ab581c2ae9ea79e8ef07292400260780a 100644 --- a/pd/src/g_canvas.h +++ b/pd/src/g_canvas.h @@ -235,6 +235,7 @@ struct _glist t_gpointer gl_gp; /* parent for "canvas" data type */ int gl_subdirties; /* number of descending dirty abstractions */ + int gl_dirties; /* number of diry instances, for multiple dirty warning */ }; #define gl_gobj gl_obj.te_g @@ -581,6 +582,7 @@ EXTERN int canvas_setdeleting(t_canvas *x, int flag); EXTERN int canvas_hasarray(t_canvas *x); EXTERN int canvas_has_scalars_only(t_canvas *x); +EXTERN void canvas_warning(t_canvas *x, int warid); #define LB_LOAD 0 /* "loadbang" actions - 0 for original meaning */ #define LB_INIT 1 /* loaded but not yet connected to parent patch */ diff --git a/pd/src/g_editor.c b/pd/src/g_editor.c index 295f13bb2b1ff701da2488b86c9ffeda970744bf..50f8d035ca87cdd2b14f98c56e1721c33f68764b 100644 --- a/pd/src/g_editor.c +++ b/pd/src/g_editor.c @@ -2752,7 +2752,7 @@ void canvas_vis(t_canvas *x, t_floatarg f) We may need to expand this to include scalars, as well. */ canvas_create_editor(x); canvas_args_to_string(argsbuf, x); - gui_vmess("gui_canvas_new", "xiisiiissiiiis", + gui_vmess("gui_canvas_new", "xiisiiissiiiiis", x, (int)(x->gl_screenx2 - x->gl_screenx1), (int)(x->gl_screeny2 - x->gl_screeny1), @@ -2763,6 +2763,9 @@ void canvas_vis(t_canvas *x, t_floatarg f) x->gl_name->s_name, canvas_getdir(x)->s_name, x->gl_dirty, + (x->gl_dirties > 1 ? + (x->gl_dirty ? 2 : 1) + : (x->gl_dirties ? !x->gl_dirty : 0)), x->gl_noscroll, x->gl_nomenu, canvas_hasarray(x), @@ -6171,6 +6174,12 @@ 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); } + /* tell the gui to display a specific message in the + top right corner */ +void canvas_warning(t_canvas *x, int warid) +{ + gui_vmess("gui_canvas_warning", "xi", x, warid); +} static int glist_dofinderror(t_glist *gl, void *error_object) {