Commit 505f8c88 authored by Guillem Bartrina's avatar Guillem Bartrina
Browse files

abstraction saving feature (squashed)

parent cfb218c7
......@@ -285,6 +285,16 @@
"cancel": "Cancel",
"cancel_tt": "Don't save any changes, and don't close the patch"
},
"abstract_dialog": {
"prompt": "Would you like to turn the identical subpatches into abstractions?\nCandidates found in the subpatch tree, from root canvas: ",
"note": "Note: this can't be undone all at once – any changes made in subpatches can be undone from that subpatch",
"single": "Only this one",
"single_tt": "Replace the subpatch you just saved with the corresponding abstraction",
"all": "All candidates",
"all_tt": "Replace all subpatches in the subpatch tree identical to the one you just saved with the corresponding abstraction",
"none": "None",
"none_tt": "Do not replace any subpatch"
},
"find": {
"placeholder": "Search in Canvas",
"search": "Search",
......@@ -294,6 +304,7 @@
"menu": {
"props": "Properties",
"open": "Open",
"saveas": "Save as",
"help": "Help",
"front": "Bring to Front",
"back": "Send to Back"
......
......@@ -77,6 +77,30 @@
<span data-i18n="canvas.save_dialog.cancel"></span>
</button>
</div>
</dialog>
<dialog id="abstract_dialog">
<h4><span style="white-space: pre-line"
data-i18n="canvas.abstract_dialog.prompt"></span>
<span id="abstract_dialog_candidates"></span>
</h4>
<h5> <span data-i18n="canvas.abstract_dialog.note"></span> </h5>
<div class="abstract_submit_buttons">
<button type="button"
id="abstract_single_button"
data-i18n="[title]canvas.abstract_dialog.single_tt">
<span data-i18n="canvas.abstract_dialog.single"></span>
</button>
<button type="button"
id="abstract_all_button"
data-i18n="[title]canvas.abstract_dialog.all_tt">
<span data-i18n="canvas.abstract_dialog.all"></span>
</button>
<button type="button"
id="abstract_none_button"
data-i18n="[title]canvas.abstract_dialog.none_tt">
<span data-i18n="canvas.abstract_dialog.none"></span>
</button>
</div>
</dialog>
<div id="hscroll" style="background-color: rgba(0, 0, 0, 0.267); position: fixed; left: 0px; bottom: 0px; border-radius: 0px; width: 10px; height: 5px; visibility: hidden;"></div>
<div id="vscroll" style="background-color: rgba(0, 0, 0, 0.267); position: fixed; right: 0px; top: 0px; border-radius: 0px; width: 5px; height: 10px; visibility: hidden;"></div>
......
......@@ -1357,6 +1357,12 @@ function create_popup_menu(name) {
pdgui.popup_action(name, 1);
}
}));
popup_menu.append(new gui.MenuItem({
label: l("canvas.menu.saveas"),
click: function() {
pdgui.popup_action(name, 5);
}
}));
popup_menu.append(new gui.MenuItem({
label: l("canvas.menu.help"),
click: function() {
......
......@@ -1185,6 +1185,48 @@ function gui_canvas_menuclose(cid_for_dialog, cid, force) {
}, 450);
}
function canvas_abstract_callback(cid_for_dialog, cid, matches) {
var nw = patchwin[cid_for_dialog],
w = nw.window,
doc = w.document,
dialog = doc.getElementById("abstract_dialog"),
dialog_candidates = doc.getElementById("abstract_dialog_candidates"),
single_button = doc.getElementById("abstract_single_button"),
all_button = doc.getElementById("abstract_all_button"),
none_button = doc.getElementById("abstract_none_button");
dialog_candidates.textContent = matches.toString();
dialog_candidates.title = matches.toString()
all_button.disabled = (matches === 1);
single_button.onclick = function() {
dialog.close();
w.canvas_events[w.canvas_events.get_previous_state()]();
pdsend(cid, "dialog", 0);
};
all_button.onclick = function() {
dialog.close();
w.canvas_events[w.canvas_events.get_previous_state()]();
pdsend(cid, "dialog", 1);
};
none_button.onclick = function() {
dialog.close();
w.canvas_events[w.canvas_events.get_previous_state()]();
}
w.canvas_events.none();
w.setTimeout(function() {
dialog.showModal();
}, 150);
}
function gui_canvas_abstract(cid_for_dialog, cid, matches) {
setTimeout(function() {
canvas_abstract_callback(cid_for_dialog, cid, matches);
}, 450);
}
function gui_quit_dialog() {
gui_raise_pd_window();
var reply = pd_window.window.confirm("Really quit?");
......@@ -4885,7 +4927,7 @@ function zoom_kludge(zoom_level) {
return zfactor;
}
function gui_canvas_popup(cid, xpos, ypos, canprop, canopen, isobject) {
function gui_canvas_popup(cid, xpos, ypos, canprop, canopen, cansaveas, isobject) {
// Get page coords for top of window, in case we're scrolled
gui(cid).get_nw_window(function(nw_win) {
// ico@vt.edu updated win_left and win_top for the 0.46.2
......@@ -4908,6 +4950,7 @@ function gui_canvas_popup(cid, xpos, ypos, canprop, canopen, isobject) {
//popup_coords[1] = ypos;
popup_menu[cid].items[0].enabled = canprop;
popup_menu[cid].items[1].enabled = canopen;
popup_menu[cid].items[2].enabled = cansaveas;
// We'll use "isobject" to enable/disable "To Front" and "To Back"
//isobject;
......
......@@ -2179,6 +2179,246 @@ void canvas_undo_font(t_canvas *x, void *z, int action)
}
}
/* ------------------------------- abstract feature --------------------- */
static t_class *abstracthandler_class;
typedef struct abstracthandler
{
t_object x_obj;
t_symbol *sym; /* symbol bound to the object */
t_canvas *dialog; /* canvas where the dialog will be displayed */
t_canvas *tarjet; /* tarjet subpatch */
char *path; /* abstraction path */
t_binbuf *subpatch; /* subpatch contents */
} t_abstracthandler;
static void *abstracthandler_new(void)
{
t_abstracthandler *x = (t_abstracthandler *)pd_new(abstracthandler_class);
char namebuf[80];
sprintf(namebuf, "ah%lx", (t_int)x);
x->sym = gensym(namebuf);
pd_bind(&x->x_obj.ob_pd, x->sym);
return (x);
}
static void abstracthandler_free(t_abstracthandler *x)
{
freebytes(x->path, MAXPDSTRING);
binbuf_free(x->subpatch);
pd_unbind(&x->x_obj.ob_pd, x->sym);
}
/* gobj_activate, canvas_addtobuf and canvas_buftotex toghether and simplified*/
static void do_rename_light(t_gobj *z, t_glist *glist, const char *text)
{
t_rtext *y = glist_findrtext(glist, (t_text *)z);
glist->gl_editor->e_textedfor = y;
char *buf = getbytes(strlen(text)+1);
strcpy(buf, text);
rtext_settext(y, buf, strlen(text)+1);
glist->gl_editor->e_textdirty = 1;
canvas_dirty(glist, 1);
glist->gl_editor->e_onmotion = MA_NONE; //necessary?
}
/* traverses the whole subtree of the given canvas/patch, replacing all subpatches identical to the
given one with an abstraction */
static int do_replace_subpatches(t_canvas *x, const char* label, t_binbuf *original)
{
t_selection *list = 0;
int edi = 0, num = 0;
/* editor is needed in order to do the operations below */
if(label && !x->gl_editor) { canvas_create_editor(x); edi = 1; }
t_gobj *y, *yn;
canvas_undo_add(x, UNDO_SEQUENCE_START, "replace", "subpatch replacements have been redone. "
"All other possible replacements within other (sub)patches have not been affected");
for(y = x->gl_list; y; y = yn)
{
yn = y->g_next; /* dirty hack,
'canvas_stowconnections' inside 'glist_deselect'
rearranges the glist */
if(pd_class(&y->g_pd) == canvas_class &&
!canvas_isabstraction((t_canvas *)y))
{
t_binbuf *tmp = binbuf_new(), *tmps = binbuf_new();
gobj_save((t_gobj *)y, tmp);
int i = 0, j = binbuf_getnatom(tmp)-2;
t_atom *v = binbuf_getvec(tmp);
while(v[i].a_type != A_SEMI) i++;
i++;
while(v[j].a_type != A_SEMI) j--;
binbuf_restore(tmps, j-i+1, v+i);
binbuf_free(tmp);
if(binbuf_match(original, tmps, 0) &&
binbuf_getnatom(original) == binbuf_getnatom(tmps))
{
if(label)
{
binbuf_free(tmps);
glist_noselect(x);
glist_select(x, y);
do_rename_light(y, x, label);
glist_deselect(x, y);
}
else num++;
}
else
{
binbuf_free(tmps);
/* all the non-matching subpatches are stored in a list, */
t_selection *new = (t_selection *)getbytes(sizeof(t_selection));
new->sel_what = y;
new->sel_next = list;
list = new;
}
}
}
canvas_undo_add(x, UNDO_SEQUENCE_END, "replace", "subpatch replacements have been undone. "
"All other possible replacements within other (sub)patches have not been affected");
if(edi) canvas_destroy_editor(x);
t_selection *z, *zn;
/* the function is called recursively on each non-matching canvas.
this has been left for last due to a visual bug that happens if
they are explored as soon as they are traversed*/
for(z = list; z; z = zn)
{
zn = z->sel_next;
num += do_replace_subpatches((t_canvas *)z->sel_what, label, original);
freebytes(z, sizeof(t_selection));
}
return num;
}
static void abstracthandler_callback(t_abstracthandler *x, t_symbol *s)
{
char fullpath[MAXPDSTRING], label[MAXPDSTRING], *dir, *filename, *o = s->s_name;
memset(fullpath, '\0', MAXPDSTRING); memset(label, '\0', MAXPDSTRING);
sys_unbashfilename(s->s_name, fullpath);
if(strlen(fullpath) < 3 || strcmp(fullpath+strlen(fullpath)-3, ".pd"))
strcat(fullpath, ".pd");
filename = strrchr(fullpath, '/')+1;
fullpath[(int)filename-(int)fullpath-1] = '\0';
dir = fullpath;
int flag, prefix = 0;
if(flag = sys_relativizepath(canvas_getdir(canvas_getrootfor(x->tarjet))->s_name, dir, label))
{
int len = strlen(label), creator, fd = -1;
if(len && label[len-1] != '/') label[len] = '/';
strncat(label, filename, strlen(filename)-3);
/* check if there is a creator with the same name or if it's one of the built-in methods */
t_symbol *sym = gensym(label);
creator = (sym == &s_bang || sym == &s_float || sym == &s_symbol || sym == &s_blob
|| sym == &s_list || sym == &s_anything || zgetfn(&pd_objectmaker, sym));
if(!len && creator) { prefix = 1; creator = 0; }
/* check if there in an abstraction with the same name in the search path */
if(!creator)
{
char opendir[MAXPDSTRING], *filenameptr;
fd = canvas_open(canvas_getrootfor(x->tarjet), label, ".pd", opendir,
&filenameptr, MAXPDSTRING, 0); //high load
if(fd > 0)
{
sys_close(fd);
/* check if we are overwriting the file */
if(!strncmp(dir, opendir, (int)filenameptr-(int)opendir-1)) fd = -1;
}
}
flag = !(creator || (fd > 0));
if(flag && prefix)
{
strcpy(label, "./");
strncat(label, filename, strlen(filename)-3);
}
else if(!flag)
post("warning: couldn't use relative path, there is a coincidence in the creator list or the search path");
}
if(!flag) /* absolute path is required */
{
memset(label, '\0', MAXPDSTRING);
strcpy(label, dir);
strcat(label, "/");
strncat(label, filename, strlen(filename)-3);
/* should check if 'filename' is one of the built-in special methods
in order to inform the user about the nameclash problem */
}
x->path = (char *)getbytes(MAXPDSTRING);
strcpy(x->path, label);
/* save the subpatch into a separated pd file */
t_atom at[3];
SETSYMBOL(at, gensym(filename)); SETSYMBOL(at+1, gensym(dir)); SETFLOAT(at+2, 0.f);
x->tarjet->gl_env = 0xF1A6; /* gl_env is set to non-zero in order to save the subcanvas as a root canvas */
typedmess(&x->tarjet->gl_pd, gensym("savetofile"), 3, at);
x->tarjet->gl_env = 0;
t_binbuf *tmp = binbuf_new(), *tmps = binbuf_new();
gobj_save((t_gobj *)x->tarjet, tmp);
/* only the internals are kept, the position on the parent canvas may differ */
int i = 0, j = binbuf_getnatom(tmp)-2, matches;
t_atom *v = binbuf_getvec(tmp);
while(v[i].a_type != A_SEMI) i++;
i++;
while(v[j].a_type != A_SEMI) j--;
binbuf_restore(tmps, j-i+1, v+i);
binbuf_free(tmp);
matches = do_replace_subpatches(canvas_getrootfor(x->tarjet), 0, tmps);
x->subpatch = tmps;
/* trigger the frontend dialog for replacing options */
gui_vmess("gui_canvas_abstract", "xsi",
x->dialog,
x->sym->s_name,
matches);
}
static void abstracthandler_dialog(t_abstracthandler *x, t_floatarg val)
{
if(x->tarjet == x->dialog) canvas_vis(x->dialog, 0);
t_canvas *owner = x->tarjet->gl_owner, *root = canvas_getrootfor(x->tarjet);
int all = val;
if(!all)
{
/* change the text of the subpatch object to create the abstraction,
emulating the procedure done by the user. could be simplified */
int edi = 0;
if(!owner->gl_editor) { canvas_create_editor(owner); edi = 1; }
glist_noselect(owner);
glist_select(owner, x->tarjet);
do_rename_light(x->tarjet, owner, x->path);
glist_deselect(owner, x->tarjet);
if(edi) canvas_destroy_editor(owner);
/* select '[args]' slice
canvas_editmode(owner, 1);
t_gobj *abst = glist_nth(owner, glist_getindex(owner, 0)-1);
int len = strlen(x->path);
glist_select(owner, abst);
gobj_activate(abst, owner, (0b1 << 31) | (((len+1) & 0x7FFF) << 16) | ((len+7) & 0xFFFF)); */
}
else
{
do_replace_subpatches(root, x->path, x->subpatch);
}
pd_free(&x->x_obj.ob_pd);
}
void abstracthandler_setup(void)
{
abstracthandler_class = class_new(gensym("abstracthandler"), 0,
abstracthandler_free, sizeof(t_abstracthandler),
CLASS_NOINLET, 0);
class_addmethod(abstracthandler_class, (t_method)abstracthandler_callback,
gensym("callback"), A_SYMBOL, 0);
class_addmethod(abstracthandler_class, (t_method)abstracthandler_dialog,
gensym("dialog"), A_FLOAT, 0);
}
/* ------------------------ event handling ------------------------ */
static char *cursorlist[] = {
......@@ -2271,7 +2511,7 @@ static void canvas_rightclick(t_canvas *x, int xpos, int ypos, t_gobj *y_sel)
{
//fprintf(stderr,"e_onmotion=%d\n",x->gl_editor->e_onmotion);
if (x->gl_editor->e_onmotion != MA_NONE) return;
int canprop, canopen, isobject;
int canprop, canopen, isobject, cansaveas;
t_gobj *y = NULL;
int x1, y1, x2, y2, scalar_has_canvas = 0;
if (x->gl_editor->e_selection)
......@@ -2328,12 +2568,19 @@ static void canvas_rightclick(t_canvas *x, int xpos, int ypos, t_gobj *y_sel)
// LATER: consider enabling help and perhaps even limited properties...
return;
}
gui_vmess("gui_canvas_popup", "xiiiii",
/* saveas option, only if it's a canvas and it isn't an abstraction */
cansaveas = (canopen && pd_class(&y->g_pd) == canvas_class &&
!canvas_isabstraction((t_canvas *)y));
/* or if it is the background of a subpatch */
cansaveas = (cansaveas || (!y && canvas_getrootfor(x) != x &&
!canvas_isabstraction((t_canvas *)x)));
gui_vmess("gui_canvas_popup", "xiiiiii",
x,
xpos,
ypos,
canprop,
canopen,
cansaveas,
isobject);
}
......@@ -3078,6 +3325,22 @@ void canvas_done_popup(t_canvas *x, t_float which, t_float xpos,
vmess(&y->g_pd, gensym("menu-open"), "");
return;
}
else if(which == 5) /* saveas */
{
t_abstracthandler *ah = abstracthandler_new();
ah->tarjet = y;
ah->dialog = x;
char buf[MAXPDSTRING];
sprintf(buf, "%s/%s.pd", canvas_getdir(canvas_getrootfor(y))->s_name, ((t_canvas *)y)->gl_name->s_name);
gui_vmess("gui_savepanel", "xss",
x,
ah->sym->s_name,
buf);
return;
}
else if (which == 2) /* help */
{
char *dir;
......@@ -3146,6 +3409,21 @@ void canvas_done_popup(t_canvas *x, t_float which, t_float xpos,
}
else if (which == 2)
open_via_helppath("intro.pd", canvas_getdir((t_canvas *)x)->s_name);
if (which == 5)
{
t_abstracthandler *ah = abstracthandler_new();
ah->tarjet = x;
ah->dialog = x;
char buf[MAXPDSTRING];
sprintf(buf, "%s/%s.pd", canvas_getdir(canvas_getrootfor(x))->s_name, ((t_canvas *)x)->gl_name->s_name);
gui_vmess("gui_savepanel", "xss",
x,
ah->sym->s_name,
buf);
}
}
extern t_class *my_canvas_class; // for ignoring runtime clicks and resizing
......@@ -7815,6 +8093,8 @@ void g_editor_setup(void)
gensym("disconnect"), A_FLOAT, A_FLOAT, A_FLOAT, A_FLOAT, A_NULL);
/* -------------- copy buffer ------------------ */
copy_binbuf = binbuf_new();
abstracthandler_setup();
}
void canvas_editor_for_class(t_class *c)
......
......@@ -587,6 +587,10 @@ void rtext_activate(t_rtext *x, int state)
glist->gl_editor->e_textedfor = 0;
x->x_active = 0;
}
/* check if it has a window */
if(!glist->gl_havewindow) return;
rtext_senditup(x, SEND_UPDATE, &w, &h, &indx);
/* hack...
state = 0 no editing
......
......@@ -110,6 +110,7 @@ void canvas_undo_undo(t_canvas *x)
if(UNDO_SEQUENCE_END == x->u_last->type)
{
int sequence_depth = 1;
if(x->u_last->data) post("undo info: %s", (char *)x->u_last->data);
while((x->u_last = x->u_last->prev)
&& (UNDO_INIT != x->u_last->type))
{
......@@ -120,6 +121,7 @@ void canvas_undo_undo(t_canvas *x)
break;
case UNDO_SEQUENCE_END:
sequence_depth++;
if(x->u_last->data) post("undo info: %s", (char *)x->u_last->data);
break;
default:
canvas_undo_doit(x, x->u_last, UNDO_UNDO);
......@@ -176,6 +178,7 @@ void canvas_undo_redo(t_canvas *x)
if(UNDO_SEQUENCE_START == x->u_last->type)
{
int sequence_depth = 1;
if(x->u_last->data) post("redo info: %s", (char *)x->u_last->data);
while(x->u_last->next && (x->u_last = x->u_last->next))
{
switch(x->u_last->type)
......@@ -185,6 +188,7 @@ void canvas_undo_redo(t_canvas *x)
break;
case UNDO_SEQUENCE_START:
sequence_depth++;
if(x->u_last->data) post("redo info: %s", (char *)x->u_last->data);
break;
default:
canvas_undo_doit(x, x->u_last, UNDO_REDO);
......
......@@ -195,6 +195,60 @@ int sys_isabsolutepath(const char *dir)
}
}
int sys_relativizepath(const char *from, const char *to, char *result)
{
char fromext[FILENAME_MAX];
sys_unbashfilename(from, fromext);
char toext[FILENAME_MAX];
sys_unbashfilename(to, toext);
int i = 0, j;
while(fromext[i] && toext[i] && fromext[i] == toext[i]) i++;
if(!i) return 0;
j = i;
if(fromext[i])
while(i > 0 && fromext[i] != '/') i--;
if(toext[j])
while(j > 0 && toext[j] != '/') j--;
if(fromext[i])
{
int k = 0;
while(fromext[i])
{
if(fromext[i] == '/')
{
if(k == 0)
{
strcpy(result+k, "..");
k += 2;
}
else
{
strcpy(result+k, "/..");
k += 3;
}
}
i++;
}
if(toext[j])
{
result[k] = '/';
strcpy(result+k+1, toext+j+1);
}
}
else if(!fromext[i] && toext[j])
{
strcpy(result, toext+j+1);
}
else
{
strcpy(result, "");
}
return 1;
}
/******************* Utility functions used below ******************/
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment