diff --git a/pd/doc/5.reference/ab-help.pd b/pd/doc/5.reference/ab-help.pd new file mode 100755 index 0000000000000000000000000000000000000000..4e4747a0f6eb4fe97f21158210ae26aee9a6c44f --- /dev/null +++ b/pd/doc/5.reference/ab-help.pd @@ -0,0 +1,136 @@ +#N canvas 717 240 480 339 12; +#X abframe 1; +#N canvas 870 153 488 334 12; +#X obj 27 32 inlet; +#X obj 27 61 t f b b; +#X obj 27 140 expr 2*$f1*$f2+$f3; +#X obj 27 169 outlet; +#X obj 88 104 f \$1; +#X obj 150 104 f \$2; +#X obj 252 225 abinfo; +#X obj 252 254 print abinfo; +#X msg 219 188 name; +#X msg 264 188 instances; +#X msg 344 188 within; +#X obj 27 233 bng 15 250 50 0 empty empty empty 17 7 0 10 #fcfcfc #000000 +#000000; +#X obj 27 257 f \$0; +#X obj 27 286 print; +#X text 94 61 private abstractions can have arguments...; +#X text 26 202 ... and have their own \$0; +#X obj 115 233 bng 15 250 50 0 empty empty empty 17 7 0 10 #fcfcfc +#000000 #000000; +#X obj 115 257 ab bar; +#X floatatom 115 286 5 0 0 0 - - -, f 5; +#X text 218 98 abinfo yields the name of the abstraction \, the number +of its instances \, and the names of other private abstractions they +are contained in (if any), f 35; +#X connect 0 0 1 0; +#X connect 1 0 2 0; +#X connect 1 1 4 0; +#X connect 1 2 5 0; +#X connect 2 0 3 0; +#X connect 4 0 2 1; +#X connect 5 0 2 2; +#X connect 6 0 7 0; +#X connect 8 0 6 0; +#X connect 9 0 6 0; +#X connect 10 0 6 0; +#X connect 11 0 12 0; +#X connect 12 0 13 0; +#X connect 16 0 17 0; +#X connect 17 0 18 0; +#X abpush foo; +#N canvas 869 280 453 305 12; +#X obj 23 21 bng 15 250 50 0 empty empty empty 17 7 0 10 #fcfcfc #000000 +#000000; +#X obj 23 45 ab bar; +#X floatatom 23 74 5 0 0 0 - - -, f 5; +#X text 81 45 <-- open "bar" and try abinfo "within"!; +#X obj 23 203 ab other_foobar; +#X text 20 233 Normally \, private abstractions such as [ab bar] above +have global scope \, so they are visible through the entire root patch +\, including all its subpatches and private abstractions., f 57; +#X text 20 109 Note that @foobar has local scope (indicated by the +"@" prefix) \, which includes all subpatches \, but doesn't cross (private) +abstraction boundaries. Thus the @foobar inside the other_foobar abstraction +below is a different @foobar than this one.; +#X connect 0 0 1 0; +#X connect 1 0 2 0; +#X abpush @foobar; +#N canvas 1023 266 454 304 12; +#X obj 44 54 f \$0; +#X obj 44 83 outlet; +#X obj 44 25 inlet; +#X obj 147 63 abinfo; +#X obj 147 92 print abinfo; +#X msg 114 26 name; +#X msg 159 26 instances; +#X msg 239 26 within; +#X connect 0 0 1 0; +#X connect 2 0 0 0; +#X connect 3 0 4 0; +#X connect 5 0 3 0; +#X connect 6 0 3 0; +#X connect 7 0 3 0; +#X abpush bar foo @foobar; +#N canvas 788 330 452 303 12; +#X obj 22 24 ab @foobar; +#X text 112 24 <-- click here; +#X abpush other_foobar @foobar; +#N canvas 950 315 452 304 12; +#X text 16 31 Note that this instance of @foobar is different from +the one in the main patch!; +#X abpush other_foobar#@foobar other_foobar; +#X abframe 0; +#X floatatom 31 12 5 0 0 0 - - -, f 5; +#X floatatom 31 70 5 0 0 0 - - -, f 5; +#X obj 31 41 ab foo 3 2; +#X obj 267 89 bng 15 250 50 0 empty empty empty 17 7 0 10 #fcfcfc #000000 +#000000; +#X obj 267 113 abdefs; +#X obj 267 142 print defs; +#X floatatom 30 159 5 0 0 0 - - -, f 5; +#X floatatom 30 217 5 0 0 0 - - -, f 5; +#X floatatom 128 71 5 0 0 0 - - -, f 5; +#X text 127 28 ... and cloned, f 10; +#X obj 128 99 pack f f; +#X msg 128 128 \$2 \$1; +#X obj 128 187 unpack f f; +#X floatatom 128 216 5 0 0 0 - - -, f 5; +#X floatatom 195 216 5 0 0 0 - - -, f 5; +#X text 29 117 ab's can be copied..., f 11; +#X obj 30 188 ab foo 3 5; +#X text 265 13 abdefs prints the list of all private abstractions contained +in a patch \, along with their instance counts, f 27; +#X text 22 266 CAVEAT: Private abstractions are identified by their +name. In contrast to one-offs \, editing their name may create a new +(and empty) abstraction!; +#X obj 128 158 abclone foo 4 5; +#X obj 181 73 hradio 15 0 0 4 empty empty empty 0 -8 0 10 #fcfcfc #000000 +#000000 0; +#X obj 267 230 ab @foobar; +#N canvas 432 306 417 220 META 0; +#X text 13 47 LIBRARY internal; +#X text 13 1 KEYWORDS abstraction private; +#X text 13 24 DESCRIPTION creates a private abstraction; +#X text 13 70 AUTHOR Guillem Bartrina; +#X text 13 116 RELEASE_DATE 2020; +#X text 13 139 HELP_PATCH_AUTHORS Albert Gräf; +#X text 13 93 WEBSITE https://agraef.github.io/purr-data/; +#X restore 408 306 pd META; +#X text 265 174 a "local" abstraction which illustrates local scope +as well as abinfo "within"., f 27; +#X connect 0 0 2 0; +#X connect 2 0 1 0; +#X connect 3 0 4 0; +#X connect 4 0 5 0; +#X connect 6 0 16 0; +#X connect 8 0 10 0; +#X connect 10 0 11 0; +#X connect 11 0 19 0; +#X connect 12 0 13 0; +#X connect 12 1 14 0; +#X connect 16 0 7 0; +#X connect 19 0 12 0; +#X connect 20 0 10 1; diff --git a/pd/nw/dialog_abstractions.html b/pd/nw/dialog_abstractions.html new file mode 100644 index 0000000000000000000000000000000000000000..cf74b55a5702240f202c1f3ebf22ef1b973b04b3 --- /dev/null +++ b/pd/nw/dialog_abstractions.html @@ -0,0 +1,213 @@ +<!DOCTYPE html> +<html> + <head> + <link id="page_style" rel="stylesheet" type="text/css" href="css/default.css"> + </head> + <body class="dialog_body prefs_body" style="overflow: hidden;"> + <div class="container noselect prefs_container"> + <table id="titlebar"> + <tr> + <td style="width: 100%;"> + <div id="titlebar_title">Abstractions</div> + </td> + <td id="titlebar_buttons_td"> + <div class="titlebar_buttons"> + <div id="titlebar_close_button" onclick="cancel();">×</div> + </div> + </td> + </tr> + </table> + <form> + <fieldset style="width: 90%"> + <legend data-i18n="abstractions.private"></legend> + <strong data-i18n="abstractions.global"></strong> + <hr> + <table style="width: 90%; table-layout: fixed;"> + </table> + <br> + <strong data-i18n="abstractions.local"></strong> + <hr> + <table style="width: 90%; table-layout: fixed;"> + </table> + <br> + <button id="selectall_button" type="button" onClick="selectall()" data-i18n="[title]abstractions.selectall_tt" style="float: right;"> + <span data-i18n="abstractions.selectall"></span> + </button> + </fieldset> + <!-- + <hr> + <fieldset style="width: 90%; border: 2px solid grey"> + <legend data-i18n="abstractions.filebased"></legend> + <i>NOT IMPLEMENTED</i> + <table style="width: 90%; table-layout: fixed;"> + </table> + </fieldset> + <hr> + --> + <div class="submit_buttons"> + <button id="delete_button" type="button" onClick="deleteselected()" data-i18n="[title]abstractions.delete_tt"> + <span data-i18n="abstractions.delete"></span> + </button> + <button type="button" onClick="cancel()" data-i18n="[title]abstractions.close_tt"> + <span data-i18n="abstractions.close"></span> + </button> + </div> + </form> + </div> + <script> +"use strict"; +var gui = require("nw.gui"); +var pdgui = require("./pdgui.js"); + +// For translations +var l = pdgui.get_local_string; + +pdgui.skin.apply(window); + +var pd_object_callback; +var canvas; +var deletequeue; + +function selectall() { + var checkboxes = document.querySelectorAll('input[type="checkbox"]:not(:checked)'); + for(var i = 0, n = checkboxes.length; i < n; i++) { + if(!checkboxes[i].checked) + { + checkboxes[i].click(); + } + } +} + +function deleteselected() { + pdgui.pdsend(pd_object_callback, "delabstractions", Array.from(deletequeue).join(" ")); + cancel(); +} + +function cancel() { + pdgui.pdsend(pd_object_callback, "cancel"); +} + +function populate_form(attrs) { + var filebased_abs = attrs.filebased_abs, + private_abs = attrs.private_abs, + i, + zero = 0; + canvas = attrs.canvas; + deletequeue = new Set(); + var tables = document.querySelectorAll("table"), + privateglobal_table = tables[1], + privatelocal_table = tables[2]; + //filebased_table = tables[2] + + /* + for(i = 0; i < filebased_abs.length; i += 2) + { + var row = document.createElement("tr"); + cell1 = document.createElement("td"), + cell2 = document.createElement("td"), + cell3 = document.createElement("td"); + + cell1.textContent = filebased_abs[i]; + cell1.style.setProperty("width", "70%"); + cell2.textContent = filebased_abs[i+1]; + cell2.style.setProperty("width", "20%"); + row.appendChild(cell1); + row.appendChild(cell2); + row.appendChild(cell3); + filebased_table.appendChild(row); + } + */ + + for(i = 0; i < private_abs.length; i += 2) + { + var row = document.createElement("tr"), + cell1 = document.createElement("td"), + cell2 = document.createElement("td"), + cell3 = document.createElement("td"); + + cell1.textContent = private_abs[i]; + cell1.style.setProperty("width", "70%"); + cell2.textContent = private_abs[i+1]; + cell2.style.setProperty("width", "20%"); + + if(private_abs[i+1] === 0) + { + let input_elem = document.createElement("input"), + j = i; + input_elem.type = "checkbox"; + input_elem.style.setProperty("max-height", "10px"); + input_elem.style.setProperty("top", "0px"); + input_elem.onchange = function() { + if(input_elem.checked) { + deletequeue.add(private_abs[j]); + } else { + deletequeue.delete(private_abs[j]); + } + get_elem("delete_button").disabled = (deletequeue.size === 0); + }; + cell3.appendChild(input_elem); + zero++; + } + + row.appendChild(cell1); + row.appendChild(cell2); + row.appendChild(cell3); + + if(private_abs[i][0] === '@') { + privatelocal_table.appendChild(row); + } else { + privateglobal_table.appendChild(row); + } + } + + get_elem("selectall_button").disabled = (zero === 0); + get_elem("delete_button").disabled = true; +} + +// This gets called from the nw_create_window function in index.html +// It provides us with our window id from the C side. Once we have it +// we can create the menu and register event callbacks +function register_window_id(gfxstub, attrs) { + pd_object_callback = gfxstub; + add_events(gfxstub); + // not sure that we need this for properties windows + //pdgui.canvas_map(gfxstub); + translate_form(); + populate_form(attrs); + // We don't turn on rendering of the "container" div until + // We've finished displaying all the spans and populating the + // labels and form elements. That makes it more efficient and + // snappier, at least on older machines. + document.getElementsByClassName("container")[0].style.setProperty("display", "inline"); + + gui.Window.get().setResizable(false); +} + +function get_elem(name) { + return document.getElementById(name); +} + +// Stop-gap translator +function translate_form() { + var elements = document.querySelectorAll("[data-i18n]"), + data, + i; + for (i = 0; i < elements.length; i++) { + data = elements[i].dataset.i18n; + if (data.slice(0, 7) === "[title]") { + elements[i].title = l(data.slice(7)); + } else { + elements[i].textContent = l(data); + } + } +} + +function add_events(name) { + gui.Window.get().on("close", function () { + cancel(); + }); + pdgui.dialog_bindings(name); +} + </script> + </body> +</html> diff --git a/pd/nw/dialog_external.html b/pd/nw/dialog_external.html index 28e4d3f9e155b7f688c0191183be1b623207633c..a0305f715ce6de93a52f5336dd25fe47b4b3aa84 100644 --- a/pd/nw/dialog_external.html +++ b/pd/nw/dialog_external.html @@ -149,7 +149,8 @@ function parse_attrs(attrs) { if (token.length > 1) { elem.type = token[token.length - 1]; if (elem.type !== "symbol" && - elem.type !== "toggle") { + elem.type !== "toggle" && + elem.type !== "hidden") { // no suffix defaults to "number" elem.type = "number"; } else { @@ -218,6 +219,7 @@ function get_input_type(t) { return t === "symbol" ? "text" : t === "number" ? "text" : t === "toggle" ? "checkbox": + t === "hidden" ? "hidden": "text"; } diff --git a/pd/nw/locales/de/translation.json b/pd/nw/locales/de/translation.json index 771b7727414d157de9e587e90fc8461022b63403..73ccfc8b2cad4fb9d7911edb1f2ac4d748a75394 100644 --- a/pd/nw/locales/de/translation.json +++ b/pd/nw/locales/de/translation.json @@ -239,6 +239,8 @@ "visible_ancestor_tt": "Zeige den nächsten aktuell sichtbaren Vorgänger des aktuellen Fensters", "pdwin": "Pd-Fenster", "pdwin_tt": "Zeige das Pd-Hauptfenster", + "abstractions": "Abstraktionen", + "abstractions_tt": "Abstraktionen abfragen und verwalten", "media": "Medien", @@ -506,5 +508,17 @@ "building_index": "Erstelle Index...", "no_results": "Keine Resultate gefunden.", "search_placeholder": "Suche Pd-Doku" + }, + "abstractions": { + "filebased": "Datei-basierte Abstraktionen", + "private": "Private Abstraktionen", + "global": "Globaler Geltungsbereich", + "local": "Lokaler Geltungsbereich", + "selectall": "Alles auswählen", + "selectall_tt": "Wähle alle uninstanziierten Definitionen aus", + "delete": "Löschen", + "delete_tt": "Löscht alle ausgewählten Definitionen", + "close": "Schließen", + "close_tt": "Schließt das Dialog-Fenster" } } diff --git a/pd/nw/locales/en/translation.json b/pd/nw/locales/en/translation.json index 9883efa2ad6b5c00c31197fe706471397d2b987b..ee65f1aa393cb47133587fdb9a875daddab2284d 100644 --- a/pd/nw/locales/en/translation.json +++ b/pd/nw/locales/en/translation.json @@ -239,6 +239,8 @@ "visible_ancestor_tt": "give focus to the closest ancestor of this window that is currently visible", "pdwin": "Pd Window", "pdwin_tt": "Give focus to the main Pd window", + "abstractions": "Abstractions", + "abstractions_tt": "Query and manage abstractions", "media": "Media", @@ -506,5 +508,17 @@ "building_index": "Building index...", "no_results": "No results found.", "search_placeholder": "Search Pd Docs" + }, + "abstractions": { + "filebased": "File-based abstractions", + "private": "Private abstractions", + "global": "Global scope", + "local": "Local scope", + "selectall": "Select all", + "selectall_tt": "Select all definitions with zero instances", + "delete": "Delete", + "delete_tt": "Delete all selected definitions", + "close": "Close", + "close_tt": "Close the dialog window" } } diff --git a/pd/nw/locales/fr/translation.json b/pd/nw/locales/fr/translation.json index 8783b6b71d0c58f93416c5f5d5e1b8a0b69aa7b7..ccdc3bc4b7524cabb3a5cd4a4a2b8ac2f9b74667 100644 --- a/pd/nw/locales/fr/translation.json +++ b/pd/nw/locales/fr/translation.json @@ -239,6 +239,8 @@ "visible_ancestor_tt": "Donner le focus à l'ancêtre le plus proche de cette fenêtre qui est actuellement visible", "pdwin": "Fenêtre Console Pd", "pdwin_tt": "Donner le focus à la fenêtre principale de Pd", + "abstractions": "Abstractions", + "abstractions_tt": "Interroger et gérer les abstractions", "media": "Média", @@ -281,7 +283,7 @@ "canvas": { "paste_clipboard_prompt": "Attention: vous êtes sur le point de coller du 'code Pd' provenant de l'extérieur de Pd. Voulez-vous continuer ?", "save_dialog": { - "prompt": "Voulez-vous enregistrer les modifications apportées à ce patch ?", + "prompt": "Voulez-vous enregistrer les modifications apportées à ", "yes": "Oui", "yes_tt": "Enregistrer les modifications dans le fichier avant de fermer le patch ?", "no": "Non", @@ -506,5 +508,17 @@ "building_index": "Construction de l'index...", "no_results": "Aucun résultat trouvé !", "search_placeholder": "Chercher dans les Docs Pd" + }, + "abstractions": { + "filebased": "Abstractions basées sur des fichiers", + "private": "Abstractions privées", + "global": "Portée globale", + "local": "Portée locale", + "selectall": "Tout sélectionner", + "selectall_tt": "Sélectionner toutes les définitions avec zéro instance", + "delete": "Supprimer", + "delete_tt": "Supprimer toutes les définitions sélectionnées", + "close": "Fermer", + "close_tt": "Fermer la fenêtre de dialogue" } } diff --git a/pd/nw/pd_canvas.js b/pd/nw/pd_canvas.js index daaf82b91da5dac0e37229e03dff117ed96a4ad7..12ad04b38017af8c17751e270d5393f87976a646 100644 --- a/pd/nw/pd_canvas.js +++ b/pd/nw/pd_canvas.js @@ -2029,6 +2029,12 @@ function nw_create_patch_window_menus(gui, w, name) { pdgui.raise_pd_window(); } }); + minit(m.win.abstractions, { + enabled: true, + click: function () { + pdgui.pdsend(name, "getabstractions"); + } + }); // Media menu minit(m.media.audio_on, { diff --git a/pd/nw/pd_menus.js b/pd/nw/pd_menus.js index a9dd9e69d189c98ec4f18a408af1a1c9f66b4489..0e3646546aef68d406085f90b7e36a24411df77a 100644 --- a/pd/nw/pd_menus.js +++ b/pd/nw/pd_menus.js @@ -579,6 +579,11 @@ function create_menu(gui, type) { key: shortcuts.menu.pdwin.key, modifiers: shortcuts.menu.pdwin.modifiers })); + winman_menu.append(new gui.MenuItem({ type: "separator" })); + winman_menu.append(m.win.abstractions = new gui.MenuItem({ + label: l("menu.abstractions"), + tooltip: l("menu.abstractions_tt") + })); } // Media menu diff --git a/pd/nw/pdgui.js b/pd/nw/pdgui.js index ea9189f2e774546b00056fb527a3f45bf3bbfc27..12cd6347ba633b474ecfaede78316c4a57f82bd0 100644 --- a/pd/nw/pdgui.js +++ b/pd/nw/pdgui.js @@ -6026,6 +6026,13 @@ function gui_external_dialog(did, external_name, attr_array) { }); } +function gui_abstractions_dialog(cid, gfxstub, filebased_abs, private_abs) { + var attrs = { canvas: cid, filebased_abs: filebased_abs, + private_abs: private_abs }; + dialogwin[gfxstub] = create_window(gfxstub, "abstractions", 300, + Math.min(600, (private_abs.length*10 + 190+(nw_os_is_osx?20:0))), 0, 0, attrs); +} + // Global settings function gui_pd_dsp(state) { diff --git a/pd/src/g_canvas.c b/pd/src/g_canvas.c index fa1447ff8906bca79c2a6df747f33c778c1e3d96..9ae490767fb08c0927d0aef3666729a0db965acd 100644 --- a/pd/src/g_canvas.c +++ b/pd/src/g_canvas.c @@ -95,6 +95,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) { @@ -157,6 +159,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)); @@ -231,12 +239,16 @@ t_symbol *canvas_realizedollar(t_canvas *x, t_symbol *s) t_symbol *canvas_getcurrentdir(void) { - t_canvasenvironment *e = canvas_getenv(canvas_getcurrent()); - return (e->ce_dir); + return (canvas_getdir(canvas_getcurrent())); } +/* ** refactored function, check for errors */ t_symbol *canvas_getdir(t_canvas *x) { + x = canvas_getrootfor(x); + /* in the case the root is an ab instance, we borrow the + dir from the main root canvas (where the definition is stored) */ + if(x->gl_isab) x = x->gl_absource->ad_owner; t_canvasenvironment *e = canvas_getenv(x); return (e->ce_dir); } @@ -410,6 +422,7 @@ static int calculate_zoom(t_float zoom_hack) } int canvas_dirty_broadcast_all(t_symbol *name, t_symbol *dir, int mess); +int canvas_dirty_broadcast_ab(t_canvas *x, t_ab_definition *abdef, 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() @@ -486,6 +499,17 @@ 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 is set means that + this canvas is going to be an ab instance */ + if(canvas_newabsource) + { + x->gl_isab = 1; + x->gl_absource = canvas_newabsource; + canvas_newabsource = 0; + } + else x->gl_isab = 0; + x->gl_subdirties = 0; x->gl_dirties = 0; @@ -542,8 +566,11 @@ t_canvas *canvas_new(void *dummy, t_symbol *sel, int argc, t_atom *argv) get the number of dirty instances of this same abstraction */ if(x->gl_env) { - x->gl_dirties = canvas_dirty_broadcast_all(x->gl_name, + if(!x->gl_isab) + x->gl_dirties = canvas_dirty_broadcast_all(x->gl_name, canvas_getdir(x), 0); + else + x->gl_dirties = canvas_dirty_broadcast_ab(canvas_getrootfor_ab(x), x->gl_absource, 0); } return(x); @@ -741,6 +768,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) @@ -822,6 +857,8 @@ void canvas_dirtyclimb(t_canvas *x, int n) void clone_iterate(t_pd *z, t_canvas_iterator it, void* data); int clone_match(t_pd *z, t_symbol *name, t_symbol *dir); +int clone_isab(t_pd *z); +int clone_matchab(t_pd *z, t_ab_definition *source); static void canvas_dirty_common(t_canvas *x, int mess) { @@ -868,7 +905,7 @@ static int canvas_dirty_broadcast(t_canvas *x, t_symbol *name, t_symbol *dir, in { if(pd_class(&g->g_pd) == canvas_class) { - if(canvas_isabstraction((t_canvas *)g) + if(canvas_isabstraction((t_canvas *)g) && !((t_canvas *)g)->gl_isab && ((t_canvas *)g)->gl_name == name && canvas_getdir((t_canvas *)g) == dir) { @@ -876,7 +913,9 @@ static int canvas_dirty_broadcast(t_canvas *x, t_symbol *name, t_symbol *dir, in 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) { @@ -910,6 +949,67 @@ int canvas_dirty_broadcast_all(t_symbol *name, t_symbol *dir, int mess) res += canvas_dirty_broadcast(x, name, dir, mess); return (res); } + +/* same but for ab */ + +typedef struct _dirty_broadcast_ab_data +{ + t_ab_definition *abdef; + int mess; + int *res; +} t_dirty_broadcast_ab_data; + +static void canvas_dirty_deliver_ab_packed(t_canvas *x, t_dirty_broadcast_ab_data *data) +{ + *data->res += (x->gl_dirty > 0); + canvas_dirty_common(x, data->mess); +} + +static void canvas_dirty_broadcast_ab_packed(t_canvas *x, t_dirty_broadcast_ab_data *data); + +int canvas_dirty_broadcast_ab(t_canvas *x, t_ab_definition *abdef, 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_isab + && ((t_canvas *)g)->gl_absource == abdef) + { + res += (((t_canvas *)g)->gl_dirty > 0); + canvas_dirty_common((t_canvas *)g, mess); + } + else if(!canvas_isabstraction((t_canvas *)g) || ((t_canvas *)g)->gl_isab) + { + res += canvas_dirty_broadcast_ab((t_canvas *)g, abdef, mess); + } + } + else if(pd_class(&g->g_pd) == clone_class) + { + int cres = 0; + t_dirty_broadcast_ab_data data; + data.abdef = abdef; data.mess = mess; data.res = &cres; + if(clone_matchab(&g->g_pd, abdef)) + { + clone_iterate(&g->g_pd, canvas_dirty_deliver_ab_packed, &data); + } + else if(clone_isab(&g->g_pd)) + { + clone_iterate(&g->g_pd, canvas_dirty_broadcast_ab_packed, &data); + } + res += cres; + } + } + return (res); +} + +static void canvas_dirty_broadcast_ab_packed(t_canvas *x, t_dirty_broadcast_ab_data *data) +{ + *data->res = canvas_dirty_broadcast_ab(x, data->abdef, data->mess); +} + /* mark a glist dirty or clean */ void canvas_dirty(t_canvas *x, t_floatarg n) { @@ -924,14 +1024,19 @@ 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)); + if(!x2->gl_isab) + canvas_dirty_broadcast_all(x2->gl_name, canvas_getdir(x2), + (x2->gl_dirty ? 1 : -1)); + else + canvas_dirty_broadcast_ab(canvas_getrootfor_ab(x2), x2->gl_absource, + (x2->gl_dirty ? 1 : -1)); } } } @@ -1157,17 +1262,28 @@ int glist_getfont(t_glist *x) } extern void canvas_group_free(t_pd *x); +static void canvas_deregister_ab(t_canvas *x, t_ab_definition *a); void canvas_free(t_canvas *x) { //fprintf(stderr,"canvas_free %zx\n", (t_uint)x); + /* crude hack. in the case it was a clone instance, it shouldn't have an owner. + For ab instances, we have set the owner inside clone_free because we need it + in order to deregister the dependencies. + here we set it to NULL again to prevent any error in the functions called bellow */ + t_canvas *aux = x->gl_owner; + if(x->gl_isclone) x->gl_owner = 0; + /* 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); + if(!x->gl_isab) + canvas_dirty_broadcast_all(x->gl_name, canvas_getdir(x), -1); + else + canvas_dirty_broadcast_ab(canvas_getrootfor_ab(x), x->gl_absource, -1); } t_gobj *y; @@ -1207,6 +1323,26 @@ void canvas_free(t_canvas *x) canvas_takeofflist(x); if (x->gl_svg) /* for groups, free the data */ canvas_group_free(x->gl_svg); + + /* freeing an ab instance */ + if(x->gl_isab) + { + x->gl_absource->ad_numinstances--; + canvas_deregister_ab((x->gl_isclone ? aux : x->gl_owner), + x->gl_absource); + } + + /* 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->ad_dep, sizeof(t_ab_definition*)*d->ad_numdep); + freebytes(d->ad_deprefs, sizeof(int)*d->ad_numdep); + freebytes(d, sizeof(t_ab_definition)); + d = daux; + } } /* ----------------- lines ---------- */ @@ -1740,13 +1876,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)); } @@ -2029,11 +2169,531 @@ 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]")); + + /* set ab source, next canvas is going to be a private abstraction */ + canvas_setabsource(abdef); + 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); + + /* open the canvas if we are creating it for the first time */ + canvas_vis((t_canvas *)newest, !glist_amreloadingabstractions + && !abdef->ad_numinstances + && binbuf_getnatom(abdef->ad_source) == 3); + + return(newest); +} + +/* get root canvas crossing ab boundaries, where ab definitions are stored */ +t_canvas *canvas_getrootfor_ab(t_canvas *x) +{ + if ((!x->gl_owner && !x->gl_isclone) || (canvas_isabstraction(x) && !x->gl_isab)) + return (x); + else if (x->gl_isab) /* shortcut + workaround for clones (since they haven't owner)*/ + return (x->gl_absource->ad_owner); + 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 if there is a cycle, a visual scheme of the cycle is stored in 'res' */ +static int ab_check_cycle(t_ab_definition *current, t_ab_definition *parent, int pathlen, + char *path, char *res) +{ + if(current == parent) + { + sprintf(path+pathlen, "[ab %s]", current->ad_name->s_name); + strcpy(res, path); + return (1); + } + else + { + /* if it is a local private abstraction, get rid of classmember-like names (only used internally) */ + char *hash = strrchr(current->ad_name->s_name, '#'); + if(!hash) hash = current->ad_name->s_name; + else hash += 1; + int len = strlen(hash); + sprintf(path+pathlen, "[ab %s]<-", hash); + pathlen += (len+7); + int i, cycle = 0; + for(i = 0; !cycle && i < current->ad_numdep; i++) + { + cycle = ab_check_cycle(current->ad_dep[i], parent, pathlen, path, res); + } + pathlen -= (len+7); + return (cycle); + } +} + +/* try to register a new dependency into the dependency graph, + returns 0 and the scheme in 'res' if a dependency issue is found */ +static int canvas_register_ab(t_canvas *x, t_ab_definition *a, char *res) +{ + /* climb to closest ab */ + while(x && !x->gl_isab) + x = x->gl_owner; + + if(x && x->gl_isab) + { + t_ab_definition *f = x->gl_absource; + + int i, found = 0; + for(i = 0; !found && i < a->ad_numdep; i++) + found = (a->ad_dep[i] == f); + + if(!found) + { + char path[MAXPDSTRING]; + sprintf(path, "[ab %s]<-", a->ad_name->s_name); + if(!ab_check_cycle(f, a, strlen(path), path, res)) + { + /* no dependency issues found so we add the new dependency */ + a->ad_dep = + (t_ab_definition **)resizebytes(a->ad_dep, sizeof(t_ab_definition *)*a->ad_numdep, + sizeof(t_ab_definition *)*(a->ad_numdep+1)); + a->ad_deprefs = + (int *)resizebytes(a->ad_deprefs, sizeof(int)*a->ad_numdep, + sizeof(int)*(a->ad_numdep+1)); + a->ad_dep[a->ad_numdep] = f; + a->ad_deprefs[a->ad_numdep] = 1; + a->ad_numdep++; + } + else return (0); + } + else + { + a->ad_deprefs[i-1]++; + } + } + return (1); +} + +static void canvas_deregister_ab(t_canvas *x, t_ab_definition *a) +{ + /* climb to closest ab */ + while(x && !x->gl_isab) + x = x->gl_owner; + + if(x && x->gl_isab) + { + t_ab_definition *f = x->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]) + { + /* we can delete the dependency since there are no instances left */ + 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"); + } +} + +/* 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; +} + +/* tries to add 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_owner = c; + 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); +} + +static int canvas_del_ab(t_canvas *x, t_symbol *name) +{ + t_canvas *c = canvas_getrootfor_ab(x); + t_ab_definition *abdef, *abdefpre; + for(abdef = c->gl_abdefs, abdefpre = 0; abdef; abdefpre = abdef, abdef = abdef->ad_next) + { + if(abdef->ad_name == name && !abdef->ad_numinstances) + { + if(abdefpre) abdefpre->ad_next = abdef->ad_next; + else c->gl_abdefs = abdef->ad_next; + binbuf_free(abdef->ad_source); + freebytes(abdef->ad_dep, sizeof(t_ab_definition*)*abdef->ad_numdep); + freebytes(abdef->ad_deprefs, sizeof(int)*abdef->ad_numdep); + freebytes(abdef, sizeof(t_ab_definition)); + return (1); + } + } + 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 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); + + int i, fra = 0; + for(i = 0; i < head; i++) + { + if(stack[i]->ad_numinstances) + { + if(!fra) + { + binbuf_addv(b, "ssi;", gensym("#X"), gensym("abframe"), 1); + fra = 1; + } + + binbuf_add(b, binbuf_getnatom(stack[i]->ad_source), binbuf_getvec(stack[i]->ad_source)); + binbuf_addv(b, "sss", gensym("#X"), gensym("abpush"), 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); + } + } + if(fra) binbuf_addv(b, "ssi;", gensym("#X"), gensym("abframe"), 0); + + freebytes(stack, sizeof(t_ab_definition *) * numabdefs); +} + +/* saves last canvas as an ab definition */ +static void canvas_abpush(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 = dummy_canvas_env(canvas_getdir(x)->s_name); //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_abpush: ab definition for '%s' already exists, skipping", + name->s_name); + } + else + { + if(argc > 1) + { + /* restore all dependencies, to get exactly the + same state (ab objects that can't be instantiated due dependencies) as before */ + n->ad_numdep = argc-1; + n->ad_dep = + (t_ab_definition **)resizebytes(n->ad_dep, 0, sizeof(t_ab_definition *)*n->ad_numdep); + n->ad_deprefs = + (int *)resizebytes(n->ad_deprefs, 0, sizeof(int)*n->ad_numdep); + + int i; + 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_abpush"); return; } + n->ad_dep[i-1] = absource; + n->ad_deprefs[i-1] = 0; + } + } + } + + pd_free(&x->gl_pd); +} + +/* extends the name for a local ab, using a classmember-like format */ +static t_symbol *ab_extend_name(t_canvas *x, t_symbol *s) +{ + char res[MAXPDSTRING]; + t_canvas *next = canvas_getrootfor(x); + if(next->gl_isab) + sprintf(res, "%s#%s", next->gl_absource->ad_name->s_name, s->s_name); + else + strcpy(res, s->s_name); + return gensym(res); +} + +int abframe = 0; +static void canvas_abframe(t_canvas *x, t_float val) +{ + abframe = val; +} + +extern t_class *text_class; + +/* creator for "ab" objects */ +static void *ab_new(t_symbol *s, int argc, t_atom *argv) +{ + if(abframe) + /* return dummy text object so that creator + does not throw an error */ + return pd_new(text_class); + + t_canvas *c = canvas_getcurrent(); + + if (argc && argv[0].a_type != A_SYMBOL) + { + error("ab_new: ab name must be a symbol"); + newest = 0; + } + else + { + t_symbol *name = (argc ? argv[0].a_w.w_symbol : gensym("(ab)")); + t_ab_definition *source; + + if(name->s_name[0] == '@') /* is local ab */ + name = ab_extend_name(c, name); + + 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); + } + + char res[MAXPDSTRING]; + if(canvas_register_ab(c, source, res)) + { + newest = do_create_ab(source, (argc ? argc-1 : 0), (argc ? argv+1 : 0)); + source->ad_numinstances++; + } + else + { + if(!glist_amreloadingabstractions) + error("ab_new: can't insantiate ab within itself\n cycle: %s", res); + newest = 0; + } + } + return (newest); +} + +static void canvas_getabstractions(t_canvas *x) +{ + t_canvas *c = canvas_getrootfor_ab(x), + *r = canvas_getrootfor(x); + gfxstub_deleteforkey(x); + char *gfxstub = gfxstub_new2(&x->gl_pd, &x->gl_pd); + t_ab_definition *abdef; + gui_start_vmess("gui_abstractions_dialog", "xs", x, gfxstub); + gui_start_array(); + gui_end_array(); + gui_start_array(); + for(abdef = c->gl_abdefs; abdef; abdef = abdef->ad_next) + { + char *hash = strrchr(abdef->ad_name->s_name, '#'); + if(!hash) + { + if(abdef->ad_name->s_name[0] != '@' || !r->gl_isab) + { + gui_s(abdef->ad_name->s_name); + gui_i(abdef->ad_numinstances); + } + } + else + { + *hash = '\0'; + if(r->gl_isab && + gensym(abdef->ad_name->s_name) == r->gl_absource->ad_name) + { + gui_s(hash+1); + gui_i(abdef->ad_numinstances); + } + *hash = '#'; + } + } + gui_end_array(); + gui_end_vmess(); +} + +static void canvas_delabstractions(t_canvas *x, t_symbol *s, int argc, t_atom *argv) +{ + t_symbol *name; + int i; + for(i = 0; i < argc; i++) + { + name = atom_getsymbol(argv++); + if(name->s_name[0] == '@') + name = ab_extend_name(x, name); + if(!canvas_del_ab(x, name)) + bug("canvas_delabstractions"); + } + startpost("info: a total of [%d] ab definitions have been deleted\n > ", argc); + postatom(argc, argv-argc); + endpost(); +} + +/* --------- */ + +static t_class *abdefs_class; + +typedef struct _abdefs +{ + t_object x_obj; + t_canvas *x_canvas; +} t_abdefs; + +static void *abdefs_new(void) +{ + t_abdefs *x = (t_abdefs *)pd_new(abdefs_class); + x->x_canvas = canvas_getcurrent(); + outlet_new(&x->x_obj, &s_list); + return (x); +} + +static void abdefs_get(t_abdefs *x); +static void abdefs_bang(t_abdefs *x) +{ + abdefs_get(x); +} + +static void abdefs_get(t_abdefs *x) +{ + t_canvas *c = canvas_getrootfor_ab(x->x_canvas), + *r = canvas_getrootfor(x->x_canvas); + t_binbuf *out = binbuf_new(); + t_ab_definition *abdef; + for(abdef = c->gl_abdefs; abdef; abdef = abdef->ad_next) + { + char *hash = strrchr(abdef->ad_name->s_name, '#'); + if(!hash) + { + if(abdef->ad_name->s_name[0] != '@' || !r->gl_isab) + { + binbuf_addv(out, "si", abdef->ad_name, abdef->ad_numinstances); + } + } + else + { + *hash = '\0'; + if(r->gl_isab && + gensym(abdef->ad_name->s_name) == r->gl_absource->ad_name) + { + binbuf_addv(out, "si", gensym(hash+1), abdef->ad_numinstances); + } + *hash = '#'; + } + } + outlet_list(x->x_obj.ob_outlet, &s_list, + binbuf_getnatom(out), binbuf_getvec(out)); + binbuf_free(out); +} + +static void abdefs_instances(t_abdefs *x, t_symbol *s) +{ + t_ab_definition *abdef; + if((abdef = canvas_find_ab(x->x_canvas, s))) + { + t_atom at[1]; + SETFLOAT(at, abdef->ad_numinstances); + outlet_list(x->x_obj.ob_outlet, &s_list, 1, at); + } + else + error("abdefs: couldn't find definition for '%s'", s->s_name); +} + /* --------- */ static void canvas_showdirty(t_canvas *x) { - canvas_dirty_broadcast_all(x->gl_name, canvas_getdir(x), 2); + if(!x->gl_isab) + canvas_dirty_broadcast_all(x->gl_name, canvas_getdir(x), 2); + else + canvas_dirty_broadcast_ab(canvas_getrootfor_ab(x), x->gl_absource, 2); } /* ------------------------------- declare ------------------------ */ @@ -2915,8 +3575,29 @@ 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); + +/*---------------------------- ab ------------------- */ + + class_addcreator((t_newmethod)ab_new, gensym("ab"), A_GIMME, 0); + class_addmethod(canvas_class, (t_method)canvas_abpush, + gensym("abpush"), A_GIMME, 0); + class_addmethod(canvas_class, (t_method)canvas_abframe, + gensym("abframe"), A_FLOAT, 0); + + abdefs_class = class_new(gensym("abdefs"), (t_newmethod)abdefs_new, + 0, sizeof(t_abdefs), CLASS_DEFAULT, A_NULL); + class_addbang(abdefs_class, (t_method)abdefs_bang); + class_addmethod(abdefs_class, (t_method)abdefs_get, gensym("get"), 0); + class_addmethod(abdefs_class, (t_method)abdefs_instances, gensym("instances"), A_SYMBOL, 0); + + class_addmethod(canvas_class, (t_method)canvas_getabstractions, + gensym("getabstractions"), 0); + class_addmethod(canvas_class, (t_method)canvas_delabstractions, + gensym("delabstractions"), A_GIMME, 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 4cafe3265a5d2e704745a5b7d1b23a49d317e7f3..cde9837014ad4a16ab8b7ca2eb8275062a2b1f7f 100644 --- a/pd/src/g_canvas.h +++ b/pd/src/g_canvas.h @@ -169,6 +169,24 @@ 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 it */ +typedef struct _ab_definition +{ + t_symbol *ad_name; /* id for the ab definition */ + t_binbuf *ad_source; /* binbuf where the source is stored */ + int ad_numinstances; /* number of instances */ + struct _ab_definition *ad_next; /* next ab definition */ + t_canvas *ad_owner; /* canvas that stores this 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; /* number of instances that define the dependency */ + 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. @@ -241,6 +259,11 @@ struct _glist int gl_subdirties; /* number of descending dirty abstractions */ int gl_dirties; /* number of diry instances, for multiple dirty warning */ + + 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 @@ -560,6 +583,8 @@ EXTERN void canvas_setcurrent(t_canvas *x); EXTERN void canvas_unsetcurrent(t_canvas *x); EXTERN t_symbol *canvas_realizedollar(t_canvas *x, t_symbol *s); EXTERN t_canvas *canvas_getrootfor(t_canvas *x); +EXTERN t_canvas *canvas_getrootfor_ab(t_canvas *x); +EXTERN int abframe; EXTERN void canvas_dirty(t_canvas *x, t_floatarg n); EXTERN int canvas_getfont(t_canvas *x); typedef int (*t_canvasapply)(t_canvas *x, t_int x1, t_int x2, t_int x3); diff --git a/pd/src/g_clone.c b/pd/src/g_clone.c index c06f8dba7fb293b955382fa085feb4137933ec64..d5ba76414de9c785cf1e71efc617c821f3da4c27 100644 --- a/pd/src/g_clone.c +++ b/pd/src/g_clone.c @@ -63,6 +63,7 @@ typedef struct _clone int x_phase; int x_startvoice; /* number of first voice, 0 by default */ int x_suppressvoice; /* suppress voice number as $1 arg */ + t_canvas *x_owner; /* clone owner */ } t_clone; /* the given 'it' function is executed over each of the underlying canvases @@ -80,8 +81,23 @@ int clone_match(t_pd *z, t_symbol *name, t_symbol *dir) t_clone *x = (t_clone *)z; if (!x->x_n) return (0); - return (x->x_vec[0].c_gl->gl_name == name && - canvas_getdir(x->x_vec[0].c_gl) == dir); + return (!x->x_vec[0].c_gl->gl_isab + && x->x_vec[0].c_gl->gl_name == name + && canvas_getdir(x->x_vec[0].c_gl) == dir); +} + +int clone_isab(t_pd *z) +{ + t_clone *x = (t_clone *)z; + if (!x->x_n) + return (0); + return (x->x_vec[0].c_gl->gl_isab); +} + +int clone_matchab(t_pd *z, t_ab_definition *source) +{ + t_clone *x = (t_clone *)z; + return (clone_isab(z) && x->x_vec[0].c_gl->gl_absource == source); } void obj_sendinlet(t_object *x, int n, t_symbol *s, int argc, t_atom *argv); @@ -185,6 +201,13 @@ static void clone_free(t_clone *x) for (i = 0; i < x->x_n; i++) { canvas_closebang(x->x_vec[i].c_gl); + if(x->x_vec[i].c_gl->gl_isab) + { + /* crude hack. since clones don't have owner, + we set it manually to allow the clone to + deregister the dependencies */ + x->x_vec[i].c_gl->gl_owner = x->x_owner; + } pd_free(&x->x_vec[i].c_gl->gl_pd); t_freebytes(x->x_outvec[i], x->x_nout * sizeof(*x->x_outvec[i])); @@ -238,7 +261,8 @@ void clone_setn(t_clone *x, t_floatarg f) { t_canvas *c; t_out *outvec; - SETFLOAT(x->x_argv, x->x_startvoice + i); + /* in the case they are [ab]s, the instance number is one atom beyond */ + SETFLOAT((x->x_vec[0].c_gl->gl_isab ? x->x_argv+1 : x->x_argv), x->x_startvoice + i); if (!(c = clone_makeone(x->x_s, x->x_argc - x->x_suppressvoice, x->x_argv + x->x_suppressvoice))) { @@ -364,6 +388,7 @@ static void clone_dsp(t_clone *x, t_signal **sp) } } +static int clone_newabclone = 0; static void *clone_new(t_symbol *s, int argc, t_atom *argv) { t_clone *x = (t_clone *)pd_new(clone_class); @@ -403,10 +428,27 @@ static void *clone_new(t_symbol *s, int argc, t_atom *argv) else goto usage; /* store a copy of the argmuents with an extra space (argc+1) for supplying an instance number, which we'll bash as we go. */ - x->x_argc = argc - 1; - x->x_argv = getbytes(x->x_argc * sizeof(*x->x_argv)); - memcpy(x->x_argv, argv+1, x->x_argc * sizeof(*x->x_argv)); - SETFLOAT(x->x_argv, x->x_startvoice); + if(clone_newabclone) + /* we are creating a clone from an [ab] definition, we use the same creation + method as for normal clones but the name we pass to objectmaker is + 'ab <name>' instead of just '<name>' */ + { + x->x_argc = argc; + x->x_argv = getbytes(x->x_argc * sizeof(*x->x_argv)); + memcpy(x->x_argv, argv, x->x_argc * sizeof(*x->x_argv)); + SETSYMBOL(x->x_argv, x->x_s); + SETFLOAT(x->x_argv+1, x->x_startvoice); + x->x_s = gensym("ab"); + x->x_owner = canvas_getcurrent(); + clone_newabclone = 0; + } + else + { + x->x_argc = argc - 1; + x->x_argv = getbytes(x->x_argc * sizeof(*x->x_argv)); + memcpy(x->x_argv, argv+1, x->x_argc * sizeof(*x->x_argv)); + SETFLOAT(x->x_argv, x->x_startvoice); + } if (!(c = clone_makeone(x->x_s, x->x_argc - x->x_suppressvoice, x->x_argv + x->x_suppressvoice))) goto fail; @@ -454,10 +496,20 @@ fail: return (0); } +/* creator for [ab]-based clones */ +static void *abclone_new(t_symbol *s, int argc, t_atom *argv) +{ + clone_newabclone = 1; + return clone_new(s, argc, argv); +} + void clone_setup(void) { clone_class = class_new(gensym("clone"), (t_newmethod)clone_new, (t_method)clone_free, sizeof(t_clone), CLASS_NOINLET, A_GIMME, 0); + + class_addcreator((t_newmethod)abclone_new, gensym("abclone"), A_GIMME, 0); + class_addmethod(clone_class, (t_method)clone_click, gensym("click"), A_FLOAT, A_FLOAT, A_FLOAT, A_FLOAT, A_FLOAT, 0); class_addmethod(clone_class, (t_method)clone_loadbang, gensym("loadbang"), diff --git a/pd/src/g_editor.c b/pd/src/g_editor.c index bddca8c714b742e82441174e9193baa8d59b6926..532cf960c24569c9ee62a6a4abbd644cd3598864 100644 --- a/pd/src/g_editor.c +++ b/pd/src/g_editor.c @@ -1288,7 +1288,21 @@ void canvas_undo_paste(t_canvas *x, void *z, int action) } } +void canvas_dirtyclimb(t_canvas *x, int n); +void clone_iterate(t_pd *z, t_canvas_iterator it, void* data); int clone_match(t_pd *z, t_symbol *name, t_symbol *dir); +int clone_isab(t_pd *z); +int clone_matchab(t_pd *z, t_ab_definition *source); + +/* packed data passing structure for glist_doreload */ +typedef struct _reload_data +{ + t_symbol *n; + t_symbol *d; + t_gobj *e; +} t_reload_data; + +static void glist_doreload_packed(t_canvas *x, t_reload_data *data); /* recursively check for abstractions to reload as result of a save. Don't reload the one we just saved ("except") though. */ @@ -1310,7 +1324,7 @@ static void glist_doreload(t_glist *gl, t_symbol *name, t_symbol *dir, /* remake the object if it's an abstraction that appears to have been loaded from the file we just saved */ remakeit = (g != except && pd_class(&g->g_pd) == canvas_class && - canvas_isabstraction((t_canvas *)g) && + canvas_isabstraction((t_canvas *)g) && !((t_canvas *)g)->gl_isab && ((t_canvas *)g)->gl_name == name && canvas_getdir((t_canvas *)g) == dir); @@ -1375,16 +1389,32 @@ static void glist_doreload(t_glist *gl, t_symbol *name, t_symbol *dir, if (g != except && pd_class(&g->g_pd) == canvas_class && (!canvas_isabstraction((t_canvas *)g) || + ((t_canvas *)g)->gl_isab || ((t_canvas *)g)->gl_name != name || canvas_getdir((t_canvas *)g) != dir) ) glist_doreload((t_canvas *)g, name, dir, except); + + /* also reload the instances within ab-based clone objects + *COMMENT: missing recursive case for abstraction-based clone + objects that don't match with the one we are reloading? */ + if(pd_class(&g->g_pd) == clone_class && clone_isab(&g->g_pd)) + { + t_reload_data d; + d.n = name; d.d = dir; d.e = except; + clone_iterate(&g->g_pd, glist_doreload_packed, &d); + } g = g->g_next; } if (!hadwindow && gl->gl_havewindow) canvas_vis(glist_getcanvas(gl), 0); } +static void glist_doreload_packed(t_canvas *x, t_reload_data *data) +{ + glist_doreload(x, data->n, data->d, data->e); +} + /* this flag stops canvases from being marked "dirty" if we have to touch them to reload an abstraction; also suppress window list update */ int glist_amreloadingabstractions = 0; @@ -1402,6 +1432,89 @@ void canvas_reload(t_symbol *name, t_symbol *dir, t_gobj *except) canvas_resume_dsp(dspwas); } +/* packed data passing structure for glist_doreload_ab */ +typedef struct _reload_ab_data +{ + t_ab_definition *a; + t_gobj *e; +} t_reload_ab_data; + +static void glist_doreload_ab_packed(t_canvas *x, t_reload_ab_data *data); + +/* recursive ab reload method */ +static void glist_doreload_ab(t_canvas *x, t_ab_definition *a, t_gobj *e) +{ + t_gobj *g; + int i, nobj = glist_getindex(x, 0); + int found = 0, remakeit = 0; + + for (g = x->gl_list, i = 0; g && i < nobj; i++) + { + remakeit = (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); + + remakeit = remakeit || (pd_class(&g->g_pd) == clone_class && clone_matchab(&g->g_pd, a)); + + if(remakeit) + { + canvas_create_editor(x); + if (!found) + { + glist_noselect(x); + found = 1; + } + glist_select(x, g); + } + /* since this one won't be reloaded, we need to trigger initbang manually */ + else if(g == e) + canvas_initbang((t_canvas *)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)))) + glist_doreload_ab((t_canvas *)g, a, e); + + /* also reload the instances within ab-based clone objects */ + if(pd_class(&g->g_pd) == clone_class + && clone_isab(&g->g_pd) && !clone_matchab(&g->g_pd, a)) + { + t_reload_ab_data d; + d.a = a; d.e = e; + clone_iterate(&g->g_pd, glist_doreload_ab_packed, &d); + } + g = g->g_next; + } +} + +static void glist_doreload_ab_packed(t_canvas *x, t_reload_ab_data *data) +{ + glist_doreload_ab(x, data->a, data->e); +} + +/* reload ab instances */ +void canvas_reload_ab(t_canvas *x) +{ + t_canvas *c = canvas_getrootfor_ab(x); + int dspwas = canvas_suspend_dsp(); + glist_amreloadingabstractions = 1; + glist_doreload_ab(c, x->gl_absource, &x->gl_gobj); + glist_amreloadingabstractions = 0; + canvas_resume_dsp(dspwas); + /* we set the dirty flag of the root canvas (where the ab definitions + are stored) because its file contents (in this case, the definition + for an specific ab) has been edited */ + canvas_dirty(c, 1); +} + /* --------- 6. apply ----------- */ typedef struct _undo_apply @@ -6188,6 +6301,7 @@ 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) diff --git a/pd/src/g_graph.c b/pd/src/g_graph.c index addca713431c6b62119aa0d8463a30a4210ed205..8691141adfa76d481997ed4d22d23e14731b0050 100644 --- a/pd/src/g_graph.c +++ b/pd/src/g_graph.c @@ -202,6 +202,8 @@ int canvas_hasarray(t_canvas *x) /* JMZ: emit a closebang message */ void canvas_closebang(t_canvas *x); +void canvas_dirtyclimb(t_canvas *x, int n); + /* delete an object from a glist and free it */ void glist_delete(t_glist *x, t_gobj *y) { diff --git a/pd/src/g_readwrite.c b/pd/src/g_readwrite.c index 672d536bb12c9ba32c39d515fedfafc944271bf3..007dcc0ee3f156ce1a686233496b4a1154912ac8 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,55 @@ 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 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 if(x->gl_dirty) + 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 if(x->gl_dirty) + canvas_save_ab(x2, fdestroy); } static void canvas_menuprint(t_canvas *x) diff --git a/pd/src/x_interface.c b/pd/src/x_interface.c index fe05f3c52b87b5b3387cbb4cc6f54965918d4fb6..6753ea626236c574d981ccfa9753e4e22a037aae 100644 --- a/pd/src/x_interface.c +++ b/pd/src/x_interface.c @@ -331,6 +331,14 @@ typedef struct _debuginfo { t_object x_obj; } t_debuginfo; +static t_class *abinfo_class; + +typedef struct _abinfo +{ + t_object x_obj; + t_ab_definition *abdef; +} t_abinfo; + /* used by all the *info objects */ static int info_to_console = 0; @@ -1629,6 +1637,74 @@ void objectinfo_setup(void) post("stable objectinfo methods: class"); } +/* -------------------------- abinfo ------------------------------ */ + +void *abinfo_new(void) +{ + t_abinfo *x; + t_canvas *c = canvas_getcurrent(); + while(c->gl_owner && !c->gl_isab) c = c->gl_owner; + if(c->gl_isab) + { + x = (t_abinfo *)pd_new(abinfo_class); + x->abdef = c->gl_absource; + outlet_new(&x->x_obj, &s_list); + } + else + { + if(!abframe) + { + error("abinfo: only instantiable inside an ab object"); + x = 0; + } + else + x = pd_new(text_class); + } + return (x); +} + +void abinfo_name(t_abinfo *x, t_symbol *s, int argc, t_atom *argv) +{ + t_atom at[1]; + SETSYMBOL(at, x->abdef->ad_name); + info_out((t_text *)x, s, 1, at); +} + +void abinfo_instances(t_abinfo *x, t_symbol *s, int argc, t_atom *argv) +{ + t_atom at[1]; + SETFLOAT(at, x->abdef->ad_numinstances); + info_out((t_text *)x, s, 1, at); +} + +void abinfo_within(t_abinfo *x, t_symbol *s, int argc, t_atom *argv) +{ + t_binbuf *buf = binbuf_new(); + int i; + for(i = 0; i < x->abdef->ad_numdep; i++) + { + binbuf_addv(buf, "s", x->abdef->ad_dep[i]->ad_name); + } + info_out((t_text *)x, s, binbuf_getnatom(buf), binbuf_getvec(buf)); + binbuf_free(buf); +} + +//ADD MORE METHODS + +void abinfo_setup(void) +{ + abinfo_class = class_new(gensym("abinfo"), (t_newmethod)abinfo_new, 0, + sizeof(t_abinfo), CLASS_DEFAULT, 0); + + class_addmethod(abinfo_class, (t_method)abinfo_name, + gensym("name"), A_GIMME, 0); + class_addmethod(abinfo_class, (t_method)abinfo_instances, + gensym("instances"), A_GIMME, 0); + class_addmethod(abinfo_class, (t_method)abinfo_within, + gensym("within"), A_GIMME, 0); +} + + void debuginfo_print(t_debuginfo *x) { info_print((t_text *)x); @@ -1665,6 +1741,7 @@ void x_interface_setup(void) classinfo_setup(); debuginfo_setup(); objectinfo_setup(); + abinfo_setup(); pdinfo_setup(); print_setup(); unpost_setup();