diff options
Diffstat (limited to 'apps/plugins/pdbox/PDa/src/g_editor.c')
| -rw-r--r-- | apps/plugins/pdbox/PDa/src/g_editor.c | 4548 |
1 files changed, 4548 insertions, 0 deletions
diff --git a/apps/plugins/pdbox/PDa/src/g_editor.c b/apps/plugins/pdbox/PDa/src/g_editor.c new file mode 100644 index 0000000..a1dea79 --- /dev/null +++ b/apps/plugins/pdbox/PDa/src/g_editor.c @@ -0,0 +1,4548 @@ +/* Copyright (c) 1997-2001 Miller Puckette and others. +* For information on usage and redistribution, and for a DISCLAIMER OF ALL +* WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ + +#include <stdlib.h> +#include <stdio.h> +#include "m_pd.h" +#include "m_imp.h" +#include "s_stuff.h" +#include "g_canvas.h" +#include <string.h> + +void glist_readfrombinbuf(t_glist *x, t_binbuf *b, char *filename, + int selectem); + +void open_via_helppath(const char *name, const char *dir); +char *class_gethelpdir(t_class *c); + +/* ------------------ forward declarations --------------- */ +static void canvas_doclear(t_canvas *x); +static void glist_setlastxy(t_glist *gl, int xval, int yval); +static void glist_donewloadbangs(t_glist *x); +static t_binbuf *canvas_docopy(t_canvas *x); +static void canvas_dopaste(t_canvas *x, t_binbuf *b); +static void canvas_paste(t_canvas *x); +static void canvas_clearline(t_canvas *x); +static t_binbuf *copy_binbuf; + +/* ---------------- generic widget behavior ------------------------- */ + +void gobj_getrect(t_gobj *x, t_glist *glist, int *x1, int *y1, + int *x2, int *y2) +{ + if (x->g_pd->c_wb && x->g_pd->c_wb->w_getrectfn) + (*x->g_pd->c_wb->w_getrectfn)(x, glist, x1, y1, x2, y2); +} + +void gobj_displace(t_gobj *x, t_glist *glist, int dx, int dy) +{ + if (x->g_pd->c_wb && x->g_pd->c_wb->w_displacefn) + (*x->g_pd->c_wb->w_displacefn)(x, glist, dx, dy); +} + +void gobj_select(t_gobj *x, t_glist *glist, int state) +{ + if (x->g_pd->c_wb && x->g_pd->c_wb->w_selectfn) + (*x->g_pd->c_wb->w_selectfn)(x, glist, state); +} + +void gobj_activate(t_gobj *x, t_glist *glist, int state) +{ + if (x->g_pd->c_wb && x->g_pd->c_wb->w_activatefn) + (*x->g_pd->c_wb->w_activatefn)(x, glist, state); +} + +void gobj_delete(t_gobj *x, t_glist *glist) +{ + if (x->g_pd->c_wb && x->g_pd->c_wb->w_deletefn) + (*x->g_pd->c_wb->w_deletefn)(x, glist); +} + +void gobj_vis(t_gobj *x, struct _glist *glist, int flag) +{ + if (x->g_pd->c_wb && x->g_pd->c_wb->w_visfn) + (*x->g_pd->c_wb->w_visfn)(x, glist, flag); +} + +int gobj_click(t_gobj *x, struct _glist *glist, + int xpix, int ypix, int shift, int alt, int dbl, int doit) +{ + if (x->g_pd->c_wb && x->g_pd->c_wb->w_clickfn) + return ((*x->g_pd->c_wb->w_clickfn)(x, + glist, xpix, ypix, shift, alt, dbl, doit)); + else return (0); +} + +/* ------------------------ managing the selection ----------------- */ + +void glist_selectline(t_glist *x, t_outconnect *oc, int index1, + int outno, int index2, int inno) +{ + if (x->gl_editor) + { + glist_noselect(x); + x->gl_editor->e_selectedline = 1; + x->gl_editor->e_selectline_index1 = index1; + x->gl_editor->e_selectline_outno = outno; + x->gl_editor->e_selectline_index2 = index2; + x->gl_editor->e_selectline_inno = inno; + x->gl_editor->e_selectline_tag = oc; + sys_vgui(".x%x.c itemconfigure l%x -fill blue\n", + x, x->gl_editor->e_selectline_tag); + } +} + +void glist_deselectline(t_glist *x) +{ + if (x->gl_editor) + { + x->gl_editor->e_selectedline = 0; + sys_vgui(".x%x.c itemconfigure l%x -fill black\n", + x, x->gl_editor->e_selectline_tag); + } +} + +int glist_isselected(t_glist *x, t_gobj *y) +{ + if (x->gl_editor) + { + t_selection *sel; + for (sel = x->gl_editor->e_selection; sel; sel = sel->sel_next) + if (sel->sel_what == y) return (1); + } + return (0); +} + + /* call this for unselected objects only */ +void glist_select(t_glist *x, t_gobj *y) +{ + if (x->gl_editor) + { + t_selection *sel = (t_selection *)getbytes(sizeof(*sel)); + if (x->gl_editor->e_selectedline) + glist_deselectline(x); + /* LATER #ifdef out the following check */ + if (glist_isselected(x, y)) bug("glist_select"); + sel->sel_next = x->gl_editor->e_selection; + sel->sel_what = y; + x->gl_editor->e_selection = sel; + gobj_select(y, x, 1); + } +} + + /* call this for selected objects only */ +void glist_deselect(t_glist *x, t_gobj *y) +{ + int fixdsp = 0; + static int reenter = 0; + if (reenter) return; + reenter = 1; + if (x->gl_editor) + { + t_selection *sel, *sel2; + t_rtext *z = 0; + if (!glist_isselected(x, y)) bug("glist_deselect"); + if (x->gl_editor->e_textedfor) + { + t_rtext *fuddy = glist_findrtext(x, (t_text *)y); + if (x->gl_editor->e_textedfor == fuddy) + { + if (x->gl_editor->e_textdirty) + { + z = fuddy; + canvas_stowconnections(glist_getcanvas(x)); + } + gobj_activate(y, x, 0); + } + if (zgetfn(&y->g_pd, gensym("dsp"))) + fixdsp = 1; + } + if ((sel = x->gl_editor->e_selection)->sel_what == y) + { + x->gl_editor->e_selection = x->gl_editor->e_selection->sel_next; + gobj_select(sel->sel_what, x, 0); + freebytes(sel, sizeof(*sel)); + } + else + { + for (sel = x->gl_editor->e_selection; sel2 = sel->sel_next; + sel = sel2) + { + if (sel2->sel_what == y) + { + sel->sel_next = sel2->sel_next; + gobj_select(sel2->sel_what, x, 0); + freebytes(sel2, sizeof(*sel2)); + break; + } + } + } + if (z) + { + char *buf; + int bufsize; + + rtext_gettext(z, &buf, &bufsize); + text_setto((t_text *)y, x, buf, bufsize); + canvas_fixlinesfor(glist_getcanvas(x), (t_text *)y); + x->gl_editor->e_textedfor = 0; + } + if (fixdsp) + canvas_update_dsp(); + } + reenter = 0; +} + +void glist_noselect(t_glist *x) +{ + if (x->gl_editor) + { + while (x->gl_editor->e_selection) + glist_deselect(x, x->gl_editor->e_selection->sel_what); + if (x->gl_editor->e_selectedline) + glist_deselectline(x); + } +} + +void glist_selectall(t_glist *x) +{ + if (x->gl_editor) + { + glist_noselect(x); + if (x->gl_list) + { + t_selection *sel = (t_selection *)getbytes(sizeof(*sel)); + t_gobj *y = x->gl_list; + x->gl_editor->e_selection = sel; + sel->sel_what = y; + gobj_select(y, x, 1); + while (y = y->g_next) + { + t_selection *sel2 = (t_selection *)getbytes(sizeof(*sel2)); + sel->sel_next = sel2; + sel = sel2; + sel->sel_what = y; + gobj_select(y, x, 1); + } + sel->sel_next = 0; + } + } +} + + /* get the index of a gobj in a glist. If y is zero, return the + total number of objects. */ +int glist_getindex(t_glist *x, t_gobj *y) +{ + t_gobj *y2; + int indx; + + for (y2 = x->gl_list, indx = 0; y2 && y2 != y; y2 = y2->g_next) + indx++; + return (indx); +} + + /* get the index of the object, among selected items, if "selected" + is set; otherwise, among unselected ones. If y is zero, just + counts the selected or unselected objects. */ +int glist_selectionindex(t_glist *x, t_gobj *y, int selected) +{ + t_gobj *y2; + int indx; + + for (y2 = x->gl_list, indx = 0; y2 && y2 != y; y2 = y2->g_next) + if (selected == glist_isselected(x, y2)) + indx++; + return (indx); +} + +static t_gobj *glist_nth(t_glist *x, int n) +{ + t_gobj *y; + int indx; + for (y = x->gl_list, indx = 0; y; y = y->g_next, indx++) + if (indx == n) + return (y); + return (0); +} + +/* ------------------- support for undo/redo -------------------------- */ + +static t_undofn canvas_undo_fn; /* current undo function if any */ +static int canvas_undo_whatnext; /* whether we can now UNDO or REDO */ +static void *canvas_undo_buf; /* data private to the undo function */ +static t_canvas *canvas_undo_canvas; /* which canvas we can undo on */ +static const char *canvas_undo_name; + +void canvas_setundo(t_canvas *x, t_undofn undofn, void *buf, + const char *name) +{ + int hadone = 0; + /* blow away the old undo information. In one special case the + old undo info is re-used; if so we shouldn't free it here. */ + if (canvas_undo_fn && canvas_undo_buf && (buf != canvas_undo_buf)) + { + (*canvas_undo_fn)(canvas_undo_canvas, canvas_undo_buf, UNDO_FREE); + hadone = 1; + } + canvas_undo_canvas = x; + canvas_undo_fn = undofn; + canvas_undo_buf = buf; + canvas_undo_whatnext = UNDO_UNDO; + canvas_undo_name = name; + if (x && glist_isvisible(x) && glist_istoplevel(x)) + /* enable undo in menu */ + sys_vgui("pdtk_undomenu .x%x %s no\n", x, name); + else if (hadone) + sys_vgui("pdtk_undomenu nobody no no\n"); +} + + /* clear undo if it happens to be for the canvas x. + (but if x is 0, clear it regardless of who owns it.) */ +void canvas_noundo(t_canvas *x) +{ + if (!x || (x == canvas_undo_canvas)) + canvas_setundo(0, 0, 0, "foo"); +} + +static void canvas_undo(t_canvas *x) +{ + if (x != canvas_undo_canvas) + bug("canvas_undo 1"); + else if (canvas_undo_whatnext != UNDO_UNDO) + bug("canvas_undo 2"); + else + { + /* post("undo"); */ + (*canvas_undo_fn)(canvas_undo_canvas, canvas_undo_buf, UNDO_UNDO); + /* enable redo in menu */ + if (glist_isvisible(x) && glist_istoplevel(x)) + sys_vgui("pdtk_undomenu .x%x no %s\n", x, canvas_undo_name); + canvas_undo_whatnext = UNDO_REDO; + } +} + +static void canvas_redo(t_canvas *x) +{ + if (x != canvas_undo_canvas) + bug("canvas_undo 1"); + else if (canvas_undo_whatnext != UNDO_REDO) + bug("canvas_undo 2"); + else + { + /* post("redo"); */ + (*canvas_undo_fn)(canvas_undo_canvas, canvas_undo_buf, UNDO_REDO); + /* enable undo in menu */ + if (glist_isvisible(x) && glist_istoplevel(x)) + sys_vgui("pdtk_undomenu .x%x %s no\n", x, canvas_undo_name); + canvas_undo_whatnext = UNDO_UNDO; + } +} + +/* ------- specific undo methods: 1. connect and disconnect -------- */ + +typedef struct _undo_connect +{ + int u_index1; + int u_outletno; + int u_index2; + int u_inletno; +} t_undo_connect; + +static void *canvas_undo_set_disconnect(t_canvas *x, + int index1, int outno, int index2, int inno) +{ + t_undo_connect *buf = (t_undo_connect *)getbytes(sizeof(*buf)); + buf->u_index1 = index1; + buf->u_outletno = outno; + buf->u_index2 = index2; + buf->u_inletno = inno; + return (buf); +} + +void canvas_disconnect(t_canvas *x, + float index1, float outno, float index2, float inno) +{ + t_linetraverser t; + t_outconnect *oc; + linetraverser_start(&t, x); + while (oc = linetraverser_next(&t)) + { + int srcno = canvas_getindex(x, &t.tr_ob->ob_g); + int sinkno = canvas_getindex(x, &t.tr_ob2->ob_g); + if (srcno == index1 && t.tr_outno == outno && + sinkno == index2 && t.tr_inno == inno) + { + sys_vgui(".x%x.c delete l%x\n", x, oc); + obj_disconnect(t.tr_ob, t.tr_outno, t.tr_ob2, t.tr_inno); + break; + } + } +} + +static void canvas_undo_disconnect(t_canvas *x, void *z, int action) +{ + t_undo_connect *buf = z; + if (action == UNDO_UNDO) + { + canvas_connect(x, buf->u_index1, buf->u_outletno, + buf->u_index2, buf->u_inletno); + } + else if (action == UNDO_REDO) + { + canvas_disconnect(x, buf->u_index1, buf->u_outletno, + buf->u_index2, buf->u_inletno); + } + else if (action == UNDO_FREE) + t_freebytes(buf, sizeof(*buf)); +} + + /* connect just calls disconnect actions backward... */ +static void *canvas_undo_set_connect(t_canvas *x, + int index1, int outno, int index2, int inno) +{ + return (canvas_undo_set_disconnect(x, index1, outno, index2, inno)); +} + +static void canvas_undo_connect(t_canvas *x, void *z, int action) +{ + int myaction; + if (action == UNDO_UNDO) + myaction = UNDO_REDO; + else if (action == UNDO_REDO) + myaction = UNDO_UNDO; + else myaction = action; + canvas_undo_disconnect(x, z, myaction); +} + +/* ---------- ... 2. cut, clear, and typing into objects: -------- */ + +#define UCUT_CUT 1 /* operation was a cut */ +#define UCUT_CLEAR 2 /* .. a clear */ +#define UCUT_TEXT 3 /* text typed into a box */ + +typedef struct _undo_cut +{ + t_binbuf *u_objectbuf; /* the object cleared or typed into */ + t_binbuf *u_reconnectbuf; /* connections into and out of object */ + t_binbuf *u_redotextbuf; /* buffer to paste back for redo if TEXT */ + int u_mode; /* from flags above */ +} t_undo_cut; + +static void *canvas_undo_set_cut(t_canvas *x, int mode) +{ + t_undo_cut *buf; + t_gobj *y; + t_linetraverser t; + t_outconnect *oc; + int nnotsel= glist_selectionindex(x, 0, 0); + buf = (t_undo_cut *)getbytes(sizeof(*buf)); + buf->u_mode = mode; + buf->u_redotextbuf = 0; + + /* store connections into/out of the selection */ + buf->u_reconnectbuf = binbuf_new(); + linetraverser_start(&t, x); + while (oc = linetraverser_next(&t)) + { + int issel1 = glist_isselected(x, &t.tr_ob->ob_g); + int issel2 = glist_isselected(x, &t.tr_ob2->ob_g); + if (issel1 != issel2) + { + binbuf_addv(buf->u_reconnectbuf, "ssiiii;", + gensym("#X"), gensym("connect"), + (issel1 ? nnotsel : 0) + + glist_selectionindex(x, &t.tr_ob->ob_g, issel1), + t.tr_outno, + (issel2 ? nnotsel : 0) + + glist_selectionindex(x, &t.tr_ob2->ob_g, issel2), + t.tr_inno); + } + } + if (mode == UCUT_TEXT) + { + buf->u_objectbuf = canvas_docopy(x); + } + else if (mode == UCUT_CUT) + { + buf->u_objectbuf = 0; + } + else if (mode == UCUT_CLEAR) + { + buf->u_objectbuf = canvas_docopy(x); + } + return (buf); +} + +static void canvas_undo_cut(t_canvas *x, void *z, int action) +{ + t_undo_cut *buf = z; + int mode = buf->u_mode; + if (action == UNDO_UNDO) + { + if (mode == UCUT_CUT) + canvas_dopaste(x, copy_binbuf); + else if (mode == UCUT_CLEAR) + canvas_dopaste(x, buf->u_objectbuf); + else if (mode == UCUT_TEXT) + { + t_gobj *y1, *y2; + glist_noselect(x); + for (y1 = x->gl_list; y2 = y1->g_next; y1 = y2) + ; + if (y1) + { + if (!buf->u_redotextbuf) + { + glist_noselect(x); + glist_select(x, y1); + buf->u_redotextbuf = canvas_docopy(x); + glist_noselect(x); + } + glist_delete(x, y1); + } + canvas_dopaste(x, buf->u_objectbuf); + } + pd_bind(&x->gl_pd, gensym("#X")); + binbuf_eval(buf->u_reconnectbuf, 0, 0, 0); + pd_unbind(&x->gl_pd, gensym("#X")); + } + else if (action == UNDO_REDO) + { + if (mode == UCUT_CUT || mode == UCUT_CLEAR) + canvas_doclear(x); + else if (mode == UCUT_TEXT) + { + t_gobj *y1, *y2; + for (y1 = x->gl_list; y2 = y1->g_next; y1 = y2) + ; + if (y1) + glist_delete(x, y1); + canvas_dopaste(x, buf->u_redotextbuf); + pd_bind(&x->gl_pd, gensym("#X")); + binbuf_eval(buf->u_reconnectbuf, 0, 0, 0); + pd_unbind(&x->gl_pd, gensym("#X")); + } + } + else if (action == UNDO_FREE) + { + if (buf->u_objectbuf) + binbuf_free(buf->u_objectbuf); + if (buf->u_reconnectbuf) + binbuf_free(buf->u_reconnectbuf); + if (buf->u_redotextbuf) + binbuf_free(buf->u_redotextbuf); + t_freebytes(buf, sizeof(*buf)); + } +} + +/* --------- 3. motion, including "tidy up" and stretching ----------- */ + +typedef struct _undo_move_elem +{ + int e_index; + int e_xpix; + int e_ypix; +} t_undo_move_elem; + +typedef struct _undo_move +{ + t_undo_move_elem *u_vec; + int u_n; +} t_undo_move; + +static int canvas_undo_already_set_move; + +static void *canvas_undo_set_move(t_canvas *x, int selected) +{ + int x1, y1, x2, y2, i, indx; + t_gobj *y; + t_undo_move *buf = (t_undo_move *)getbytes(sizeof(*buf)); + buf->u_n = selected ? glist_selectionindex(x, 0, 1) : glist_getindex(x, 0); + buf->u_vec = (t_undo_move_elem *)getbytes(sizeof(*buf->u_vec) * + (selected ? glist_selectionindex(x, 0, 1) : glist_getindex(x, 0))); + if (selected) + { + for (y = x->gl_list, i = indx = 0; y; y = y->g_next, indx++) + if (glist_isselected(x, y)) + { + gobj_getrect(y, x, &x1, &y1, &x2, &y2); + buf->u_vec[i].e_index = indx; + buf->u_vec[i].e_xpix = x1; + buf->u_vec[i].e_ypix = y1; + i++; + } + } + else + { + for (y = x->gl_list, indx = 0; y; y = y->g_next, indx++) + { + gobj_getrect(y, x, &x1, &y1, &x2, &y2); + buf->u_vec[indx].e_index = indx; + buf->u_vec[indx].e_xpix = x1; + buf->u_vec[indx].e_ypix = y1; + } + } + canvas_undo_already_set_move = 1; + return (buf); +} + +static void canvas_undo_move(t_canvas *x, void *z, int action) +{ + t_undo_move *buf = z; + if (action == UNDO_UNDO || action == UNDO_REDO) + { + int i; + for (i = 0; i < buf->u_n; i++) + { + int x1, y1, x2, y2, newx, newy; + t_gobj *y; + newx = buf->u_vec[i].e_xpix; + newy = buf->u_vec[i].e_ypix; + y = glist_nth(x, buf->u_vec[i].e_index); + if (y) + { + gobj_getrect(y, x, &x1, &y1, &x2, &y2); + gobj_displace(y, x, newx-x1, newy - y1); + buf->u_vec[i].e_xpix = x1; + buf->u_vec[i].e_ypix = y1; + } + } + } + else if (action == UNDO_FREE) + { + t_freebytes(buf->u_vec, buf->u_n * sizeof(*buf->u_vec)); + t_freebytes(buf, sizeof(*buf)); + } +} + +/* --------- 4. paste (also duplicate) ----------- */ + +typedef struct _undo_paste +{ + int u_index; /* index of first object pasted */ +} t_undo_paste; + +static void *canvas_undo_set_paste(t_canvas *x) +{ + t_undo_paste *buf = (t_undo_paste *)getbytes(sizeof(*buf)); + buf->u_index = glist_getindex(x, 0); + return (buf); +} + +static void canvas_undo_paste(t_canvas *x, void *z, int action) +{ + t_undo_paste *buf = z; + if (action == UNDO_UNDO) + { + t_gobj *y; + glist_noselect(x); + for (y = glist_nth(x, buf->u_index); y; y = y->g_next) + glist_select(x, y); + canvas_doclear(x); + } + else if (action == UNDO_REDO) + { + t_selection *sel; + canvas_dopaste(x, copy_binbuf); + /* if it was "duplicate" have to re-enact the displacement. */ + if (canvas_undo_name && canvas_undo_name[0] == 'd') + for (sel = x->gl_editor->e_selection; sel; sel = sel->sel_next) + gobj_displace(sel->sel_what, x, 10, 10); + } +else if (action == UNDO_FREE) + t_freebytes(buf, sizeof(*buf)); +} + + /* recursively check for abstractions to reload as result of a save. + Don't reload the one we just saved ("except") though. */ + /* LATER try to do the same trick for externs. */ +static void glist_doreload(t_glist *gl, t_symbol *name, t_symbol *dir, + t_gobj *except) +{ + t_gobj *g; + int i, nobj = glist_getindex(gl, 0); /* number of objects */ + for (g = gl->gl_list, i = 0; g && i < nobj; i++) + { + if (g != except && pd_class(&g->g_pd) == canvas_class && + canvas_isabstraction((t_canvas *)g) && + ((t_canvas *)g)->gl_name == name && + canvas_getdir((t_canvas *)g) == dir) + { + /* we're going to remake the object, so "g" will go stale. + Get its index here, and afterward restore g. Also, the + replacement will be at teh end of the list, so we don't + do g = g->g_next in this case. */ + int j = glist_getindex(gl, g); + if (!gl->gl_havewindow) + canvas_vis(glist_getcanvas(gl), 1); + glist_noselect(gl); + glist_select(gl, g); + canvas_setundo(gl, canvas_undo_cut, + canvas_undo_set_cut(gl, UCUT_CLEAR), "clear"); + canvas_doclear(gl); + canvas_undo(gl); + glist_noselect(gl); + g = glist_nth(gl, j); + } + else + { + if (g != except && pd_class(&g->g_pd) == canvas_class) + glist_doreload((t_canvas *)g, name, dir, except); + g = g->g_next; + } + } +} + + /* call canvas_doreload on everyone */ +void canvas_reload(t_symbol *name, t_symbol *dir, t_gobj *except) +{ + t_canvas *x; + /* find all root canvases */ + for (x = canvas_list; x; x = x->gl_next) + glist_doreload(x, name, dir, except); +} + +/* ------------------------ event handling ------------------------ */ + +#define CURSOR_RUNMODE_NOTHING 0 +#define CURSOR_RUNMODE_CLICKME 1 +#define CURSOR_RUNMODE_THICKEN 2 +#define CURSOR_RUNMODE_ADDPOINT 3 +#define CURSOR_EDITMODE_NOTHING 4 +#define CURSOR_EDITMODE_CONNECT 5 +#define CURSOR_EDITMODE_DISCONNECT 6 + +static char *cursorlist[] = { +#ifdef MSW + "right_ptr", /* CURSOR_RUNMODE_NOTHING */ +#else + "left_ptr", /* CURSOR_RUNMODE_NOTHING */ +#endif + "arrow", /* CURSOR_RUNMODE_CLICKME */ + "sb_v_double_arrow", /* CURSOR_RUNMODE_THICKEN */ + "plus", /* CURSOR_RUNMODE_ADDPOINT */ + "hand2", /* CURSOR_EDITMODE_NOTHING */ + "circle", /* CURSOR_EDITMODE_CONNECT */ + "X_cursor" /* CURSOR_EDITMODE_DISCONNECT */ +}; + +void canvas_setcursor(t_canvas *x, unsigned int cursornum) +{ + static t_canvas *xwas; + static unsigned int cursorwas; + if (cursornum >= sizeof(cursorlist)/sizeof *cursorlist) + { + bug("canvas_setcursor"); + return; + } + if (xwas != x || cursorwas != cursornum) + { + sys_vgui(".x%x configure -cursor %s\n", x, cursorlist[cursornum]); + xwas = x; + cursorwas = cursornum; + } +} + + /* check if a point lies in a gobj. */ +int canvas_hitbox(t_canvas *x, t_gobj *y, int xpos, int ypos, + int *x1p, int *y1p, int *x2p, int *y2p) +{ + int x1, y1, x2, y2; + t_text *ob; + if ((ob = pd_checkobject(&y->g_pd)) && + !text_shouldvis(ob, x)) + return (0); + gobj_getrect(y, x, &x1, &y1, &x2, &y2); + if (xpos >= x1 && xpos <= x2 && ypos >= y1 && ypos <= y2) + { + *x1p = x1; + *y1p = y1; + *x2p = x2; + *y2p = y2; + return (1); + } + else return (0); +} + + /* find the last gobj, if any, containing the point. */ +static t_gobj *canvas_findhitbox(t_canvas *x, int xpos, int ypos, + int *x1p, int *y1p, int *x2p, int *y2p) +{ + t_gobj *y, *rval = 0; + for (y = x->gl_list; y; y = y->g_next) + { + if (canvas_hitbox(x, y, xpos, ypos, x1p, y1p, x2p, y2p)) + rval = y; + } + return (rval); +} + + /* right-clicking on a canvas object pops up a menu. */ +static void canvas_rightclick(t_canvas *x, int xpos, int ypos, t_gobj *y) +{ + int canprop, canopen; + canprop = (!y || (y && class_getpropertiesfn(pd_class(&y->g_pd)))); + canopen = (y && zgetfn(&y->g_pd, gensym("menu-open"))); + sys_vgui("pdtk_canvas_popup .x%x %d %d %d %d\n", + x, xpos, ypos, canprop, canopen); +} + + /* tell GUI to create a properties dialog on the canvas. We tell + the user the negative of the "pixel" y scale to make it appear to grow + naturally upward, whereas pixels grow downward. */ +static void canvas_properties(t_glist *x) +{ + char graphbuf[200]; + sprintf(graphbuf, "pdtk_canvas_dialog %%s %g %g %g %g \n", + glist_dpixtodx(x, 1), -glist_dpixtody(x, 1), + (float)glist_isgraph(x), (float)x->gl_stretch); + gfxstub_new(&x->gl_pd, x, graphbuf); +} + + +void canvas_setgraph(t_glist *x, int flag) +{ + if (!flag && glist_isgraph(x)) + { + if (x->gl_owner && !x->gl_loading && glist_isvisible(x->gl_owner)) + gobj_vis(&x->gl_gobj, x->gl_owner, 0); + x->gl_isgraph = 0; + if (x->gl_owner && !x->gl_loading && glist_isvisible(x->gl_owner)) + { + gobj_vis(&x->gl_gobj, x->gl_owner, 1); + canvas_fixlinesfor(x->gl_owner, &x->gl_obj); + } + } + else if (flag && !glist_isgraph(x)) + { + if (x->gl_pixwidth <= 0) + x->gl_pixwidth = GLIST_DEFGRAPHWIDTH; + + if (x->gl_pixheight <= 0) + x->gl_pixheight = GLIST_DEFGRAPHHEIGHT; + + if (x->gl_owner && !x->gl_loading && glist_isvisible(x->gl_owner)) + gobj_vis(&x->gl_gobj, x->gl_owner, 0); + x->gl_isgraph = 1; + /* if (x->gl_owner && glist_isvisible(x->gl_owner)) + canvas_vis(x, 1); */ + if (x->gl_loading && x->gl_owner && glist_isvisible(x->gl_owner)) + canvas_create_editor(x, 1); + if (x->gl_owner && !x->gl_loading && glist_isvisible(x->gl_owner)) + { + gobj_vis(&x->gl_gobj, x->gl_owner, 1); + canvas_fixlinesfor(x->gl_owner, &x->gl_obj); + } + } +} + + /* called from the gui when "OK" is selected on the canvas properties + dialog. Again we negate "y" scale. */ +static void canvas_donecanvasdialog(t_glist *x, t_floatarg xperpix, + t_floatarg yperpix, t_floatarg fgraphme) +{ + int graphme = (fgraphme != 0), redraw = 0; + yperpix = -yperpix; + if (xperpix == 0) + xperpix = 1; + if (yperpix == 0) + yperpix = 1; + canvas_setgraph(x, graphme); + if (!x->gl_isgraph && (xperpix != glist_dpixtodx(x, 1))) + { + if (xperpix > 0) + { + x->gl_x1 = 0; + x->gl_x2 = xperpix; + } + else + { + x->gl_x1 = -xperpix * (x->gl_screenx2 - x->gl_screenx1); + x->gl_x2 = x->gl_x1 + xperpix; + } + redraw = 1; + } + if (!x->gl_isgraph && (yperpix != glist_dpixtody(x, 1))) + { + if (yperpix > 0) + { + x->gl_y1 = 0; + x->gl_y2 = yperpix; + } + else + { + x->gl_y1 = -yperpix * (x->gl_screeny2 - x->gl_screeny1); + x->gl_y2 = x->gl_y1 + yperpix; + } + redraw = 1; + } + if (redraw) + canvas_redraw(x); +} + + /* called from the gui when a popup menu comes back with "properties," + "open," or "help." */ +static void canvas_done_popup(t_canvas *x, float which, float xpos, float ypos) +{ + char pathbuf[MAXPDSTRING], namebuf[MAXPDSTRING]; + t_gobj *y; + for (y = x->gl_list; y; y = y->g_next) + { + int x1, y1, x2, y2; + if (canvas_hitbox(x, y, xpos, ypos, &x1, &y1, &x2, &y2)) + { + if (which == 0) /* properties */ + { + if (!class_getpropertiesfn(pd_class(&y->g_pd))) + continue; + (*class_getpropertiesfn(pd_class(&y->g_pd)))(y, x); + return; + } + else if (which == 1) /* open */ + { + if (!zgetfn(&y->g_pd, gensym("menu-open"))) + continue; + vmess(&y->g_pd, gensym("menu-open"), ""); + return; + } + else /* help */ + { + char *dir; + if (pd_class(&y->g_pd) == canvas_class && + canvas_isabstraction((t_canvas *)y)) + { + t_object *ob = (t_object *)y; + int ac = binbuf_getnatom(ob->te_binbuf); + t_atom *av = binbuf_getvec(ob->te_binbuf); + if (ac < 1) + return; + atom_string(av, namebuf, MAXPDSTRING); + dir = canvas_getdir((t_canvas *)y)->s_name; + } + else + { + strcpy(namebuf, class_gethelpname(pd_class(&y->g_pd))); + dir = class_gethelpdir(pd_class(&y->g_pd)); + } + if (strcmp(namebuf + strlen(namebuf) - 3, ".pd")) + strcat(namebuf, ".pd"); + open_via_helppath(namebuf, dir); + return; + } + } + } + if (which == 0) + canvas_properties(x); + else if (which == 2) + { + strcpy(pathbuf, sys_libdir->s_name); + strcat(pathbuf, "/doc/5.reference/0.INTRO.txt"); + sys_vgui("menu_opentext %s\n", pathbuf); + } +} + +#define NOMOD 0 +#define SHIFTMOD 1 +#define CTRLMOD 2 +#define ALTMOD 4 +#define RIGHTCLICK 8 + +/* on one-button-mouse machines, you can use double click to + mean right click (which gets the popup menu.) Do this for Mac. */ +#ifdef MACOSX +#define SIMULATERIGHTCLICK +#endif + +#ifdef SIMULATERIGHTCLICK +static double canvas_upclicktime; +static int canvas_upx, canvas_upy; +#define DCLICKINTERVAL 0.25 +#endif + + /* mouse click */ +void canvas_doclick(t_canvas *x, int xpos, int ypos, int which, + int mod, int doit) +{ + t_gobj *y; + int shiftmod, runmode, altmod, rightclick; + int x1, y1, x2, y2, clickreturned = 0; + + if (!x->gl_editor) + { + bug("editor"); + return; + } + + shiftmod = (mod & SHIFTMOD); + runmode = ((mod & CTRLMOD) || (!x->gl_edit)); + altmod = (mod & ALTMOD); + rightclick = (mod & RIGHTCLICK); + + canvas_undo_already_set_move = 0; + + /* if keyboard was grabbed, notify grabber and cancel the grab */ + if (doit && x->gl_editor->e_grab && x->gl_editor->e_keyfn) + { + (* x->gl_editor->e_keyfn) (x->gl_editor->e_grab, 0); + glist_grab(x, 0, 0, 0, 0, 0); + } + +#ifdef SIMULATERIGHTCLICK + if (doit && !runmode && xpos == canvas_upx && ypos == canvas_upy && + sys_getrealtime() - canvas_upclicktime < DCLICKINTERVAL) + rightclick = 1; +#endif + + x->gl_editor->e_lastmoved = 0; + if (doit) + { + x->gl_editor->e_grab = 0; + x->gl_editor->e_onmotion = MA_NONE; + } + /* post("click %d %d %d %d", xpos, ypos, which, mod); */ + + if (x->gl_editor->e_onmotion != MA_NONE) + return; + + x->gl_editor->e_xwas = xpos; + x->gl_editor->e_ywas = ypos; + + if (runmode && !rightclick) + { + for (y = x->gl_list; y; y = y->g_next) + { + /* check if the object wants to be clicked */ + if (canvas_hitbox(x, y, xpos, ypos, &x1, &y1, &x2, &y2) + && (clickreturned = gobj_click(y, x, xpos, ypos, + shiftmod, altmod, 0, doit))) + break; + } + if (!doit) + { + if (y) + canvas_setcursor(x, clickreturned); + else canvas_setcursor(x, CURSOR_RUNMODE_NOTHING); + } + return; + } + /* if not a runmode left click, fall here. */ + if (y = canvas_findhitbox(x, xpos, ypos, &x1, &y1, &x2, &y2)) + { + t_object *ob = pd_checkobject(&y->g_pd); + /* check you're in the rectangle */ + ob = pd_checkobject(&y->g_pd); + if (rightclick) + canvas_rightclick(x, xpos, ypos, y); + else if (shiftmod) + { + if (doit) + { + t_rtext *rt; + if (ob && (rt = x->gl_editor->e_textedfor) && + rt == glist_findrtext(x, ob)) + { + rtext_mouse(rt, xpos - x1, ypos - y1, RTEXT_SHIFT); + x->gl_editor->e_onmotion = MA_DRAGTEXT; + x->gl_editor->e_xwas = x1; + x->gl_editor->e_ywas = y1; + } + else + { + if (glist_isselected(x, y)) + glist_deselect(x, y); + else glist_select(x, y); + } + } + } + else + { + /* look for an outlet */ + int noutlet; + if (ob && (noutlet = obj_noutlets(ob)) && ypos >= y2-4) + { + int width = x2 - x1; + int nout1 = (noutlet > 1 ? noutlet - 1 : 1); + int closest = ((xpos-x1) * (nout1) + width/2)/width; + int hotspot = x1 + + (width - IOWIDTH) * closest / (nout1); + if (closest < noutlet && + xpos >= (hotspot-1) && xpos <= hotspot + (IOWIDTH+1)) + { + if (doit) + { + int issignal = obj_issignaloutlet(ob, closest); + x->gl_editor->e_onmotion = MA_CONNECT; + x->gl_editor->e_xwas = xpos; + x->gl_editor->e_ywas = ypos; + sys_vgui( + ".x%x.c create line %d %d %d %d -width %d -tags x\n", + x, xpos, ypos, xpos, ypos, + (issignal ? 2 : 1)); + } + else canvas_setcursor(x, CURSOR_EDITMODE_CONNECT); + } + else if (doit) + goto nooutletafterall; + } + /* not in an outlet; select and move */ + else if (doit) + { + t_rtext *rt; + /* check if the box is being text edited */ + nooutletafterall: + if (ob && (rt = x->gl_editor->e_textedfor) && + rt == glist_findrtext(x, ob)) + { + rtext_mouse(rt, xpos - x1, ypos - y1, RTEXT_DOWN); + x->gl_editor->e_onmotion = MA_DRAGTEXT; + x->gl_editor->e_xwas = x1; + x->gl_editor->e_ywas = y1; + } + else + { + /* otherwise select and drag to displace */ + if (!glist_isselected(x, y)) + { + glist_noselect(x); + glist_select(x, y); + } + x->gl_editor->e_onmotion = MA_MOVE; + } + } + else canvas_setcursor(x, CURSOR_EDITMODE_NOTHING); + } + return; + } + /* if right click doesn't hit any boxes, call rightclick + routine anyway */ + if (rightclick) + canvas_rightclick(x, xpos, ypos, 0); + + /* if not an editing action, and if we didn't hit a + box, set cursor and return */ + if (runmode || rightclick) + { + canvas_setcursor(x, CURSOR_RUNMODE_NOTHING); + return; + } + /* having failed to find a box, we try lines now. */ + if (!runmode && !altmod && !shiftmod) + { + t_linetraverser t; + t_outconnect *oc; + float fx = xpos, fy = ypos; + t_glist *glist2 = glist_getcanvas(x); + linetraverser_start(&t, glist2); + while (oc = linetraverser_next(&t)) + { + float lx1 = t.tr_lx1, ly1 = t.tr_ly1, + lx2 = t.tr_lx2, ly2 = t.tr_ly2; + float area = (lx2 - lx1) * (fy - ly1) - + (ly2 - ly1) * (fx - lx1); + float dsquare = (lx2-lx1) * (lx2-lx1) + (ly2-ly1) * (ly2-ly1); + if (area * area >= 50 * dsquare) continue; + if ((lx2-lx1) * (fx-lx1) + (ly2-ly1) * (fy-ly1) < 0) continue; + if ((lx2-lx1) * (lx2-fx) + (ly2-ly1) * (ly2-fy) < 0) continue; + if (doit) + { + glist_selectline(glist2, oc, + canvas_getindex(glist2, &t.tr_ob->ob_g), t.tr_outno, + canvas_getindex(glist2, &t.tr_ob2->ob_g), t.tr_inno); + } + canvas_setcursor(x, CURSOR_EDITMODE_DISCONNECT); + return; + } + } + canvas_setcursor(x, CURSOR_EDITMODE_NOTHING); + if (doit) + { + if (!shiftmod) glist_noselect(x); + sys_vgui(".x%x.c create rectangle %d %d %d %d -tags x\n", + x, xpos, ypos, xpos, ypos); + x->gl_editor->e_xwas = xpos; + x->gl_editor->e_ywas = ypos; + x->gl_editor->e_onmotion = MA_REGION; + } +} + +void canvas_mousedown(t_canvas *x, t_floatarg xpos, t_floatarg ypos, + t_floatarg which, t_floatarg mod) +{ + canvas_doclick(x, xpos, ypos, which, mod, 1); +} + +int canvas_isconnected (t_canvas *x, t_text *ob1, int n1, + t_text *ob2, int n2) +{ + t_linetraverser t; + t_outconnect *oc; + linetraverser_start(&t, x); + while (oc = linetraverser_next(&t)) + if (t.tr_ob == ob1 && t.tr_outno == n1 && + t.tr_ob2 == ob2 && t.tr_inno == n2) + return (1); + return (0); +} + +void canvas_doconnect(t_canvas *x, int xpos, int ypos, int which, int doit) +{ + int x11, y11, x12, y12; + t_gobj *y1; + int x21, y21, x22, y22; + t_gobj *y2; + int xwas = x->gl_editor->e_xwas, + ywas = x->gl_editor->e_ywas; + if (doit) sys_vgui(".x%x.c delete x\n", x); + else sys_vgui(".x%x.c coords x %d %d %d %d\n", + x, x->gl_editor->e_xwas, + x->gl_editor->e_ywas, xpos, ypos); + + if ((y1 = canvas_findhitbox(x, xwas, ywas, &x11, &y11, &x12, &y12)) + && (y2 = canvas_findhitbox(x, xpos, ypos, &x21, &y21, &x22, &y22))) + { + t_object *ob1 = pd_checkobject(&y1->g_pd); + t_object *ob2 = pd_checkobject(&y2->g_pd); + int noutlet1, ninlet2; + if (ob1 && ob2 && ob1 != ob2 && + (noutlet1 = obj_noutlets(ob1)) + && (ninlet2 = obj_ninlets(ob2))) + { + int width1 = x12 - x11, closest1, hotspot1; + int width2 = x22 - x21, closest2, hotspot2; + int lx1, lx2, ly1, ly2; + t_outconnect *oc; + + if (noutlet1 > 1) + { + closest1 = ((xwas-x11) * (noutlet1-1) + width1/2)/width1; + hotspot1 = x11 + + (width1 - IOWIDTH) * closest1 / (noutlet1-1); + } + else closest1 = 0, hotspot1 = x11; + + if (ninlet2 > 1) + { + closest2 = ((xpos-x21) * (ninlet2-1) + width2/2)/width2; + hotspot2 = x21 + + (width2 - IOWIDTH) * closest2 / (ninlet2-1); + } + else closest2 = 0, hotspot2 = x21; + + if (closest1 >= noutlet1) + closest1 = noutlet1 - 1; + if (closest2 >= ninlet2) + closest2 = ninlet2 - 1; + + if (canvas_isconnected (x, ob1, closest1, ob2, closest2)) + { + canvas_setcursor(x, CURSOR_EDITMODE_NOTHING); + return; + } + if (obj_issignaloutlet(ob1, closest1) && + !obj_issignalinlet(ob2, closest2)) + { + if (doit) + error("can't connect signal outlet to control inlet"); + canvas_setcursor(x, CURSOR_EDITMODE_NOTHING); + return; + } + if (doit) + { + oc = obj_connect(ob1, closest1, ob2, closest2); + lx1 = x11 + (noutlet1 > 1 ? + ((x12-x11-IOWIDTH) * closest1)/(noutlet1-1) : 0) + + IOMIDDLE; + ly1 = y12; + lx2 = x21 + (ninlet2 > 1 ? + ((x22-x21-IOWIDTH) * closest2)/(ninlet2-1) : 0) + + IOMIDDLE; + ly2 = y21; + sys_vgui(".x%x.c create line %d %d %d %d -width %d -tags l%x\n", + glist_getcanvas(x), + lx1, ly1, lx2, ly2, + (obj_issignaloutlet(ob1, closest1) ? 2 : 1), oc); + canvas_setundo(x, canvas_undo_connect, + canvas_undo_set_connect(x, + canvas_getindex(x, &ob1->ob_g), closest1, + canvas_getindex(x, &ob2->ob_g), closest2), + "connect"); + } + else canvas_setcursor(x, CURSOR_EDITMODE_CONNECT); + return; + } + } + canvas_setcursor(x, CURSOR_EDITMODE_NOTHING); +} + +void canvas_selectinrect(t_canvas *x, int lox, int loy, int hix, int hiy) +{ + t_gobj *y; + for (y = x->gl_list; y; y = y->g_next) + { + int x1, y1, x2, y2; + gobj_getrect(y, x, &x1, &y1, &x2, &y2); + if (hix >= x1 && lox <= x2 && hiy >= y1 && loy <= y2 + && !glist_isselected(x, y)) + glist_select(x, y); + } +} + +static void canvas_doregion(t_canvas *x, int xpos, int ypos, int doit) +{ + if (doit) + { + int lox, loy, hix, hiy; + if (x->gl_editor->e_xwas < xpos) + lox = x->gl_editor->e_xwas, hix = xpos; + else hix = x->gl_editor->e_xwas, lox = xpos; + if (x->gl_editor->e_ywas < ypos) + loy = x->gl_editor->e_ywas, hiy = ypos; + else hiy = x->gl_editor->e_ywas, loy = ypos; + canvas_selectinrect(x, lox, loy, hix, hiy); + sys_vgui(".x%x.c delete x\n", x); + x->gl_editor->e_onmotion = 0; + } + else sys_vgui(".x%x.c coords x %d %d %d %d\n", + x, x->gl_editor->e_xwas, + x->gl_editor->e_ywas, xpos, ypos); +} + +void canvas_mouseup(t_canvas *x, + t_floatarg fxpos, t_floatarg fypos, t_floatarg fwhich) +{ + t_gobj *y; + + int xpos = fxpos, ypos = fypos, which = fwhich; + + if (!x->gl_editor) + { + bug("editor"); + return; + } + +#ifdef SIMULATERIGHTCLICK + canvas_upclicktime = sys_getrealtime(); + canvas_upx = xpos; + canvas_upy = ypos; +#endif + + if (x->gl_editor->e_onmotion == MA_CONNECT) + canvas_doconnect(x, xpos, ypos, which, 1); + else if (x->gl_editor->e_onmotion == MA_REGION) + canvas_doregion(x, xpos, ypos, 1); + else if (x->gl_editor->e_onmotion == MA_MOVE) + { + /* after motion, if there's only one item selected, activate it */ + if (x->gl_editor->e_selection && + !(x->gl_editor->e_selection->sel_next)) + gobj_activate(x->gl_editor->e_selection->sel_what, + x, 1); + } + else if (x->gl_editor->e_onmotion == MA_PASSOUT) + x->gl_editor->e_onmotion = 0; + x->gl_editor->e_onmotion = MA_NONE; + + +#if 1 + /* GG misused the (unused) dbl parameter to tell if mouseup */ + + for (y = x->gl_list; y; y = y->g_next) { + int x1, y1, x2, y2, clickreturned = 0; + + /* check if the object wants to be clicked */ + if (canvas_hitbox(x, y, xpos, ypos, &x1, &y1, &x2, &y2) + && (clickreturned = gobj_click(y, x, xpos, ypos, + 0, 0, 1, 0))) + break; + } +#endif + + +} + + /* displace the selection by (dx, dy) pixels */ +static void canvas_displaceselection(t_canvas *x, int dx, int dy) +{ + t_selection *y; + int resortin = 0, resortout = 0; + if (!canvas_undo_already_set_move) + { + canvas_setundo(x, canvas_undo_move, canvas_undo_set_move(x, 1), + "motion"); + canvas_undo_already_set_move = 1; + } + for (y = x->gl_editor->e_selection; y; y = y->sel_next) + { + t_class *cl = pd_class(&y->sel_what->g_pd); + gobj_displace(y->sel_what, x, dx, dy); + if (cl == vinlet_class) resortin = 1; + else if (cl == voutlet_class) resortout = 1; + } + if (resortin) canvas_resortinlets(x); + if (resortout) canvas_resortoutlets(x); + canvas_dirty(x, 1); +} + + /* this routine is called whenever a key is pressed or released. "x" + may be zero if there's no current canvas. The first argument is true or + fals for down/up; the second one is either a symbolic key name (e.g., + "Right" or an Ascii key number. */ +void canvas_key(t_canvas *x, t_symbol *s, int ac, t_atom *av) +{ + static t_symbol *keynumsym, *keyupsym, *keynamesym; + int keynum, fflag; + t_symbol *gotkeysym; + + int down, shift; + + if (ac < 3) + return; + if (!x->gl_editor) + { + bug("editor"); + return; + } + canvas_undo_already_set_move = 0; + down = (atom_getfloat(av) != 0); /* nonzero if it's a key down */ + shift = (atom_getfloat(av+2) != 0); /* nonzero if shift-ed */ + if (av[1].a_type == A_SYMBOL) + gotkeysym = av[1].a_w.w_symbol; + else if (av[1].a_type == A_FLOAT) + { + char buf[3]; + sprintf(buf, "%c", (int)(av[1].a_w.w_float)); + gotkeysym = gensym(buf); + } + else gotkeysym = gensym("?"); + fflag = (av[0].a_type == A_FLOAT ? av[0].a_w.w_float : 0); + keynum = (av[1].a_type == A_FLOAT ? av[1].a_w.w_float : 0); + if (keynum == '\\' || keynum == '{' || keynum == '}') + { + post("%c: dropped", (int)keynum); + return; + } + if (keynum == '\r') keynum = '\n'; + if (av[1].a_type == A_SYMBOL && + !strcmp(av[1].a_w.w_symbol->s_name, "Return")) + keynum = '\n'; + if (!keynumsym) + { + keynumsym = gensym("#key"); + keyupsym = gensym("#keyup"); + keynamesym = gensym("#keyname"); + } +#ifdef MACOSX + if (keynum == 30) + keynum = 0, gotkeysym = gensym("Up"); + else if (keynum == 31) + keynum = 0, gotkeysym = gensym("Down"); + else if (keynum == 28) + keynum = 0, gotkeysym = gensym("Left"); + else if (keynum == 29) + keynum = 0, gotkeysym = gensym("Right"); +#endif + if (keynumsym->s_thing && down) + pd_float(keynumsym->s_thing, (float)keynum); + if (keyupsym->s_thing && !down) + pd_float(keyupsym->s_thing, (float)keynum); + if (keynamesym->s_thing) + { + t_atom at[2]; + at[0] = av[0]; + SETFLOAT(at, down); + SETSYMBOL(at+1, gotkeysym); + pd_list(keynamesym->s_thing, 0, 2, at); + } + if (!x->gl_editor) /* if that 'invis'ed the window, we'd better stop. */ + return; + if (x && down) + { + /* if an object has "grabbed" keys just send them on */ + if (x->gl_editor->e_grab + && x->gl_editor->e_keyfn && keynum) + (* x->gl_editor->e_keyfn) + (x->gl_editor->e_grab, (float)keynum); + /* if a text editor is open send it on */ + else if (x->gl_editor->e_textedfor) + { + if (!x->gl_editor->e_textdirty) + { + canvas_setundo(x, canvas_undo_cut, + canvas_undo_set_cut(x, UCUT_TEXT), "typing"); + } + rtext_key(x->gl_editor->e_textedfor, + (int)keynum, gotkeysym); + if (x->gl_editor->e_textdirty) + canvas_dirty(x, 1); + } + /* check for backspace or clear */ + else if (keynum == 8 || keynum == 127) + { + if (x->gl_editor->e_selectedline) + canvas_clearline(x); + else if (x->gl_editor->e_selection) + { + canvas_setundo(x, canvas_undo_cut, + canvas_undo_set_cut(x, UCUT_CLEAR), "clear"); + canvas_doclear(x); + } + } + /* check for arrow keys */ + else if (!strcmp(gotkeysym->s_name, "Up")) + canvas_displaceselection(x, 0, shift ? -10 : -1); + else if (!strcmp(gotkeysym->s_name, "Down")) + canvas_displaceselection(x, 0, shift ? 10 : 1); + else if (!strcmp(gotkeysym->s_name, "Left")) + canvas_displaceselection(x, shift ? -10 : -1, 0); + else if (!strcmp(gotkeysym->s_name, "Right")) + canvas_displaceselection(x, shift ? 10 : 1, 0); + } +} + +void canvas_motion(t_canvas *x, t_floatarg xpos, t_floatarg ypos, + t_floatarg fmod) +{ + /* post("motion %d %d", xpos, ypos); */ + int mod = fmod; + if (!x->gl_editor) + { + bug("editor"); + return; + } + glist_setlastxy(x, xpos, ypos); + if (x->gl_editor->e_onmotion == MA_MOVE) + { + canvas_displaceselection(x, + xpos - x->gl_editor->e_xwas, ypos - x->gl_editor->e_ywas); + x->gl_editor->e_xwas = xpos; + x->gl_editor->e_ywas = ypos; + } + else if (x->gl_editor->e_onmotion == MA_REGION) + canvas_doregion(x, xpos, ypos, 0); + else if (x->gl_editor->e_onmotion == MA_CONNECT) + canvas_doconnect(x, xpos, ypos, 0, 0); + else if (x->gl_editor->e_onmotion == MA_PASSOUT) + { + if (!x->gl_editor->e_motionfn) + bug("e_motionfn"); + (*x->gl_editor->e_motionfn)(&x->gl_editor->e_grab->g_pd, + xpos - x->gl_editor->e_xwas, + ypos - x->gl_editor->e_ywas); + x->gl_editor->e_xwas = xpos; + x->gl_editor->e_ywas = ypos; + } + else if (x->gl_editor->e_onmotion == MA_DRAGTEXT) + { + t_rtext *rt = x->gl_editor->e_textedfor; + if (rt) + rtext_mouse(rt, xpos - x->gl_editor->e_xwas, + ypos - x->gl_editor->e_ywas, RTEXT_DRAG); + } + else canvas_doclick(x, xpos, ypos, 0, mod, 0); + + x->gl_editor->e_lastmoved = 1; +} + +void canvas_startmotion(t_canvas *x) +{ + int xval, yval; + if (!x->gl_editor) return; + glist_getnextxy(x, &xval, &yval); + if (xval == 0 && yval == 0) return; + x->gl_editor->e_onmotion = MA_MOVE; + x->gl_editor->e_xwas = xval; + x->gl_editor->e_ywas = yval; +} + +/* ----------------------------- window stuff ----------------------- */ + +void canvas_print(t_canvas *x, t_symbol *s) +{ + if (*s->s_name) sys_vgui(".x%x.c postscript -file %s\n", x, s->s_name); + else sys_vgui(".x%x.c postscript -file x.ps\n", x); +} + +void canvas_menuclose(t_canvas *x, t_floatarg force) +{ + if (x->gl_owner) + canvas_vis(x, 0); + else if ((force != 0) || (!x->gl_dirty)) + pd_free(&x->gl_pd); + else sys_vgui("pdtk_check {This window has been modified. Close anyway?}\ + {.x%x menuclose 1;\n}\n", x); +} + + /* put up a dialog which may call canvas_font back to do the work */ +static void canvas_menufont(t_canvas *x) +{ + char buf[80]; + t_canvas *x2 = canvas_getrootfor(x); + gfxstub_deleteforkey(x2); + sprintf(buf, "pdtk_canvas_dofont %%s %d\n", x2->gl_font); + gfxstub_new(&x2->gl_pd, &x2->gl_pd, buf); +} + +static int canvas_find_index1, canvas_find_index2; +static t_binbuf *canvas_findbuf; +int binbuf_match(t_binbuf *inbuf, t_binbuf *searchbuf); + + /* find an atom or string of atoms */ +static int canvas_dofind(t_canvas *x, int *myindex1p) +{ + t_gobj *y; + int myindex1 = *myindex1p, myindex2; + if (myindex1 >= canvas_find_index1) + { + for (y = x->gl_list, myindex2 = 0; y; + y = y->g_next, myindex2++) + { + t_object *ob = 0; + if (ob = pd_checkobject(&y->g_pd)) + { + if (binbuf_match(ob->ob_binbuf, canvas_findbuf)) + { + if (myindex1 > canvas_find_index1 || + myindex1 == canvas_find_index1 && + myindex2 > canvas_find_index2) + { + canvas_find_index1 = myindex1; + canvas_find_index2 = myindex2; + glist_noselect(x); + canvas_vis(x, 1); + canvas_editmode(x, 1.); + glist_select(x, y); + return (1); + } + } + } + } + } + for (y = x->gl_list, myindex2 = 0; y; y = y->g_next, myindex2++) + { + if (pd_class(&y->g_pd) == canvas_class) + { + (*myindex1p)++; + if (canvas_dofind((t_canvas *)y, myindex1p)) + return (1); + } + } + return (0); +} + +static void canvas_find(t_canvas *x, t_symbol *s, int ac, t_atom *av) +{ + int myindex1 = 0, i; + for (i = 0; i < ac; i++) + { + if (av[i].a_type == A_SYMBOL) + { + if (!strcmp(av[i].a_w.w_symbol->s_name, "_semi_")) + SETSEMI(&av[i]); + else if (!strcmp(av[i].a_w.w_symbol->s_name, "_comma_")) + SETCOMMA(&av[i]); + } + } + if (!canvas_findbuf) + canvas_findbuf = binbuf_new(); + binbuf_clear(canvas_findbuf); + binbuf_add(canvas_findbuf, ac, av); + canvas_find_index1 = 0; + canvas_find_index2 = -1; + canvas_whichfind = x; + if (!canvas_dofind(x, &myindex1)) + { + binbuf_print(canvas_findbuf); + post("... couldn't find"); + } +} + +static void canvas_find_again(t_canvas *x) +{ + int myindex1 = 0; + if (!canvas_findbuf || !canvas_whichfind) + return; + if (!canvas_dofind(canvas_whichfind, &myindex1)) + { + binbuf_print(canvas_findbuf); + post("... couldn't find"); + } +} + +static void canvas_find_parent(t_canvas *x) +{ + if (x->gl_owner) + canvas_vis(glist_getcanvas(x->gl_owner), 1); +} + +static int glist_dofinderror(t_glist *gl, void *error_object) +{ + t_gobj *g; + for (g = gl->gl_list; g; g = g->g_next) + { + if ((void *)g == error_object) + { + /* got it... now show it. */ + glist_noselect(gl); + canvas_vis(glist_getcanvas(gl), 1); + canvas_editmode(glist_getcanvas(gl), 1.); + glist_select(gl, g); + return (1); + } + else if (g->g_pd == canvas_class) + { + if (glist_dofinderror((t_canvas *)g, error_object)) + return (1); + } + } + return (0); +} + +void canvas_finderror(void *error_object) +{ + t_canvas *x; + /* find all root canvases */ + for (x = canvas_list; x; x = x->gl_next) + { + if (glist_dofinderror(x, error_object)) + return; + } + post("... sorry, I couldn't find the source of that error."); +} + +void canvas_stowconnections(t_canvas *x) +{ + t_gobj *selhead = 0, *seltail = 0, *nonhead = 0, *nontail = 0, *y, *y2; + t_linetraverser t; + t_outconnect *oc; + if (!x->gl_editor) return; + /* split list to "selected" and "unselected" parts */ + for (y = x->gl_list; y; y = y2) + { + y2 = y->g_next; + if (glist_isselected(x, y)) + { + if (seltail) + { + seltail->g_next = y; + seltail = y; + y->g_next = 0; + } + else + { + selhead = seltail = y; + seltail->g_next = 0; + } + } + else + { + if (nontail) + { + nontail->g_next = y; + nontail = y; + y->g_next = 0; + } + else + { + nonhead = nontail = y; + nontail->g_next = 0; + } + } + } + /* move the selected part to the end */ + if (!nonhead) x->gl_list = selhead; + else x->gl_list = nonhead, nontail->g_next = selhead; + + /* add connections to binbuf */ + binbuf_clear(x->gl_editor->e_connectbuf); + linetraverser_start(&t, x); + while (oc = linetraverser_next(&t)) + { + int s1 = glist_isselected(x, &t.tr_ob->ob_g); + int s2 = glist_isselected(x, &t.tr_ob2->ob_g); + if (s1 != s2) + binbuf_addv(x->gl_editor->e_connectbuf, "ssiiii;", + gensym("#X"), gensym("connect"), + glist_getindex(x, &t.tr_ob->ob_g), t.tr_outno, + glist_getindex(x, &t.tr_ob2->ob_g), t.tr_inno); + } +} + +void canvas_restoreconnections(t_canvas *x) +{ + pd_bind(&x->gl_pd, gensym("#X")); + binbuf_eval(x->gl_editor->e_connectbuf, 0, 0, 0); + pd_unbind(&x->gl_pd, gensym("#X")); +} + +static t_binbuf *canvas_docopy(t_canvas *x) +{ + t_gobj *y; + t_linetraverser t; + t_outconnect *oc; + t_binbuf *b = binbuf_new(); + for (y = x->gl_list; y; y = y->g_next) + { + if (glist_isselected(x, y)) + gobj_save(y, b); + } + linetraverser_start(&t, x); + while (oc = linetraverser_next(&t)) + { + if (glist_isselected(x, &t.tr_ob->ob_g) + && glist_isselected(x, &t.tr_ob2->ob_g)) + { + binbuf_addv(b, "ssiiii;", gensym("#X"), gensym("connect"), + glist_selectionindex(x, &t.tr_ob->ob_g, 1), t.tr_outno, + glist_selectionindex(x, &t.tr_ob2->ob_g, 1), t.tr_inno); + } + } + return (b); +} + +static void canvas_copy(t_canvas *x) +{ + if (!x->gl_editor || !x->gl_editor->e_selection) + return; + binbuf_free(copy_binbuf); + copy_binbuf = canvas_docopy(x); +} + +static void canvas_clearline(t_canvas *x) +{ + if (x->gl_editor->e_selectedline) + { + canvas_disconnect(x, x->gl_editor->e_selectline_index1, + x->gl_editor->e_selectline_outno, + x->gl_editor->e_selectline_index2, + x->gl_editor->e_selectline_inno); + canvas_setundo(x, canvas_undo_disconnect, + canvas_undo_set_disconnect(x, + x->gl_editor->e_selectline_index1, + x->gl_editor->e_selectline_outno, + x->gl_editor->e_selectline_index2, + x->gl_editor->e_selectline_inno), + "disconnect"); + } +} + +extern t_pd *newest; +static void canvas_doclear(t_canvas *x) +{ + t_gobj *y, *y2; + int dspstate; + + dspstate = canvas_suspend_dsp(); + if (x->gl_editor->e_selectedline) + { + canvas_disconnect(x, x->gl_editor->e_selectline_index1, + x->gl_editor->e_selectline_outno, + x->gl_editor->e_selectline_index2, + x->gl_editor->e_selectline_inno); + canvas_setundo(x, canvas_undo_disconnect, + canvas_undo_set_disconnect(x, + x->gl_editor->e_selectline_index1, + x->gl_editor->e_selectline_outno, + x->gl_editor->e_selectline_index2, + x->gl_editor->e_selectline_inno), + "disconnect"); + } + /* if text is selected, deselecting it might remake the + object. So we deselect it and hunt for a "new" object on + the glist to reselect. */ + if (x->gl_editor->e_textedfor) + { + newest = 0; + glist_noselect(x); + if (newest) + { + for (y = x->gl_list; y; y = y->g_next) + if (&y->g_pd == newest) glist_select(x, y); + } + } + while (1) /* this is pretty wierd... should rewrite it */ + { + for (y = x->gl_list; y; y = y2) + { + y2 = y->g_next; + if (glist_isselected(x, y)) + { + glist_delete(x, y); +#if 0 + if (y2) post("cut 5 %x %x", y2, y2->g_next); + else post("cut 6"); +#endif + goto next; + } + } + goto restore; + next: ; + } +restore: + canvas_resume_dsp(dspstate); + canvas_dirty(x, 1); +} + +static void canvas_cut(t_canvas *x) +{ + if (x->gl_editor && x->gl_editor->e_selectedline) + canvas_clearline(x); + else if (x->gl_editor && x->gl_editor->e_selection) + { + canvas_setundo(x, canvas_undo_cut, + canvas_undo_set_cut(x, UCUT_CUT), "cut"); + canvas_copy(x); + canvas_doclear(x); + } +} + +static int paste_onset; +static t_canvas *paste_canvas; + +static void glist_donewloadbangs(t_glist *x) +{ + if (x->gl_editor) + { + t_selection *sel; + for (sel = x->gl_editor->e_selection; sel; sel = sel->sel_next) + if (pd_class(&sel->sel_what->g_pd) == canvas_class) + canvas_loadbang((t_canvas *)(&sel->sel_what->g_pd)); + } +} + +static void canvas_dopaste(t_canvas *x, t_binbuf *b) +{ + t_gobj *newgobj, *last, *g2; + int dspstate = canvas_suspend_dsp(), nbox, count; + + canvas_editmode(x, 1.); + glist_noselect(x); + for (g2 = x->gl_list, nbox = 0; g2; g2 = g2->g_next) nbox++; + + paste_onset = nbox; + paste_canvas = x; + + pd_bind(&x->gl_pd, gensym("#X")); + binbuf_eval(b, 0, 0, 0); + pd_unbind(&x->gl_pd, gensym("#X")); + for (g2 = x->gl_list, count = 0; g2; g2 = g2->g_next, count++) + if (count >= nbox) + glist_select(x, g2); + paste_canvas = 0; + canvas_resume_dsp(dspstate); + canvas_dirty(x, 1); + glist_donewloadbangs(x); +} + +static void canvas_paste(t_canvas *x) +{ + canvas_setundo(x, canvas_undo_paste, canvas_undo_set_paste(x), "paste"); + canvas_dopaste(x, copy_binbuf); +} + +static void canvas_duplicate(t_canvas *x) +{ + if (x->gl_editor->e_onmotion == MA_NONE) + { + t_selection *y; + canvas_copy(x); + canvas_setundo(x, canvas_undo_paste, canvas_undo_set_paste(x), + "duplicate"); + canvas_dopaste(x, copy_binbuf); + for (y = x->gl_editor->e_selection; y; y = y->sel_next) + gobj_displace(y->sel_what, x, + 10, 10); + canvas_dirty(x, 1); + } +} + +static void canvas_selectall(t_canvas *x) +{ + t_gobj *y; + if (!x->gl_edit) + canvas_editmode(x, 1); + for (y = x->gl_list; y; y = y->g_next) + { + if (!glist_isselected(x, y)) + glist_select(x, y); + } +} + +void canvas_connect(t_canvas *x, t_floatarg fwhoout, t_floatarg foutno, + t_floatarg fwhoin, t_floatarg finno) +{ + int whoout = fwhoout, outno = foutno, whoin = fwhoin, inno = finno; + t_gobj *src = 0, *sink = 0; + t_object *objsrc, *objsink; + t_outconnect *oc; + int nin = whoin, nout = whoout; + if (paste_canvas == x) whoout += paste_onset, whoin += paste_onset; + for (src = x->gl_list; whoout; src = src->g_next, whoout--) + if (!src->g_next) goto bad; /* bug fix thanks to Hannes */ + for (sink = x->gl_list; whoin; sink = sink->g_next, whoin--) + if (!sink->g_next) goto bad; + if (!(objsrc = pd_checkobject(&src->g_pd)) || + !(objsink = pd_checkobject(&sink->g_pd))) + goto bad; + if (!(oc = obj_connect(objsrc, outno, objsink, inno))) goto bad; + if (glist_isvisible(x)) + { + sys_vgui(".x%x.c create line %d %d %d %d -width %d -tags l%x\n", + glist_getcanvas(x), 0, 0, 0, 0, + (obj_issignaloutlet(objsrc, outno) ? 2 : 1),oc); + canvas_fixlinesfor(x, objsrc); + } + return; + +bad: + post("%s %d %d %d %d (%s->%s) connection failed", + x->gl_name->s_name, nout, outno, nin, inno, + (src? class_getname(pd_class(&src->g_pd)) : "???"), + (sink? class_getname(pd_class(&sink->g_pd)) : "???")); +} + +#define XTOLERANCE 4 +#define YTOLERANCE 3 +#define NHIST 15 + + /* LATER might have to speed this up */ +static void canvas_tidy(t_canvas *x) +{ + t_gobj *y, *y2, *y3; + int ax1, ay1, ax2, ay2, bx1, by1, bx2, by2; + int histogram[NHIST], *ip, i, besthist, bestdist; + /* if nobody is selected, this means do it to all boxes; + othewise just the selection */ + int all = (x->gl_editor ? (x->gl_editor->e_selection == 0) : 1); + + canvas_setundo(x, canvas_undo_move, canvas_undo_set_move(x, !all), + "motion"); + + /* tidy horizontally */ + for (y = x->gl_list; y; y = y->g_next) + if (all || glist_isselected(x, y)) + { + gobj_getrect(y, x, &ax1, &ay1, &ax2, &ay2); + + for (y2 = x->gl_list; y2; y2 = y2->g_next) + if (all || glist_isselected(x, y2)) + { + gobj_getrect(y2, x, &bx1, &by1, &bx2, &by2); + if (by1 <= ay1 + YTOLERANCE && by1 >= ay1 - YTOLERANCE && + bx1 < ax1) + goto nothorizhead; + } + + for (y2 = x->gl_list; y2; y2 = y2->g_next) + if (all || glist_isselected(x, y2)) + { + gobj_getrect(y2, x, &bx1, &by1, &bx2, &by2); + if (by1 <= ay1 + YTOLERANCE && by1 >= ay1 - YTOLERANCE + && by1 != ay1) + gobj_displace(y2, x, 0, ay1-by1); + } + nothorizhead: ; + } + /* tidy vertically. First guess the user's favorite vertical spacing */ + for (i = NHIST, ip = histogram; i--; ip++) *ip = 0; + for (y = x->gl_list; y; y = y->g_next) + if (all || glist_isselected(x, y)) + { + gobj_getrect(y, x, &ax1, &ay1, &ax2, &ay2); + for (y2 = x->gl_list; y2; y2 = y2->g_next) + if (all || glist_isselected(x, y2)) + { + gobj_getrect(y2, x, &bx1, &by1, &bx2, &by2); + if (bx1 <= ax1 + XTOLERANCE && bx1 >= ax1 - XTOLERANCE) + { + int distance = by1-ay2; + if (distance >= 0 && distance < NHIST) + histogram[distance]++; + } + } + } + for (i = 1, besthist = 0, bestdist = 4, ip = histogram + 1; + i < (NHIST-1); i++, ip++) + { + int hit = ip[-1] + 2 * ip[0] + ip[1]; + if (hit > besthist) + { + besthist = hit; + bestdist = i; + } + } + post("best vertical distance %d", bestdist); + for (y = x->gl_list; y; y = y->g_next) + if (all || glist_isselected(x, y)) + { + int keep = 1; + gobj_getrect(y, x, &ax1, &ay1, &ax2, &ay2); + for (y2 = x->gl_list; y2; y2 = y2->g_next) + if (all || glist_isselected(x, y2)) + { + gobj_getrect(y2, x, &bx1, &by1, &bx2, &by2); + if (bx1 <= ax1 + XTOLERANCE && bx1 >= ax1 - XTOLERANCE && + ay1 >= by2 - 10 && ay1 < by2 + NHIST) + goto nothead; + } + while (keep) + { + keep = 0; + for (y2 = x->gl_list; y2; y2 = y2->g_next) + if (all || glist_isselected(x, y2)) + { + gobj_getrect(y2, x, &bx1, &by1, &bx2, &by2); + if (bx1 <= ax1 + XTOLERANCE && bx1 >= ax1 - XTOLERANCE && + by1 > ay1 && by1 < ay2 + NHIST) + { + int vmove = ay2 + bestdist - by1; + gobj_displace(y2, x, ax1-bx1, vmove); + ay1 = by1 + vmove; + ay2 = by2 + vmove; + keep = 1; + break; + } + } + } + nothead: ; + } + canvas_dirty(x, 1); +} + +static void canvas_texteditor(t_canvas *x) +{ + t_rtext *foo; + char *buf; + int bufsize; + if (foo = x->gl_editor->e_textedfor) + rtext_gettext(foo, &buf, &bufsize); + else buf = "", bufsize = 0; + sys_vgui("pdtk_pd_texteditor {%.*s}\n", bufsize, buf); + +} + +void glob_key(void *dummy, t_symbol *s, int ac, t_atom *av) +{ + /* canvas_editing can be zero; canvas_key checks for that */ + canvas_key(canvas_editing, s, ac, av); +} + +void canvas_editmode(t_canvas *x, t_floatarg fyesplease) +{ + int yesplease = fyesplease; + if (yesplease && x->gl_edit) + return; + x->gl_edit = !x->gl_edit; + if (x->gl_edit && glist_isvisible(x) && glist_istoplevel(x)) + canvas_setcursor(x, CURSOR_EDITMODE_NOTHING); + else + { + glist_noselect(x); + if (glist_isvisible(x) && glist_istoplevel(x)) + canvas_setcursor(x, CURSOR_RUNMODE_NOTHING); + } + sys_vgui("pdtk_canvas_editval .x%x %d\n", + glist_getcanvas(x), x->gl_edit); +} + + /* called by canvas_font below */ +static void canvas_dofont(t_canvas *x, t_floatarg font, t_floatarg xresize, + t_floatarg yresize) +{ + t_gobj *y; + x->gl_font = font; + if (xresize != 1 || yresize != 1) + { + canvas_setundo(x, canvas_undo_move, canvas_undo_set_move(x, 0), + "motion"); + for (y = x->gl_list; y; y = y->g_next) + { + int x1, x2, y1, y2, nx1, ny1; + gobj_getrect(y, x, &x1, &y1, &x2, &y2); + nx1 = x1 * xresize + 0.5; + ny1 = y1 * yresize + 0.5; + gobj_displace(y, x, nx1-x1, ny1-y1); + } + } + if (glist_isvisible(x)) + glist_redraw(x); + for (y = x->gl_list; y; y = y->g_next) + if (pd_class(&y->g_pd) == canvas_class + && !canvas_isabstraction((t_canvas *)y)) + canvas_dofont((t_canvas *)y, font, xresize, yresize); +} + + /* canvas_menufont calls up a TK dialog which calls this back */ +static void canvas_font(t_canvas *x, t_floatarg font, t_floatarg resize, + t_floatarg whichresize) +{ + float realresize, realresx = 1, realresy = 1; + t_canvas *x2 = canvas_getrootfor(x); + if (!resize) realresize = 1; + else + { + if (resize < 20) resize = 20; + if (resize > 500) resize = 500; + realresize = resize * 0.01; + } + if (whichresize != 3) realresx = realresize; + if (whichresize != 2) realresy = realresize; + canvas_dofont(x2, font, realresx, realresy); + sys_defaultfont = font; +} + +static t_glist *canvas_last_glist; +static int canvas_last_glist_x, canvas_last_glist_y; + +void glist_getnextxy(t_glist *gl, int *xpix, int *ypix) +{ + if (canvas_last_glist == gl) + *xpix = canvas_last_glist_x, *ypix = canvas_last_glist_y; + else *xpix = *ypix = 40; +} + +static void glist_setlastxy(t_glist *gl, int xval, int yval) +{ + canvas_last_glist = gl; + canvas_last_glist_x = xval; + canvas_last_glist_y = yval; +} + + +void g_editor_setup(void) +{ +/* ------------------------ events ---------------------------------- */ + class_addmethod(canvas_class, (t_method)canvas_mousedown, gensym("mouse"), + A_FLOAT, A_FLOAT, A_FLOAT, A_FLOAT, A_NULL); + class_addmethod(canvas_class, (t_method)canvas_mouseup, gensym("mouseup"), + A_FLOAT, A_FLOAT, A_FLOAT, A_NULL); + class_addmethod(canvas_class, (t_method)canvas_key, gensym("key"), + A_GIMME, A_NULL); + class_addmethod(canvas_class, (t_method)canvas_motion, gensym("motion"), + A_FLOAT, A_FLOAT, A_FLOAT, A_NULL); + +/* ------------------------ menu actions ---------------------------- */ + class_addmethod(canvas_class, (t_method)canvas_menuclose, + gensym("menuclose"), A_DEFFLOAT, 0); + class_addmethod(canvas_class, (t_method)canvas_cut, + gensym("cut"), A_NULL); + class_addmethod(canvas_class, (t_method)canvas_copy, + gensym("copy"), A_NULL); + class_addmethod(canvas_class, (t_method)canvas_paste, + gensym("paste"), A_NULL); + class_addmethod(canvas_class, (t_method)canvas_duplicate, + gensym("duplicate"), A_NULL); + class_addmethod(canvas_class, (t_method)canvas_selectall, + gensym("selectall"), A_NULL); + class_addmethod(canvas_class, (t_method)canvas_undo, + gensym("undo"), A_NULL); + class_addmethod(canvas_class, (t_method)canvas_redo, + gensym("redo"), A_NULL); + class_addmethod(canvas_class, (t_method)canvas_tidy, + gensym("tidy"), A_NULL); + class_addmethod(canvas_class, (t_method)canvas_texteditor, + gensym("texteditor"), A_NULL); + class_addmethod(canvas_class, (t_method)canvas_editmode, + gensym("editmode"), A_DEFFLOAT, A_NULL); + class_addmethod(canvas_class, (t_method)canvas_print, + gensym("print"), A_SYMBOL, A_NULL); + class_addmethod(canvas_class, (t_method)canvas_menufont, + gensym("menufont"), A_NULL); + class_addmethod(canvas_class, (t_method)canvas_font, + gensym("font"), A_FLOAT, A_FLOAT, A_FLOAT, A_NULL); + class_addmethod(canvas_class, (t_method)canvas_find, + gensym("find"), A_GIMME, A_NULL); + class_addmethod(canvas_class, (t_method)canvas_find_again, + gensym("findagain"), A_NULL); + class_addmethod(canvas_class, (t_method)canvas_find_parent, + gensym("findparent"), A_NULL); + class_addmethod(canvas_class, (t_method)canvas_done_popup, + gensym("done-popup"), A_FLOAT, A_FLOAT, A_FLOAT, A_NULL); + class_addmethod(canvas_class, (t_method)canvas_donecanvasdialog, + gensym("donecanvasdialog"), A_FLOAT, A_FLOAT, A_FLOAT, A_NULL); + class_addmethod(canvas_class, (t_method)glist_arraydialog, + gensym("arraydialog"), A_SYMBOL, A_FLOAT, A_FLOAT, A_FLOAT, A_NULL); + +/* -------------- connect method used in reading files ------------------ */ + class_addmethod(canvas_class, (t_method)canvas_connect, + gensym("connect"), A_FLOAT, A_FLOAT, A_FLOAT, A_FLOAT, A_NULL); + + class_addmethod(canvas_class, (t_method)canvas_disconnect, + gensym("disconnect"), A_FLOAT, A_FLOAT, A_FLOAT, A_FLOAT, A_NULL); +/* -------------- copy buffer ------------------ */ + copy_binbuf = binbuf_new(); +} +/* Copyright (c) 1997-2001 Miller Puckette and others. +* For information on usage and redistribution, and for a DISCLAIMER OF ALL +* WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ + +#include <stdlib.h> +#include <stdio.h> +#include "m_pd.h" +#include "m_imp.h" +#include "s_stuff.h" +#include "g_canvas.h" +#include <string.h> + +void glist_readfrombinbuf(t_glist *x, t_binbuf *b, char *filename, + int selectem); + +void open_via_helppath(const char *name, const char *dir); +char *class_gethelpdir(t_class *c); + +/* ------------------ forward declarations --------------- */ +static void canvas_doclear(t_canvas *x); +static void glist_setlastxy(t_glist *gl, int xval, int yval); +static void glist_donewloadbangs(t_glist *x); +static t_binbuf *canvas_docopy(t_canvas *x); +static void canvas_dopaste(t_canvas *x, t_binbuf *b); +static void canvas_paste(t_canvas *x); +static void canvas_clearline(t_canvas *x); +static t_binbuf *copy_binbuf; + +/* ---------------- generic widget behavior ------------------------- */ + +void gobj_getrect(t_gobj *x, t_glist *glist, int *x1, int *y1, + int *x2, int *y2) +{ + if (x->g_pd->c_wb && x->g_pd->c_wb->w_getrectfn) + (*x->g_pd->c_wb->w_getrectfn)(x, glist, x1, y1, x2, y2); +} + +void gobj_displace(t_gobj *x, t_glist *glist, int dx, int dy) +{ + if (x->g_pd->c_wb && x->g_pd->c_wb->w_displacefn) + (*x->g_pd->c_wb->w_displacefn)(x, glist, dx, dy); +} + +void gobj_select(t_gobj *x, t_glist *glist, int state) +{ + if (x->g_pd->c_wb && x->g_pd->c_wb->w_selectfn) + (*x->g_pd->c_wb->w_selectfn)(x, glist, state); +} + +void gobj_activate(t_gobj *x, t_glist *glist, int state) +{ + if (x->g_pd->c_wb && x->g_pd->c_wb->w_activatefn) + (*x->g_pd->c_wb->w_activatefn)(x, glist, state); +} + +void gobj_delete(t_gobj *x, t_glist *glist) +{ + if (x->g_pd->c_wb && x->g_pd->c_wb->w_deletefn) + (*x->g_pd->c_wb->w_deletefn)(x, glist); +} + +void gobj_vis(t_gobj *x, struct _glist *glist, int flag) +{ + if (x->g_pd->c_wb && x->g_pd->c_wb->w_visfn) + (*x->g_pd->c_wb->w_visfn)(x, glist, flag); +} + +int gobj_click(t_gobj *x, struct _glist *glist, + int xpix, int ypix, int shift, int alt, int dbl, int doit) +{ + if (x->g_pd->c_wb && x->g_pd->c_wb->w_clickfn) + return ((*x->g_pd->c_wb->w_clickfn)(x, + glist, xpix, ypix, shift, alt, dbl, doit)); + else return (0); +} + +/* ------------------------ managing the selection ----------------- */ + +void glist_selectline(t_glist *x, t_outconnect *oc, int index1, + int outno, int index2, int inno) +{ + if (x->gl_editor) + { + glist_noselect(x); + x->gl_editor->e_selectedline = 1; + x->gl_editor->e_selectline_index1 = index1; + x->gl_editor->e_selectline_outno = outno; + x->gl_editor->e_selectline_index2 = index2; + x->gl_editor->e_selectline_inno = inno; + x->gl_editor->e_selectline_tag = oc; + sys_vgui(".x%x.c itemconfigure l%x -fill blue\n", + x, x->gl_editor->e_selectline_tag); + } +} + +void glist_deselectline(t_glist *x) +{ + if (x->gl_editor) + { + x->gl_editor->e_selectedline = 0; + sys_vgui(".x%x.c itemconfigure l%x -fill black\n", + x, x->gl_editor->e_selectline_tag); + } +} + +int glist_isselected(t_glist *x, t_gobj *y) +{ + if (x->gl_editor) + { + t_selection *sel; + for (sel = x->gl_editor->e_selection; sel; sel = sel->sel_next) + if (sel->sel_what == y) return (1); + } + return (0); +} + + /* call this for unselected objects only */ +void glist_select(t_glist *x, t_gobj *y) +{ + if (x->gl_editor) + { + t_selection *sel = (t_selection *)getbytes(sizeof(*sel)); + if (x->gl_editor->e_selectedline) + glist_deselectline(x); + /* LATER #ifdef out the following check */ + if (glist_isselected(x, y)) bug("glist_select"); + sel->sel_next = x->gl_editor->e_selection; + sel->sel_what = y; + x->gl_editor->e_selection = sel; + gobj_select(y, x, 1); + } +} + + /* call this for selected objects only */ +void glist_deselect(t_glist *x, t_gobj *y) +{ + int fixdsp = 0; + static int reenter = 0; + if (reenter) return; + reenter = 1; + if (x->gl_editor) + { + t_selection *sel, *sel2; + t_rtext *z = 0; + if (!glist_isselected(x, y)) bug("glist_deselect"); + if (x->gl_editor->e_textedfor) + { + t_rtext *fuddy = glist_findrtext(x, (t_text *)y); + if (x->gl_editor->e_textedfor == fuddy) + { + if (x->gl_editor->e_textdirty) + { + z = fuddy; + canvas_stowconnections(glist_getcanvas(x)); + } + gobj_activate(y, x, 0); + } + if (zgetfn(&y->g_pd, gensym("dsp"))) + fixdsp = 1; + } + if ((sel = x->gl_editor->e_selection)->sel_what == y) + { + x->gl_editor->e_selection = x->gl_editor->e_selection->sel_next; + gobj_select(sel->sel_what, x, 0); + freebytes(sel, sizeof(*sel)); + } + else + { + for (sel = x->gl_editor->e_selection; sel2 = sel->sel_next; + sel = sel2) + { + if (sel2->sel_what == y) + { + sel->sel_next = sel2->sel_next; + gobj_select(sel2->sel_what, x, 0); + freebytes(sel2, sizeof(*sel2)); + break; + } + } + } + if (z) + { + char *buf; + int bufsize; + + rtext_gettext(z, &buf, &bufsize); + text_setto((t_text *)y, x, buf, bufsize); + canvas_fixlinesfor(glist_getcanvas(x), (t_text *)y); + x->gl_editor->e_textedfor = 0; + } + if (fixdsp) + canvas_update_dsp(); + } + reenter = 0; +} + +void glist_noselect(t_glist *x) +{ + if (x->gl_editor) + { + while (x->gl_editor->e_selection) + glist_deselect(x, x->gl_editor->e_selection->sel_what); + if (x->gl_editor->e_selectedline) + glist_deselectline(x); + } +} + +void glist_selectall(t_glist *x) +{ + if (x->gl_editor) + { + glist_noselect(x); + if (x->gl_list) + { + t_selection *sel = (t_selection *)getbytes(sizeof(*sel)); + t_gobj *y = x->gl_list; + x->gl_editor->e_selection = sel; + sel->sel_what = y; + gobj_select(y, x, 1); + while (y = y->g_next) + { + t_selection *sel2 = (t_selection *)getbytes(sizeof(*sel2)); + sel->sel_next = sel2; + sel = sel2; + sel->sel_what = y; + gobj_select(y, x, 1); + } + sel->sel_next = 0; + } + } +} + + /* get the index of a gobj in a glist. If y is zero, return the + total number of objects. */ +int glist_getindex(t_glist *x, t_gobj *y) +{ + t_gobj *y2; + int indx; + + for (y2 = x->gl_list, indx = 0; y2 && y2 != y; y2 = y2->g_next) + indx++; + return (indx); +} + + /* get the index of the object, among selected items, if "selected" + is set; otherwise, among unselected ones. If y is zero, just + counts the selected or unselected objects. */ +int glist_selectionindex(t_glist *x, t_gobj *y, int selected) +{ + t_gobj *y2; + int indx; + + for (y2 = x->gl_list, indx = 0; y2 && y2 != y; y2 = y2->g_next) + if (selected == glist_isselected(x, y2)) + indx++; + return (indx); +} + +static t_gobj *glist_nth(t_glist *x, int n) +{ + t_gobj *y; + int indx; + for (y = x->gl_list, indx = 0; y; y = y->g_next, indx++) + if (indx == n) + return (y); + return (0); +} + +/* ------------------- support for undo/redo -------------------------- */ + +static t_undofn canvas_undo_fn; /* current undo function if any */ +static int canvas_undo_whatnext; /* whether we can now UNDO or REDO */ +static void *canvas_undo_buf; /* data private to the undo function */ +static t_canvas *canvas_undo_canvas; /* which canvas we can undo on */ +static const char *canvas_undo_name; + +void canvas_setundo(t_canvas *x, t_undofn undofn, void *buf, + const char *name) +{ + int hadone = 0; + /* blow away the old undo information. In one special case the + old undo info is re-used; if so we shouldn't free it here. */ + if (canvas_undo_fn && canvas_undo_buf && (buf != canvas_undo_buf)) + { + (*canvas_undo_fn)(canvas_undo_canvas, canvas_undo_buf, UNDO_FREE); + hadone = 1; + } + canvas_undo_canvas = x; + canvas_undo_fn = undofn; + canvas_undo_buf = buf; + canvas_undo_whatnext = UNDO_UNDO; + canvas_undo_name = name; + if (x && glist_isvisible(x) && glist_istoplevel(x)) + /* enable undo in menu */ + sys_vgui("pdtk_undomenu .x%x %s no\n", x, name); + else if (hadone) + sys_vgui("pdtk_undomenu nobody no no\n"); +} + + /* clear undo if it happens to be for the canvas x. + (but if x is 0, clear it regardless of who owns it.) */ +void canvas_noundo(t_canvas *x) +{ + if (!x || (x == canvas_undo_canvas)) + canvas_setundo(0, 0, 0, "foo"); +} + +static void canvas_undo(t_canvas *x) +{ + if (x != canvas_undo_canvas) + bug("canvas_undo 1"); + else if (canvas_undo_whatnext != UNDO_UNDO) + bug("canvas_undo 2"); + else + { + /* post("undo"); */ + (*canvas_undo_fn)(canvas_undo_canvas, canvas_undo_buf, UNDO_UNDO); + /* enable redo in menu */ + if (glist_isvisible(x) && glist_istoplevel(x)) + sys_vgui("pdtk_undomenu .x%x no %s\n", x, canvas_undo_name); + canvas_undo_whatnext = UNDO_REDO; + } +} + +static void canvas_redo(t_canvas *x) +{ + if (x != canvas_undo_canvas) + bug("canvas_undo 1"); + else if (canvas_undo_whatnext != UNDO_REDO) + bug("canvas_undo 2"); + else + { + /* post("redo"); */ + (*canvas_undo_fn)(canvas_undo_canvas, canvas_undo_buf, UNDO_REDO); + /* enable undo in menu */ + if (glist_isvisible(x) && glist_istoplevel(x)) + sys_vgui("pdtk_undomenu .x%x %s no\n", x, canvas_undo_name); + canvas_undo_whatnext = UNDO_UNDO; + } +} + +/* ------- specific undo methods: 1. connect and disconnect -------- */ + +typedef struct _undo_connect +{ + int u_index1; + int u_outletno; + int u_index2; + int u_inletno; +} t_undo_connect; + +static void *canvas_undo_set_disconnect(t_canvas *x, + int index1, int outno, int index2, int inno) +{ + t_undo_connect *buf = (t_undo_connect *)getbytes(sizeof(*buf)); + buf->u_index1 = index1; + buf->u_outletno = outno; + buf->u_index2 = index2; + buf->u_inletno = inno; + return (buf); +} + +void canvas_disconnect(t_canvas *x, + float index1, float outno, float index2, float inno) +{ + t_linetraverser t; + t_outconnect *oc; + linetraverser_start(&t, x); + while (oc = linetraverser_next(&t)) + { + int srcno = canvas_getindex(x, &t.tr_ob->ob_g); + int sinkno = canvas_getindex(x, &t.tr_ob2->ob_g); + if (srcno == index1 && t.tr_outno == outno && + sinkno == index2 && t.tr_inno == inno) + { + sys_vgui(".x%x.c delete l%x\n", x, oc); + obj_disconnect(t.tr_ob, t.tr_outno, t.tr_ob2, t.tr_inno); + break; + } + } +} + +static void canvas_undo_disconnect(t_canvas *x, void *z, int action) +{ + t_undo_connect *buf = z; + if (action == UNDO_UNDO) + { + canvas_connect(x, buf->u_index1, buf->u_outletno, + buf->u_index2, buf->u_inletno); + } + else if (action == UNDO_REDO) + { + canvas_disconnect(x, buf->u_index1, buf->u_outletno, + buf->u_index2, buf->u_inletno); + } + else if (action == UNDO_FREE) + t_freebytes(buf, sizeof(*buf)); +} + + /* connect just calls disconnect actions backward... */ +static void *canvas_undo_set_connect(t_canvas *x, + int index1, int outno, int index2, int inno) +{ + return (canvas_undo_set_disconnect(x, index1, outno, index2, inno)); +} + +static void canvas_undo_connect(t_canvas *x, void *z, int action) +{ + int myaction; + if (action == UNDO_UNDO) + myaction = UNDO_REDO; + else if (action == UNDO_REDO) + myaction = UNDO_UNDO; + else myaction = action; + canvas_undo_disconnect(x, z, myaction); +} + +/* ---------- ... 2. cut, clear, and typing into objects: -------- */ + +#define UCUT_CUT 1 /* operation was a cut */ +#define UCUT_CLEAR 2 /* .. a clear */ +#define UCUT_TEXT 3 /* text typed into a box */ + +typedef struct _undo_cut +{ + t_binbuf *u_objectbuf; /* the object cleared or typed into */ + t_binbuf *u_reconnectbuf; /* connections into and out of object */ + t_binbuf *u_redotextbuf; /* buffer to paste back for redo if TEXT */ + int u_mode; /* from flags above */ +} t_undo_cut; + +static void *canvas_undo_set_cut(t_canvas *x, int mode) +{ + t_undo_cut *buf; + t_gobj *y; + t_linetraverser t; + t_outconnect *oc; + int nnotsel= glist_selectionindex(x, 0, 0); + buf = (t_undo_cut *)getbytes(sizeof(*buf)); + buf->u_mode = mode; + buf->u_redotextbuf = 0; + + /* store connections into/out of the selection */ + buf->u_reconnectbuf = binbuf_new(); + linetraverser_start(&t, x); + while (oc = linetraverser_next(&t)) + { + int issel1 = glist_isselected(x, &t.tr_ob->ob_g); + int issel2 = glist_isselected(x, &t.tr_ob2->ob_g); + if (issel1 != issel2) + { + binbuf_addv(buf->u_reconnectbuf, "ssiiii;", + gensym("#X"), gensym("connect"), + (issel1 ? nnotsel : 0) + + glist_selectionindex(x, &t.tr_ob->ob_g, issel1), + t.tr_outno, + (issel2 ? nnotsel : 0) + + glist_selectionindex(x, &t.tr_ob2->ob_g, issel2), + t.tr_inno); + } + } + if (mode == UCUT_TEXT) + { + buf->u_objectbuf = canvas_docopy(x); + } + else if (mode == UCUT_CUT) + { + buf->u_objectbuf = 0; + } + else if (mode == UCUT_CLEAR) + { + buf->u_objectbuf = canvas_docopy(x); + } + return (buf); +} + +static void canvas_undo_cut(t_canvas *x, void *z, int action) +{ + t_undo_cut *buf = z; + int mode = buf->u_mode; + if (action == UNDO_UNDO) + { + if (mode == UCUT_CUT) + canvas_dopaste(x, copy_binbuf); + else if (mode == UCUT_CLEAR) + canvas_dopaste(x, buf->u_objectbuf); + else if (mode == UCUT_TEXT) + { + t_gobj *y1, *y2; + glist_noselect(x); + for (y1 = x->gl_list; y2 = y1->g_next; y1 = y2) + ; + if (y1) + { + if (!buf->u_redotextbuf) + { + glist_noselect(x); + glist_select(x, y1); + buf->u_redotextbuf = canvas_docopy(x); + glist_noselect(x); + } + glist_delete(x, y1); + } + canvas_dopaste(x, buf->u_objectbuf); + } + pd_bind(&x->gl_pd, gensym("#X")); + binbuf_eval(buf->u_reconnectbuf, 0, 0, 0); + pd_unbind(&x->gl_pd, gensym("#X")); + } + else if (action == UNDO_REDO) + { + if (mode == UCUT_CUT || mode == UCUT_CLEAR) + canvas_doclear(x); + else if (mode == UCUT_TEXT) + { + t_gobj *y1, *y2; + for (y1 = x->gl_list; y2 = y1->g_next; y1 = y2) + ; + if (y1) + glist_delete(x, y1); + canvas_dopaste(x, buf->u_redotextbuf); + pd_bind(&x->gl_pd, gensym("#X")); + binbuf_eval(buf->u_reconnectbuf, 0, 0, 0); + pd_unbind(&x->gl_pd, gensym("#X")); + } + } + else if (action == UNDO_FREE) + { + if (buf->u_objectbuf) + binbuf_free(buf->u_objectbuf); + if (buf->u_reconnectbuf) + binbuf_free(buf->u_reconnectbuf); + if (buf->u_redotextbuf) + binbuf_free(buf->u_redotextbuf); + t_freebytes(buf, sizeof(*buf)); + } +} + +/* --------- 3. motion, including "tidy up" and stretching ----------- */ + +typedef struct _undo_move_elem +{ + int e_index; + int e_xpix; + int e_ypix; +} t_undo_move_elem; + +typedef struct _undo_move +{ + t_undo_move_elem *u_vec; + int u_n; +} t_undo_move; + +static int canvas_undo_already_set_move; + +static void *canvas_undo_set_move(t_canvas *x, int selected) +{ + int x1, y1, x2, y2, i, indx; + t_gobj *y; + t_undo_move *buf = (t_undo_move *)getbytes(sizeof(*buf)); + buf->u_n = selected ? glist_selectionindex(x, 0, 1) : glist_getindex(x, 0); + buf->u_vec = (t_undo_move_elem *)getbytes(sizeof(*buf->u_vec) * + (selected ? glist_selectionindex(x, 0, 1) : glist_getindex(x, 0))); + if (selected) + { + for (y = x->gl_list, i = indx = 0; y; y = y->g_next, indx++) + if (glist_isselected(x, y)) + { + gobj_getrect(y, x, &x1, &y1, &x2, &y2); + buf->u_vec[i].e_index = indx; + buf->u_vec[i].e_xpix = x1; + buf->u_vec[i].e_ypix = y1; + i++; + } + } + else + { + for (y = x->gl_list, indx = 0; y; y = y->g_next, indx++) + { + gobj_getrect(y, x, &x1, &y1, &x2, &y2); + buf->u_vec[indx].e_index = indx; + buf->u_vec[indx].e_xpix = x1; + buf->u_vec[indx].e_ypix = y1; + } + } + canvas_undo_already_set_move = 1; + return (buf); +} + +static void canvas_undo_move(t_canvas *x, void *z, int action) +{ + t_undo_move *buf = z; + if (action == UNDO_UNDO || action == UNDO_REDO) + { + int i; + for (i = 0; i < buf->u_n; i++) + { + int x1, y1, x2, y2, newx, newy; + t_gobj *y; + newx = buf->u_vec[i].e_xpix; + newy = buf->u_vec[i].e_ypix; + y = glist_nth(x, buf->u_vec[i].e_index); + if (y) + { + gobj_getrect(y, x, &x1, &y1, &x2, &y2); + gobj_displace(y, x, newx-x1, newy - y1); + buf->u_vec[i].e_xpix = x1; + buf->u_vec[i].e_ypix = y1; + } + } + } + else if (action == UNDO_FREE) + { + t_freebytes(buf->u_vec, buf->u_n * sizeof(*buf->u_vec)); + t_freebytes(buf, sizeof(*buf)); + } +} + +/* --------- 4. paste (also duplicate) ----------- */ + +typedef struct _undo_paste +{ + int u_index; /* index of first object pasted */ +} t_undo_paste; + +static void *canvas_undo_set_paste(t_canvas *x) +{ + t_undo_paste *buf = (t_undo_paste *)getbytes(sizeof(*buf)); + buf->u_index = glist_getindex(x, 0); + return (buf); +} + +static void canvas_undo_paste(t_canvas *x, void *z, int action) +{ + t_undo_paste *buf = z; + if (action == UNDO_UNDO) + { + t_gobj *y; + glist_noselect(x); + for (y = glist_nth(x, buf->u_index); y; y = y->g_next) + glist_select(x, y); + canvas_doclear(x); + } + else if (action == UNDO_REDO) + { + t_selection *sel; + canvas_dopaste(x, copy_binbuf); + /* if it was "duplicate" have to re-enact the displacement. */ + if (canvas_undo_name && canvas_undo_name[0] == 'd') + for (sel = x->gl_editor->e_selection; sel; sel = sel->sel_next) + gobj_displace(sel->sel_what, x, 10, 10); + } +else if (action == UNDO_FREE) + t_freebytes(buf, sizeof(*buf)); +} + + /* recursively check for abstractions to reload as result of a save. + Don't reload the one we just saved ("except") though. */ + /* LATER try to do the same trick for externs. */ +static void glist_doreload(t_glist *gl, t_symbol *name, t_symbol *dir, + t_gobj *except) +{ + t_gobj *g; + int i, nobj = glist_getindex(gl, 0); /* number of objects */ + for (g = gl->gl_list, i = 0; g && i < nobj; i++) + { + if (g != except && pd_class(&g->g_pd) == canvas_class && + canvas_isabstraction((t_canvas *)g) && + ((t_canvas *)g)->gl_name == name && + canvas_getdir((t_canvas *)g) == dir) + { + /* we're going to remake the object, so "g" will go stale. + Get its index here, and afterward restore g. Also, the + replacement will be at teh end of the list, so we don't + do g = g->g_next in this case. */ + int j = glist_getindex(gl, g); + if (!gl->gl_havewindow) + canvas_vis(glist_getcanvas(gl), 1); + glist_noselect(gl); + glist_select(gl, g); + canvas_setundo(gl, canvas_undo_cut, + canvas_undo_set_cut(gl, UCUT_CLEAR), "clear"); + canvas_doclear(gl); + canvas_undo(gl); + glist_noselect(gl); + g = glist_nth(gl, j); + } + else + { + if (g != except && pd_class(&g->g_pd) == canvas_class) + glist_doreload((t_canvas *)g, name, dir, except); + g = g->g_next; + } + } +} + + /* call canvas_doreload on everyone */ +void canvas_reload(t_symbol *name, t_symbol *dir, t_gobj *except) +{ + t_canvas *x; + /* find all root canvases */ + for (x = canvas_list; x; x = x->gl_next) + glist_doreload(x, name, dir, except); +} + +/* ------------------------ event handling ------------------------ */ + +#define CURSOR_RUNMODE_NOTHING 0 +#define CURSOR_RUNMODE_CLICKME 1 +#define CURSOR_RUNMODE_THICKEN 2 +#define CURSOR_RUNMODE_ADDPOINT 3 +#define CURSOR_EDITMODE_NOTHING 4 +#define CURSOR_EDITMODE_CONNECT 5 +#define CURSOR_EDITMODE_DISCONNECT 6 + +static char *cursorlist[] = { +#ifdef MSW + "right_ptr", /* CURSOR_RUNMODE_NOTHING */ +#else + "left_ptr", /* CURSOR_RUNMODE_NOTHING */ +#endif + "arrow", /* CURSOR_RUNMODE_CLICKME */ + "sb_v_double_arrow", /* CURSOR_RUNMODE_THICKEN */ + "plus", /* CURSOR_RUNMODE_ADDPOINT */ + "hand2", /* CURSOR_EDITMODE_NOTHING */ + "circle", /* CURSOR_EDITMODE_CONNECT */ + "X_cursor" /* CURSOR_EDITMODE_DISCONNECT */ +}; + +void canvas_setcursor(t_canvas *x, unsigned int cursornum) +{ + static t_canvas *xwas; + static unsigned int cursorwas; + if (cursornum >= sizeof(cursorlist)/sizeof *cursorlist) + { + bug("canvas_setcursor"); + return; + } + if (xwas != x || cursorwas != cursornum) + { + sys_vgui(".x%x configure -cursor %s\n", x, cursorlist[cursornum]); + xwas = x; + cursorwas = cursornum; + } +} + + /* check if a point lies in a gobj. */ +int canvas_hitbox(t_canvas *x, t_gobj *y, int xpos, int ypos, + int *x1p, int *y1p, int *x2p, int *y2p) +{ + int x1, y1, x2, y2; + t_text *ob; + if ((ob = pd_checkobject(&y->g_pd)) && + !text_shouldvis(ob, x)) + return (0); + gobj_getrect(y, x, &x1, &y1, &x2, &y2); + if (xpos >= x1 && xpos <= x2 && ypos >= y1 && ypos <= y2) + { + *x1p = x1; + *y1p = y1; + *x2p = x2; + *y2p = y2; + return (1); + } + else return (0); +} + + /* find the last gobj, if any, containing the point. */ +static t_gobj *canvas_findhitbox(t_canvas *x, int xpos, int ypos, + int *x1p, int *y1p, int *x2p, int *y2p) +{ + t_gobj *y, *rval = 0; + for (y = x->gl_list; y; y = y->g_next) + { + if (canvas_hitbox(x, y, xpos, ypos, x1p, y1p, x2p, y2p)) + rval = y; + } + return (rval); +} + + /* right-clicking on a canvas object pops up a menu. */ +static void canvas_rightclick(t_canvas *x, int xpos, int ypos, t_gobj *y) +{ + int canprop, canopen; + canprop = (!y || (y && class_getpropertiesfn(pd_class(&y->g_pd)))); + canopen = (y && zgetfn(&y->g_pd, gensym("menu-open"))); + sys_vgui("pdtk_canvas_popup .x%x %d %d %d %d\n", + x, xpos, ypos, canprop, canopen); +} + + /* tell GUI to create a properties dialog on the canvas. We tell + the user the negative of the "pixel" y scale to make it appear to grow + naturally upward, whereas pixels grow downward. */ +static void canvas_properties(t_glist *x) +{ + char graphbuf[200]; + sprintf(graphbuf, "pdtk_canvas_dialog %%s %g %g %g %g \n", + glist_dpixtodx(x, 1), -glist_dpixtody(x, 1), + (float)glist_isgraph(x), (float)x->gl_stretch); + gfxstub_new(&x->gl_pd, x, graphbuf); +} + + +void canvas_setgraph(t_glist *x, int flag) +{ + if (!flag && glist_isgraph(x)) + { + if (x->gl_owner && !x->gl_loading && glist_isvisible(x->gl_owner)) + gobj_vis(&x->gl_gobj, x->gl_owner, 0); + x->gl_isgraph = 0; + if (x->gl_owner && !x->gl_loading && glist_isvisible(x->gl_owner)) + { + gobj_vis(&x->gl_gobj, x->gl_owner, 1); + canvas_fixlinesfor(x->gl_owner, &x->gl_obj); + } + } + else if (flag && !glist_isgraph(x)) + { + if (x->gl_pixwidth <= 0) + x->gl_pixwidth = GLIST_DEFGRAPHWIDTH; + + if (x->gl_pixheight <= 0) + x->gl_pixheight = GLIST_DEFGRAPHHEIGHT; + + if (x->gl_owner && !x->gl_loading && glist_isvisible(x->gl_owner)) + gobj_vis(&x->gl_gobj, x->gl_owner, 0); + x->gl_isgraph = 1; + /* if (x->gl_owner && glist_isvisible(x->gl_owner)) + canvas_vis(x, 1); */ + if (x->gl_loading && x->gl_owner && glist_isvisible(x->gl_owner)) + canvas_create_editor(x, 1); + if (x->gl_owner && !x->gl_loading && glist_isvisible(x->gl_owner)) + { + gobj_vis(&x->gl_gobj, x->gl_owner, 1); + canvas_fixlinesfor(x->gl_owner, &x->gl_obj); + } + } +} + + /* called from the gui when "OK" is selected on the canvas properties + dialog. Again we negate "y" scale. */ +static void canvas_donecanvasdialog(t_glist *x, t_floatarg xperpix, + t_floatarg yperpix, t_floatarg fgraphme) +{ + int graphme = (fgraphme != 0), redraw = 0; + yperpix = -yperpix; + if (xperpix == 0) + xperpix = 1; + if (yperpix == 0) + yperpix = 1; + canvas_setgraph(x, graphme); + if (!x->gl_isgraph && (xperpix != glist_dpixtodx(x, 1))) + { + if (xperpix > 0) + { + x->gl_x1 = 0; + x->gl_x2 = xperpix; + } + else + { + x->gl_x1 = -xperpix * (x->gl_screenx2 - x->gl_screenx1); + x->gl_x2 = x->gl_x1 + xperpix; + } + redraw = 1; + } + if (!x->gl_isgraph && (yperpix != glist_dpixtody(x, 1))) + { + if (yperpix > 0) + { + x->gl_y1 = 0; + x->gl_y2 = yperpix; + } + else + { + x->gl_y1 = -yperpix * (x->gl_screeny2 - x->gl_screeny1); + x->gl_y2 = x->gl_y1 + yperpix; + } + redraw = 1; + } + if (redraw) + canvas_redraw(x); +} + + /* called from the gui when a popup menu comes back with "properties," + "open," or "help." */ +static void canvas_done_popup(t_canvas *x, float which, float xpos, float ypos) +{ + char pathbuf[MAXPDSTRING], namebuf[MAXPDSTRING]; + t_gobj *y; + for (y = x->gl_list; y; y = y->g_next) + { + int x1, y1, x2, y2; + if (canvas_hitbox(x, y, xpos, ypos, &x1, &y1, &x2, &y2)) + { + if (which == 0) /* properties */ + { + if (!class_getpropertiesfn(pd_class(&y->g_pd))) + continue; + (*class_getpropertiesfn(pd_class(&y->g_pd)))(y, x); + return; + } + else if (which == 1) /* open */ + { + if (!zgetfn(&y->g_pd, gensym("menu-open"))) + continue; + vmess(&y->g_pd, gensym("menu-open"), ""); + return; + } + else /* help */ + { + char *dir; + if (pd_class(&y->g_pd) == canvas_class && + canvas_isabstraction((t_canvas *)y)) + { + t_object *ob = (t_object *)y; + int ac = binbuf_getnatom(ob->te_binbuf); + t_atom *av = binbuf_getvec(ob->te_binbuf); + if (ac < 1) + return; + atom_string(av, namebuf, MAXPDSTRING); + dir = canvas_getdir((t_canvas *)y)->s_name; + } + else + { + strcpy(namebuf, class_gethelpname(pd_class(&y->g_pd))); + dir = class_gethelpdir(pd_class(&y->g_pd)); + } + if (strcmp(namebuf + strlen(namebuf) - 3, ".pd")) + strcat(namebuf, ".pd"); + open_via_helppath(namebuf, dir); + return; + } + } + } + if (which == 0) + canvas_properties(x); + else if (which == 2) + { + strcpy(pathbuf, sys_libdir->s_name); + strcat(pathbuf, "/doc/5.reference/0.INTRO.txt"); + sys_vgui("menu_opentext %s\n", pathbuf); + } +} + +#define NOMOD 0 +#define SHIFTMOD 1 +#define CTRLMOD 2 +#define ALTMOD 4 +#define RIGHTCLICK 8 + +/* on one-button-mouse machines, you can use double click to + mean right click (which gets the popup menu.) Do this for Mac. */ +#ifdef MACOSX +#define SIMULATERIGHTCLICK +#endif + +#ifdef SIMULATERIGHTCLICK +static double canvas_upclicktime; +static int canvas_upx, canvas_upy; +#define DCLICKINTERVAL 0.25 +#endif + + /* mouse click */ +void canvas_doclick(t_canvas *x, int xpos, int ypos, int which, + int mod, int doit) +{ + t_gobj *y; + int shiftmod, runmode, altmod, rightclick; + int x1, y1, x2, y2, clickreturned = 0; + + if (!x->gl_editor) + { + bug("editor"); + return; + } + + shiftmod = (mod & SHIFTMOD); + runmode = ((mod & CTRLMOD) || (!x->gl_edit)); + altmod = (mod & ALTMOD); + rightclick = (mod & RIGHTCLICK); + + canvas_undo_already_set_move = 0; + + /* if keyboard was grabbed, notify grabber and cancel the grab */ + if (doit && x->gl_editor->e_grab && x->gl_editor->e_keyfn) + { + (* x->gl_editor->e_keyfn) (x->gl_editor->e_grab, 0); + glist_grab(x, 0, 0, 0, 0, 0); + } + +#ifdef SIMULATERIGHTCLICK + if (doit && !runmode && xpos == canvas_upx && ypos == canvas_upy && + sys_getrealtime() - canvas_upclicktime < DCLICKINTERVAL) + rightclick = 1; +#endif + + x->gl_editor->e_lastmoved = 0; + if (doit) + { + x->gl_editor->e_grab = 0; + x->gl_editor->e_onmotion = MA_NONE; + } + /* post("click %d %d %d %d", xpos, ypos, which, mod); */ + + if (x->gl_editor->e_onmotion != MA_NONE) + return; + + x->gl_editor->e_xwas = xpos; + x->gl_editor->e_ywas = ypos; + + if (runmode && !rightclick) + { + for (y = x->gl_list; y; y = y->g_next) + { + /* check if the object wants to be clicked */ + if (canvas_hitbox(x, y, xpos, ypos, &x1, &y1, &x2, &y2) + && (clickreturned = gobj_click(y, x, xpos, ypos, + shiftmod, altmod, 0, doit))) + break; + } + if (!doit) + { + if (y) + canvas_setcursor(x, clickreturned); + else canvas_setcursor(x, CURSOR_RUNMODE_NOTHING); + } + return; + } + /* if not a runmode left click, fall here. */ + if (y = canvas_findhitbox(x, xpos, ypos, &x1, &y1, &x2, &y2)) + { + t_object *ob = pd_checkobject(&y->g_pd); + /* check you're in the rectangle */ + ob = pd_checkobject(&y->g_pd); + if (rightclick) + canvas_rightclick(x, xpos, ypos, y); + else if (shiftmod) + { + if (doit) + { + t_rtext *rt; + if (ob && (rt = x->gl_editor->e_textedfor) && + rt == glist_findrtext(x, ob)) + { + rtext_mouse(rt, xpos - x1, ypos - y1, RTEXT_SHIFT); + x->gl_editor->e_onmotion = MA_DRAGTEXT; + x->gl_editor->e_xwas = x1; + x->gl_editor->e_ywas = y1; + } + else + { + if (glist_isselected(x, y)) + glist_deselect(x, y); + else glist_select(x, y); + } + } + } + else + { + /* look for an outlet */ + int noutlet; + if (ob && (noutlet = obj_noutlets(ob)) && ypos >= y2-4) + { + int width = x2 - x1; + int nout1 = (noutlet > 1 ? noutlet - 1 : 1); + int closest = ((xpos-x1) * (nout1) + width/2)/width; + int hotspot = x1 + + (width - IOWIDTH) * closest / (nout1); + if (closest < noutlet && + xpos >= (hotspot-1) && xpos <= hotspot + (IOWIDTH+1)) + { + if (doit) + { + int issignal = obj_issignaloutlet(ob, closest); + x->gl_editor->e_onmotion = MA_CONNECT; + x->gl_editor->e_xwas = xpos; + x->gl_editor->e_ywas = ypos; + sys_vgui( + ".x%x.c create line %d %d %d %d -width %d -tags x\n", + x, xpos, ypos, xpos, ypos, + (issignal ? 2 : 1)); + } + else canvas_setcursor(x, CURSOR_EDITMODE_CONNECT); + } + else if (doit) + goto nooutletafterall; + } + /* not in an outlet; select and move */ + else if (doit) + { + t_rtext *rt; + /* check if the box is being text edited */ + nooutletafterall: + if (ob && (rt = x->gl_editor->e_textedfor) && + rt == glist_findrtext(x, ob)) + { + rtext_mouse(rt, xpos - x1, ypos - y1, RTEXT_DOWN); + x->gl_editor->e_onmotion = MA_DRAGTEXT; + x->gl_editor->e_xwas = x1; + x->gl_editor->e_ywas = y1; + } + else + { + /* otherwise select and drag to displace */ + if (!glist_isselected(x, y)) + { + glist_noselect(x); + glist_select(x, y); + } + x->gl_editor->e_onmotion = MA_MOVE; + } + } + else canvas_setcursor(x, CURSOR_EDITMODE_NOTHING); + } + return; + } + /* if right click doesn't hit any boxes, call rightclick + routine anyway */ + if (rightclick) + canvas_rightclick(x, xpos, ypos, 0); + + /* if not an editing action, and if we didn't hit a + box, set cursor and return */ + if (runmode || rightclick) + { + canvas_setcursor(x, CURSOR_RUNMODE_NOTHING); + return; + } + /* having failed to find a box, we try lines now. */ + if (!runmode && !altmod && !shiftmod) + { + t_linetraverser t; + t_outconnect *oc; + float fx = xpos, fy = ypos; + t_glist *glist2 = glist_getcanvas(x); + linetraverser_start(&t, glist2); + while (oc = linetraverser_next(&t)) + { + float lx1 = t.tr_lx1, ly1 = t.tr_ly1, + lx2 = t.tr_lx2, ly2 = t.tr_ly2; + float area = (lx2 - lx1) * (fy - ly1) - + (ly2 - ly1) * (fx - lx1); + float dsquare = (lx2-lx1) * (lx2-lx1) + (ly2-ly1) * (ly2-ly1); + if (area * area >= 50 * dsquare) continue; + if ((lx2-lx1) * (fx-lx1) + (ly2-ly1) * (fy-ly1) < 0) continue; + if ((lx2-lx1) * (lx2-fx) + (ly2-ly1) * (ly2-fy) < 0) continue; + if (doit) + { + glist_selectline(glist2, oc, + canvas_getindex(glist2, &t.tr_ob->ob_g), t.tr_outno, + canvas_getindex(glist2, &t.tr_ob2->ob_g), t.tr_inno); + } + canvas_setcursor(x, CURSOR_EDITMODE_DISCONNECT); + return; + } + } + canvas_setcursor(x, CURSOR_EDITMODE_NOTHING); + if (doit) + { + if (!shiftmod) glist_noselect(x); + sys_vgui(".x%x.c create rectangle %d %d %d %d -tags x\n", + x, xpos, ypos, xpos, ypos); + x->gl_editor->e_xwas = xpos; + x->gl_editor->e_ywas = ypos; + x->gl_editor->e_onmotion = MA_REGION; + } +} + +void canvas_mousedown(t_canvas *x, t_floatarg xpos, t_floatarg ypos, + t_floatarg which, t_floatarg mod) +{ + canvas_doclick(x, xpos, ypos, which, mod, 1); +} + +int canvas_isconnected (t_canvas *x, t_text *ob1, int n1, + t_text *ob2, int n2) +{ + t_linetraverser t; + t_outconnect *oc; + linetraverser_start(&t, x); + while (oc = linetraverser_next(&t)) + if (t.tr_ob == ob1 && t.tr_outno == n1 && + t.tr_ob2 == ob2 && t.tr_inno == n2) + return (1); + return (0); +} + +void canvas_doconnect(t_canvas *x, int xpos, int ypos, int which, int doit) +{ + int x11, y11, x12, y12; + t_gobj *y1; + int x21, y21, x22, y22; + t_gobj *y2; + int xwas = x->gl_editor->e_xwas, + ywas = x->gl_editor->e_ywas; + if (doit) sys_vgui(".x%x.c delete x\n", x); + else sys_vgui(".x%x.c coords x %d %d %d %d\n", + x, x->gl_editor->e_xwas, + x->gl_editor->e_ywas, xpos, ypos); + + if ((y1 = canvas_findhitbox(x, xwas, ywas, &x11, &y11, &x12, &y12)) + && (y2 = canvas_findhitbox(x, xpos, ypos, &x21, &y21, &x22, &y22))) + { + t_object *ob1 = pd_checkobject(&y1->g_pd); + t_object *ob2 = pd_checkobject(&y2->g_pd); + int noutlet1, ninlet2; + if (ob1 && ob2 && ob1 != ob2 && + (noutlet1 = obj_noutlets(ob1)) + && (ninlet2 = obj_ninlets(ob2))) + { + int width1 = x12 - x11, closest1, hotspot1; + int width2 = x22 - x21, closest2, hotspot2; + int lx1, lx2, ly1, ly2; + t_outconnect *oc; + + if (noutlet1 > 1) + { + closest1 = ((xwas-x11) * (noutlet1-1) + width1/2)/width1; + hotspot1 = x11 + + (width1 - IOWIDTH) * closest1 / (noutlet1-1); + } + else closest1 = 0, hotspot1 = x11; + + if (ninlet2 > 1) + { + closest2 = ((xpos-x21) * (ninlet2-1) + width2/2)/width2; + hotspot2 = x21 + + (width2 - IOWIDTH) * closest2 / (ninlet2-1); + } + else closest2 = 0, hotspot2 = x21; + + if (closest1 >= noutlet1) + closest1 = noutlet1 - 1; + if (closest2 >= ninlet2) + closest2 = ninlet2 - 1; + + if (canvas_isconnected (x, ob1, closest1, ob2, closest2)) + { + canvas_setcursor(x, CURSOR_EDITMODE_NOTHING); + return; + } + if (obj_issignaloutlet(ob1, closest1) && + !obj_issignalinlet(ob2, closest2)) + { + if (doit) + error("can't connect signal outlet to control inlet"); + canvas_setcursor(x, CURSOR_EDITMODE_NOTHING); + return; + } + if (doit) + { + oc = obj_connect(ob1, closest1, ob2, closest2); + lx1 = x11 + (noutlet1 > 1 ? + ((x12-x11-IOWIDTH) * closest1)/(noutlet1-1) : 0) + + IOMIDDLE; + ly1 = y12; + lx2 = x21 + (ninlet2 > 1 ? + ((x22-x21-IOWIDTH) * closest2)/(ninlet2-1) : 0) + + IOMIDDLE; + ly2 = y21; + sys_vgui(".x%x.c create line %d %d %d %d -width %d -tags l%x\n", + glist_getcanvas(x), + lx1, ly1, lx2, ly2, + (obj_issignaloutlet(ob1, closest1) ? 2 : 1), oc); + canvas_setundo(x, canvas_undo_connect, + canvas_undo_set_connect(x, + canvas_getindex(x, &ob1->ob_g), closest1, + canvas_getindex(x, &ob2->ob_g), closest2), + "connect"); + } + else canvas_setcursor(x, CURSOR_EDITMODE_CONNECT); + return; + } + } + canvas_setcursor(x, CURSOR_EDITMODE_NOTHING); +} + +void canvas_selectinrect(t_canvas *x, int lox, int loy, int hix, int hiy) +{ + t_gobj *y; + for (y = x->gl_list; y; y = y->g_next) + { + int x1, y1, x2, y2; + gobj_getrect(y, x, &x1, &y1, &x2, &y2); + if (hix >= x1 && lox <= x2 && hiy >= y1 && loy <= y2 + && !glist_isselected(x, y)) + glist_select(x, y); + } +} + +static void canvas_doregion(t_canvas *x, int xpos, int ypos, int doit) +{ + if (doit) + { + int lox, loy, hix, hiy; + if (x->gl_editor->e_xwas < xpos) + lox = x->gl_editor->e_xwas, hix = xpos; + else hix = x->gl_editor->e_xwas, lox = xpos; + if (x->gl_editor->e_ywas < ypos) + loy = x->gl_editor->e_ywas, hiy = ypos; + else hiy = x->gl_editor->e_ywas, loy = ypos; + canvas_selectinrect(x, lox, loy, hix, hiy); + sys_vgui(".x%x.c delete x\n", x); + x->gl_editor->e_onmotion = 0; + } + else sys_vgui(".x%x.c coords x %d %d %d %d\n", + x, x->gl_editor->e_xwas, + x->gl_editor->e_ywas, xpos, ypos); +} + +void canvas_mouseup(t_canvas *x, + t_floatarg fxpos, t_floatarg fypos, t_floatarg fwhich) +{ + t_gobj *y; + + int xpos = fxpos, ypos = fypos, which = fwhich; + + if (!x->gl_editor) + { + bug("editor"); + return; + } + +#ifdef SIMULATERIGHTCLICK + canvas_upclicktime = sys_getrealtime(); + canvas_upx = xpos; + canvas_upy = ypos; +#endif + + if (x->gl_editor->e_onmotion == MA_CONNECT) + canvas_doconnect(x, xpos, ypos, which, 1); + else if (x->gl_editor->e_onmotion == MA_REGION) + canvas_doregion(x, xpos, ypos, 1); + else if (x->gl_editor->e_onmotion == MA_MOVE) + { + /* after motion, if there's only one item selected, activate it */ + if (x->gl_editor->e_selection && + !(x->gl_editor->e_selection->sel_next)) + gobj_activate(x->gl_editor->e_selection->sel_what, + x, 1); + } + else if (x->gl_editor->e_onmotion == MA_PASSOUT) + x->gl_editor->e_onmotion = 0; + x->gl_editor->e_onmotion = MA_NONE; + + +#if 1 + /* GG misused the (unused) dbl parameter to tell if mouseup */ + + for (y = x->gl_list; y; y = y->g_next) { + int x1, y1, x2, y2, clickreturned = 0; + + /* check if the object wants to be clicked */ + if (canvas_hitbox(x, y, xpos, ypos, &x1, &y1, &x2, &y2) + && (clickreturned = gobj_click(y, x, xpos, ypos, + 0, 0, 1, 0))) + break; + } +#endif + + +} + + /* displace the selection by (dx, dy) pixels */ +static void canvas_displaceselection(t_canvas *x, int dx, int dy) +{ + t_selection *y; + int resortin = 0, resortout = 0; + if (!canvas_undo_already_set_move) + { + canvas_setundo(x, canvas_undo_move, canvas_undo_set_move(x, 1), + "motion"); + canvas_undo_already_set_move = 1; + } + for (y = x->gl_editor->e_selection; y; y = y->sel_next) + { + t_class *cl = pd_class(&y->sel_what->g_pd); + gobj_displace(y->sel_what, x, dx, dy); + if (cl == vinlet_class) resortin = 1; + else if (cl == voutlet_class) resortout = 1; + } + if (resortin) canvas_resortinlets(x); + if (resortout) canvas_resortoutlets(x); + canvas_dirty(x, 1); +} + + /* this routine is called whenever a key is pressed or released. "x" + may be zero if there's no current canvas. The first argument is true or + fals for down/up; the second one is either a symbolic key name (e.g., + "Right" or an Ascii key number. */ +void canvas_key(t_canvas *x, t_symbol *s, int ac, t_atom *av) +{ + static t_symbol *keynumsym, *keyupsym, *keynamesym; + int keynum, fflag; + t_symbol *gotkeysym; + + int down, shift; + + if (ac < 3) + return; + if (!x->gl_editor) + { + bug("editor"); + return; + } + canvas_undo_already_set_move = 0; + down = (atom_getfloat(av) != 0); /* nonzero if it's a key down */ + shift = (atom_getfloat(av+2) != 0); /* nonzero if shift-ed */ + if (av[1].a_type == A_SYMBOL) + gotkeysym = av[1].a_w.w_symbol; + else if (av[1].a_type == A_FLOAT) + { + char buf[3]; + sprintf(buf, "%c", (int)(av[1].a_w.w_float)); + gotkeysym = gensym(buf); + } + else gotkeysym = gensym("?"); + fflag = (av[0].a_type == A_FLOAT ? av[0].a_w.w_float : 0); + keynum = (av[1].a_type == A_FLOAT ? av[1].a_w.w_float : 0); + if (keynum == '\\' || keynum == '{' || keynum == '}') + { + post("%c: dropped", (int)keynum); + return; + } + if (keynum == '\r') keynum = '\n'; + if (av[1].a_type == A_SYMBOL && + !strcmp(av[1].a_w.w_symbol->s_name, "Return")) + keynum = '\n'; + if (!keynumsym) + { + keynumsym = gensym("#key"); + keyupsym = gensym("#keyup"); + keynamesym = gensym("#keyname"); + } +#ifdef MACOSX + if (keynum == 30) + keynum = 0, gotkeysym = gensym("Up"); + else if (keynum == 31) + keynum = 0, gotkeysym = gensym("Down"); + else if (keynum == 28) + keynum = 0, gotkeysym = gensym("Left"); + else if (keynum == 29) + keynum = 0, gotkeysym = gensym("Right"); +#endif + if (keynumsym->s_thing && down) + pd_float(keynumsym->s_thing, (float)keynum); + if (keyupsym->s_thing && !down) + pd_float(keyupsym->s_thing, (float)keynum); + if (keynamesym->s_thing) + { + t_atom at[2]; + at[0] = av[0]; + SETFLOAT(at, down); + SETSYMBOL(at+1, gotkeysym); + pd_list(keynamesym->s_thing, 0, 2, at); + } + if (!x->gl_editor) /* if that 'invis'ed the window, we'd better stop. */ + return; + if (x && down) + { + /* if an object has "grabbed" keys just send them on */ + if (x->gl_editor->e_grab + && x->gl_editor->e_keyfn && keynum) + (* x->gl_editor->e_keyfn) + (x->gl_editor->e_grab, (float)keynum); + /* if a text editor is open send it on */ + else if (x->gl_editor->e_textedfor) + { + if (!x->gl_editor->e_textdirty) + { + canvas_setundo(x, canvas_undo_cut, + canvas_undo_set_cut(x, UCUT_TEXT), "typing"); + } + rtext_key(x->gl_editor->e_textedfor, + (int)keynum, gotkeysym); + if (x->gl_editor->e_textdirty) + canvas_dirty(x, 1); + } + /* check for backspace or clear */ + else if (keynum == 8 || keynum == 127) + { + if (x->gl_editor->e_selectedline) + canvas_clearline(x); + else if (x->gl_editor->e_selection) + { + canvas_setundo(x, canvas_undo_cut, + canvas_undo_set_cut(x, UCUT_CLEAR), "clear"); + canvas_doclear(x); + } + } + /* check for arrow keys */ + else if (!strcmp(gotkeysym->s_name, "Up")) + canvas_displaceselection(x, 0, shift ? -10 : -1); + else if (!strcmp(gotkeysym->s_name, "Down")) + canvas_displaceselection(x, 0, shift ? 10 : 1); + else if (!strcmp(gotkeysym->s_name, "Left")) + canvas_displaceselection(x, shift ? -10 : -1, 0); + else if (!strcmp(gotkeysym->s_name, "Right")) + canvas_displaceselection(x, shift ? 10 : 1, 0); + } +} + +void canvas_motion(t_canvas *x, t_floatarg xpos, t_floatarg ypos, + t_floatarg fmod) +{ + /* post("motion %d %d", xpos, ypos); */ + int mod = fmod; + if (!x->gl_editor) + { + bug("editor"); + return; + } + glist_setlastxy(x, xpos, ypos); + if (x->gl_editor->e_onmotion == MA_MOVE) + { + canvas_displaceselection(x, + xpos - x->gl_editor->e_xwas, ypos - x->gl_editor->e_ywas); + x->gl_editor->e_xwas = xpos; + x->gl_editor->e_ywas = ypos; + } + else if (x->gl_editor->e_onmotion == MA_REGION) + canvas_doregion(x, xpos, ypos, 0); + else if (x->gl_editor->e_onmotion == MA_CONNECT) + canvas_doconnect(x, xpos, ypos, 0, 0); + else if (x->gl_editor->e_onmotion == MA_PASSOUT) + { + if (!x->gl_editor->e_motionfn) + bug("e_motionfn"); + (*x->gl_editor->e_motionfn)(&x->gl_editor->e_grab->g_pd, + xpos - x->gl_editor->e_xwas, + ypos - x->gl_editor->e_ywas); + x->gl_editor->e_xwas = xpos; + x->gl_editor->e_ywas = ypos; + } + else if (x->gl_editor->e_onmotion == MA_DRAGTEXT) + { + t_rtext *rt = x->gl_editor->e_textedfor; + if (rt) + rtext_mouse(rt, xpos - x->gl_editor->e_xwas, + ypos - x->gl_editor->e_ywas, RTEXT_DRAG); + } + else canvas_doclick(x, xpos, ypos, 0, mod, 0); + + x->gl_editor->e_lastmoved = 1; +} + +void canvas_startmotion(t_canvas *x) +{ + int xval, yval; + if (!x->gl_editor) return; + glist_getnextxy(x, &xval, &yval); + if (xval == 0 && yval == 0) return; + x->gl_editor->e_onmotion = MA_MOVE; + x->gl_editor->e_xwas = xval; + x->gl_editor->e_ywas = yval; +} + +/* ----------------------------- window stuff ----------------------- */ + +void canvas_print(t_canvas *x, t_symbol *s) +{ + if (*s->s_name) sys_vgui(".x%x.c postscript -file %s\n", x, s->s_name); + else sys_vgui(".x%x.c postscript -file x.ps\n", x); +} + +void canvas_menuclose(t_canvas *x, t_floatarg force) +{ + if (x->gl_owner) + canvas_vis(x, 0); + else if ((force != 0) || (!x->gl_dirty)) + pd_free(&x->gl_pd); + else sys_vgui("pdtk_check {This window has been modified. Close anyway?}\ + {.x%x menuclose 1;\n}\n", x); +} + + /* put up a dialog which may call canvas_font back to do the work */ +static void canvas_menufont(t_canvas *x) +{ + char buf[80]; + t_canvas *x2 = canvas_getrootfor(x); + gfxstub_deleteforkey(x2); + sprintf(buf, "pdtk_canvas_dofont %%s %d\n", x2->gl_font); + gfxstub_new(&x2->gl_pd, &x2->gl_pd, buf); +} + +static int canvas_find_index1, canvas_find_index2; +static t_binbuf *canvas_findbuf; +int binbuf_match(t_binbuf *inbuf, t_binbuf *searchbuf); + + /* find an atom or string of atoms */ +static int canvas_dofind(t_canvas *x, int *myindex1p) +{ + t_gobj *y; + int myindex1 = *myindex1p, myindex2; + if (myindex1 >= canvas_find_index1) + { + for (y = x->gl_list, myindex2 = 0; y; + y = y->g_next, myindex2++) + { + t_object *ob = 0; + if (ob = pd_checkobject(&y->g_pd)) + { + if (binbuf_match(ob->ob_binbuf, canvas_findbuf)) + { + if (myindex1 > canvas_find_index1 || + myindex1 == canvas_find_index1 && + myindex2 > canvas_find_index2) + { + canvas_find_index1 = myindex1; + canvas_find_index2 = myindex2; + glist_noselect(x); + canvas_vis(x, 1); + canvas_editmode(x, 1.); + glist_select(x, y); + return (1); + } + } + } + } + } + for (y = x->gl_list, myindex2 = 0; y; y = y->g_next, myindex2++) + { + if (pd_class(&y->g_pd) == canvas_class) + { + (*myindex1p)++; + if (canvas_dofind((t_canvas *)y, myindex1p)) + return (1); + } + } + return (0); +} + +static void canvas_find(t_canvas *x, t_symbol *s, int ac, t_atom *av) +{ + int myindex1 = 0, i; + for (i = 0; i < ac; i++) + { + if (av[i].a_type == A_SYMBOL) + { + if (!strcmp(av[i].a_w.w_symbol->s_name, "_semi_")) + SETSEMI(&av[i]); + else if (!strcmp(av[i].a_w.w_symbol->s_name, "_comma_")) + SETCOMMA(&av[i]); + } + } + if (!canvas_findbuf) + canvas_findbuf = binbuf_new(); + binbuf_clear(canvas_findbuf); + binbuf_add(canvas_findbuf, ac, av); + canvas_find_index1 = 0; + canvas_find_index2 = -1; + canvas_whichfind = x; + if (!canvas_dofind(x, &myindex1)) + { + binbuf_print(canvas_findbuf); + post("... couldn't find"); + } +} + +static void canvas_find_again(t_canvas *x) +{ + int myindex1 = 0; + if (!canvas_findbuf || !canvas_whichfind) + return; + if (!canvas_dofind(canvas_whichfind, &myindex1)) + { + binbuf_print(canvas_findbuf); + post("... couldn't find"); + } +} + +static void canvas_find_parent(t_canvas *x) +{ + if (x->gl_owner) + canvas_vis(glist_getcanvas(x->gl_owner), 1); +} + +static int glist_dofinderror(t_glist *gl, void *error_object) +{ + t_gobj *g; + for (g = gl->gl_list; g; g = g->g_next) + { + if ((void *)g == error_object) + { + /* got it... now show it. */ + glist_noselect(gl); + canvas_vis(glist_getcanvas(gl), 1); + canvas_editmode(glist_getcanvas(gl), 1.); + glist_select(gl, g); + return (1); + } + else if (g->g_pd == canvas_class) + { + if (glist_dofinderror((t_canvas *)g, error_object)) + return (1); + } + } + return (0); +} + +void canvas_finderror(void *error_object) +{ + t_canvas *x; + /* find all root canvases */ + for (x = canvas_list; x; x = x->gl_next) + { + if (glist_dofinderror(x, error_object)) + return; + } + post("... sorry, I couldn't find the source of that error."); +} + +void canvas_stowconnections(t_canvas *x) +{ + t_gobj *selhead = 0, *seltail = 0, *nonhead = 0, *nontail = 0, *y, *y2; + t_linetraverser t; + t_outconnect *oc; + if (!x->gl_editor) return; + /* split list to "selected" and "unselected" parts */ + for (y = x->gl_list; y; y = y2) + { + y2 = y->g_next; + if (glist_isselected(x, y)) + { + if (seltail) + { + seltail->g_next = y; + seltail = y; + y->g_next = 0; + } + else + { + selhead = seltail = y; + seltail->g_next = 0; + } + } + else + { + if (nontail) + { + nontail->g_next = y; + nontail = y; + y->g_next = 0; + } + else + { + nonhead = nontail = y; + nontail->g_next = 0; + } + } + } + /* move the selected part to the end */ + if (!nonhead) x->gl_list = selhead; + else x->gl_list = nonhead, nontail->g_next = selhead; + + /* add connections to binbuf */ + binbuf_clear(x->gl_editor->e_connectbuf); + linetraverser_start(&t, x); + while (oc = linetraverser_next(&t)) + { + int s1 = glist_isselected(x, &t.tr_ob->ob_g); + int s2 = glist_isselected(x, &t.tr_ob2->ob_g); + if (s1 != s2) + binbuf_addv(x->gl_editor->e_connectbuf, "ssiiii;", + gensym("#X"), gensym("connect"), + glist_getindex(x, &t.tr_ob->ob_g), t.tr_outno, + glist_getindex(x, &t.tr_ob2->ob_g), t.tr_inno); + } +} + +void canvas_restoreconnections(t_canvas *x) +{ + pd_bind(&x->gl_pd, gensym("#X")); + binbuf_eval(x->gl_editor->e_connectbuf, 0, 0, 0); + pd_unbind(&x->gl_pd, gensym("#X")); +} + +static t_binbuf *canvas_docopy(t_canvas *x) +{ + t_gobj *y; + t_linetraverser t; + t_outconnect *oc; + t_binbuf *b = binbuf_new(); + for (y = x->gl_list; y; y = y->g_next) + { + if (glist_isselected(x, y)) + gobj_save(y, b); + } + linetraverser_start(&t, x); + while (oc = linetraverser_next(&t)) + { + if (glist_isselected(x, &t.tr_ob->ob_g) + && glist_isselected(x, &t.tr_ob2->ob_g)) + { + binbuf_addv(b, "ssiiii;", gensym("#X"), gensym("connect"), + glist_selectionindex(x, &t.tr_ob->ob_g, 1), t.tr_outno, + glist_selectionindex(x, &t.tr_ob2->ob_g, 1), t.tr_inno); + } + } + return (b); +} + +static void canvas_copy(t_canvas *x) +{ + if (!x->gl_editor || !x->gl_editor->e_selection) + return; + binbuf_free(copy_binbuf); + copy_binbuf = canvas_docopy(x); +} + +static void canvas_clearline(t_canvas *x) +{ + if (x->gl_editor->e_selectedline) + { + canvas_disconnect(x, x->gl_editor->e_selectline_index1, + x->gl_editor->e_selectline_outno, + x->gl_editor->e_selectline_index2, + x->gl_editor->e_selectline_inno); + canvas_setundo(x, canvas_undo_disconnect, + canvas_undo_set_disconnect(x, + x->gl_editor->e_selectline_index1, + x->gl_editor->e_selectline_outno, + x->gl_editor->e_selectline_index2, + x->gl_editor->e_selectline_inno), + "disconnect"); + } +} + +extern t_pd *newest; +static void canvas_doclear(t_canvas *x) +{ + t_gobj *y, *y2; + int dspstate; + + dspstate = canvas_suspend_dsp(); + if (x->gl_editor->e_selectedline) + { + canvas_disconnect(x, x->gl_editor->e_selectline_index1, + x->gl_editor->e_selectline_outno, + x->gl_editor->e_selectline_index2, + x->gl_editor->e_selectline_inno); + canvas_setundo(x, canvas_undo_disconnect, + canvas_undo_set_disconnect(x, + x->gl_editor->e_selectline_index1, + x->gl_editor->e_selectline_outno, + x->gl_editor->e_selectline_index2, + x->gl_editor->e_selectline_inno), + "disconnect"); + } + /* if text is selected, deselecting it might remake the + object. So we deselect it and hunt for a "new" object on + the glist to reselect. */ + if (x->gl_editor->e_textedfor) + { + newest = 0; + glist_noselect(x); + if (newest) + { + for (y = x->gl_list; y; y = y->g_next) + if (&y->g_pd == newest) glist_select(x, y); + } + } + while (1) /* this is pretty wierd... should rewrite it */ + { + for (y = x->gl_list; y; y = y2) + { + y2 = y->g_next; + if (glist_isselected(x, y)) + { + glist_delete(x, y); +#if 0 + if (y2) post("cut 5 %x %x", y2, y2->g_next); + else post("cut 6"); +#endif + goto next; + } + } + goto restore; + next: ; + } +restore: + canvas_resume_dsp(dspstate); + canvas_dirty(x, 1); +} + +static void canvas_cut(t_canvas *x) +{ + if (x->gl_editor && x->gl_editor->e_selectedline) + canvas_clearline(x); + else if (x->gl_editor && x->gl_editor->e_selection) + { + canvas_setundo(x, canvas_undo_cut, + canvas_undo_set_cut(x, UCUT_CUT), "cut"); + canvas_copy(x); + canvas_doclear(x); + } +} + +static int paste_onset; +static t_canvas *paste_canvas; + +static void glist_donewloadbangs(t_glist *x) +{ + if (x->gl_editor) + { + t_selection *sel; + for (sel = x->gl_editor->e_selection; sel; sel = sel->sel_next) + if (pd_class(&sel->sel_what->g_pd) == canvas_class) + canvas_loadbang((t_canvas *)(&sel->sel_what->g_pd)); + } +} + +static void canvas_dopaste(t_canvas *x, t_binbuf *b) +{ + t_gobj *newgobj, *last, *g2; + int dspstate = canvas_suspend_dsp(), nbox, count; + + canvas_editmode(x, 1.); + glist_noselect(x); + for (g2 = x->gl_list, nbox = 0; g2; g2 = g2->g_next) nbox++; + + paste_onset = nbox; + paste_canvas = x; + + pd_bind(&x->gl_pd, gensym("#X")); + binbuf_eval(b, 0, 0, 0); + pd_unbind(&x->gl_pd, gensym("#X")); + for (g2 = x->gl_list, count = 0; g2; g2 = g2->g_next, count++) + if (count >= nbox) + glist_select(x, g2); + paste_canvas = 0; + canvas_resume_dsp(dspstate); + canvas_dirty(x, 1); + glist_donewloadbangs(x); +} + +static void canvas_paste(t_canvas *x) +{ + canvas_setundo(x, canvas_undo_paste, canvas_undo_set_paste(x), "paste"); + canvas_dopaste(x, copy_binbuf); +} + +static void canvas_duplicate(t_canvas *x) +{ + if (x->gl_editor->e_onmotion == MA_NONE) + { + t_selection *y; + canvas_copy(x); + canvas_setundo(x, canvas_undo_paste, canvas_undo_set_paste(x), + "duplicate"); + canvas_dopaste(x, copy_binbuf); + for (y = x->gl_editor->e_selection; y; y = y->sel_next) + gobj_displace(y->sel_what, x, + 10, 10); + canvas_dirty(x, 1); + } +} + +static void canvas_selectall(t_canvas *x) +{ + t_gobj *y; + if (!x->gl_edit) + canvas_editmode(x, 1); + for (y = x->gl_list; y; y = y->g_next) + { + if (!glist_isselected(x, y)) + glist_select(x, y); + } +} + +void canvas_connect(t_canvas *x, t_floatarg fwhoout, t_floatarg foutno, + t_floatarg fwhoin, t_floatarg finno) +{ + int whoout = fwhoout, outno = foutno, whoin = fwhoin, inno = finno; + t_gobj *src = 0, *sink = 0; + t_object *objsrc, *objsink; + t_outconnect *oc; + int nin = whoin, nout = whoout; + if (paste_canvas == x) whoout += paste_onset, whoin += paste_onset; + for (src = x->gl_list; whoout; src = src->g_next, whoout--) + if (!src->g_next) goto bad; /* bug fix thanks to Hannes */ + for (sink = x->gl_list; whoin; sink = sink->g_next, whoin--) + if (!sink->g_next) goto bad; + if (!(objsrc = pd_checkobject(&src->g_pd)) || + !(objsink = pd_checkobject(&sink->g_pd))) + goto bad; + if (!(oc = obj_connect(objsrc, outno, objsink, inno))) goto bad; + if (glist_isvisible(x)) + { + sys_vgui(".x%x.c create line %d %d %d %d -width %d -tags l%x\n", + glist_getcanvas(x), 0, 0, 0, 0, + (obj_issignaloutlet(objsrc, outno) ? 2 : 1),oc); + canvas_fixlinesfor(x, objsrc); + } + return; + +bad: + post("%s %d %d %d %d (%s->%s) connection failed", + x->gl_name->s_name, nout, outno, nin, inno, + (src? class_getname(pd_class(&src->g_pd)) : "???"), + (sink? class_getname(pd_class(&sink->g_pd)) : "???")); +} + +#define XTOLERANCE 4 +#define YTOLERANCE 3 +#define NHIST 15 + + /* LATER might have to speed this up */ +static void canvas_tidy(t_canvas *x) +{ + t_gobj *y, *y2, *y3; + int ax1, ay1, ax2, ay2, bx1, by1, bx2, by2; + int histogram[NHIST], *ip, i, besthist, bestdist; + /* if nobody is selected, this means do it to all boxes; + othewise just the selection */ + int all = (x->gl_editor ? (x->gl_editor->e_selection == 0) : 1); + + canvas_setundo(x, canvas_undo_move, canvas_undo_set_move(x, !all), + "motion"); + + /* tidy horizontally */ + for (y = x->gl_list; y; y = y->g_next) + if (all || glist_isselected(x, y)) + { + gobj_getrect(y, x, &ax1, &ay1, &ax2, &ay2); + + for (y2 = x->gl_list; y2; y2 = y2->g_next) + if (all || glist_isselected(x, y2)) + { + gobj_getrect(y2, x, &bx1, &by1, &bx2, &by2); + if (by1 <= ay1 + YTOLERANCE && by1 >= ay1 - YTOLERANCE && + bx1 < ax1) + goto nothorizhead; + } + + for (y2 = x->gl_list; y2; y2 = y2->g_next) + if (all || glist_isselected(x, y2)) + { + gobj_getrect(y2, x, &bx1, &by1, &bx2, &by2); + if (by1 <= ay1 + YTOLERANCE && by1 >= ay1 - YTOLERANCE + && by1 != ay1) + gobj_displace(y2, x, 0, ay1-by1); + } + nothorizhead: ; + } + /* tidy vertically. First guess the user's favorite vertical spacing */ + for (i = NHIST, ip = histogram; i--; ip++) *ip = 0; + for (y = x->gl_list; y; y = y->g_next) + if (all || glist_isselected(x, y)) + { + gobj_getrect(y, x, &ax1, &ay1, &ax2, &ay2); + for (y2 = x->gl_list; y2; y2 = y2->g_next) + if (all || glist_isselected(x, y2)) + { + gobj_getrect(y2, x, &bx1, &by1, &bx2, &by2); + if (bx1 <= ax1 + XTOLERANCE && bx1 >= ax1 - XTOLERANCE) + { + int distance = by1-ay2; + if (distance >= 0 && distance < NHIST) + histogram[distance]++; + } + } + } + for (i = 1, besthist = 0, bestdist = 4, ip = histogram + 1; + i < (NHIST-1); i++, ip++) + { + int hit = ip[-1] + 2 * ip[0] + ip[1]; + if (hit > besthist) + { + besthist = hit; + bestdist = i; + } + } + post("best vertical distance %d", bestdist); + for (y = x->gl_list; y; y = y->g_next) + if (all || glist_isselected(x, y)) + { + int keep = 1; + gobj_getrect(y, x, &ax1, &ay1, &ax2, &ay2); + for (y2 = x->gl_list; y2; y2 = y2->g_next) + if (all || glist_isselected(x, y2)) + { + gobj_getrect(y2, x, &bx1, &by1, &bx2, &by2); + if (bx1 <= ax1 + XTOLERANCE && bx1 >= ax1 - XTOLERANCE && + ay1 >= by2 - 10 && ay1 < by2 + NHIST) + goto nothead; + } + while (keep) + { + keep = 0; + for (y2 = x->gl_list; y2; y2 = y2->g_next) + if (all || glist_isselected(x, y2)) + { + gobj_getrect(y2, x, &bx1, &by1, &bx2, &by2); + if (bx1 <= ax1 + XTOLERANCE && bx1 >= ax1 - XTOLERANCE && + by1 > ay1 && by1 < ay2 + NHIST) + { + int vmove = ay2 + bestdist - by1; + gobj_displace(y2, x, ax1-bx1, vmove); + ay1 = by1 + vmove; + ay2 = by2 + vmove; + keep = 1; + break; + } + } + } + nothead: ; + } + canvas_dirty(x, 1); +} + +static void canvas_texteditor(t_canvas *x) +{ + t_rtext *foo; + char *buf; + int bufsize; + if (foo = x->gl_editor->e_textedfor) + rtext_gettext(foo, &buf, &bufsize); + else buf = "", bufsize = 0; + sys_vgui("pdtk_pd_texteditor {%.*s}\n", bufsize, buf); + +} + +void glob_key(void *dummy, t_symbol *s, int ac, t_atom *av) +{ + /* canvas_editing can be zero; canvas_key checks for that */ + canvas_key(canvas_editing, s, ac, av); +} + +void canvas_editmode(t_canvas *x, t_floatarg fyesplease) +{ + int yesplease = fyesplease; + if (yesplease && x->gl_edit) + return; + x->gl_edit = !x->gl_edit; + if (x->gl_edit && glist_isvisible(x) && glist_istoplevel(x)) + canvas_setcursor(x, CURSOR_EDITMODE_NOTHING); + else + { + glist_noselect(x); + if (glist_isvisible(x) && glist_istoplevel(x)) + canvas_setcursor(x, CURSOR_RUNMODE_NOTHING); + } + sys_vgui("pdtk_canvas_editval .x%x %d\n", + glist_getcanvas(x), x->gl_edit); +} + + /* called by canvas_font below */ +static void canvas_dofont(t_canvas *x, t_floatarg font, t_floatarg xresize, + t_floatarg yresize) +{ + t_gobj *y; + x->gl_font = font; + if (xresize != 1 || yresize != 1) + { + canvas_setundo(x, canvas_undo_move, canvas_undo_set_move(x, 0), + "motion"); + for (y = x->gl_list; y; y = y->g_next) + { + int x1, x2, y1, y2, nx1, ny1; + gobj_getrect(y, x, &x1, &y1, &x2, &y2); + nx1 = x1 * xresize + 0.5; + ny1 = y1 * yresize + 0.5; + gobj_displace(y, x, nx1-x1, ny1-y1); + } + } + if (glist_isvisible(x)) + glist_redraw(x); + for (y = x->gl_list; y; y = y->g_next) + if (pd_class(&y->g_pd) == canvas_class + && !canvas_isabstraction((t_canvas *)y)) + canvas_dofont((t_canvas *)y, font, xresize, yresize); +} + + /* canvas_menufont calls up a TK dialog which calls this back */ +static void canvas_font(t_canvas *x, t_floatarg font, t_floatarg resize, + t_floatarg whichresize) +{ + float realresize, realresx = 1, realresy = 1; + t_canvas *x2 = canvas_getrootfor(x); + if (!resize) realresize = 1; + else + { + if (resize < 20) resize = 20; + if (resize > 500) resize = 500; + realresize = resize * 0.01; + } + if (whichresize != 3) realresx = realresize; + if (whichresize != 2) realresy = realresize; + canvas_dofont(x2, font, realresx, realresy); + sys_defaultfont = font; +} + +static t_glist *canvas_last_glist; +static int canvas_last_glist_x, canvas_last_glist_y; + +void glist_getnextxy(t_glist *gl, int *xpix, int *ypix) +{ + if (canvas_last_glist == gl) + *xpix = canvas_last_glist_x, *ypix = canvas_last_glist_y; + else *xpix = *ypix = 40; +} + +static void glist_setlastxy(t_glist *gl, int xval, int yval) +{ + canvas_last_glist = gl; + canvas_last_glist_x = xval; + canvas_last_glist_y = yval; +} + + +void g_editor_setup(void) +{ +/* ------------------------ events ---------------------------------- */ + class_addmethod(canvas_class, (t_method)canvas_mousedown, gensym("mouse"), + A_FLOAT, A_FLOAT, A_FLOAT, A_FLOAT, A_NULL); + class_addmethod(canvas_class, (t_method)canvas_mouseup, gensym("mouseup"), + A_FLOAT, A_FLOAT, A_FLOAT, A_NULL); + class_addmethod(canvas_class, (t_method)canvas_key, gensym("key"), + A_GIMME, A_NULL); + class_addmethod(canvas_class, (t_method)canvas_motion, gensym("motion"), + A_FLOAT, A_FLOAT, A_FLOAT, A_NULL); + +/* ------------------------ menu actions ---------------------------- */ + class_addmethod(canvas_class, (t_method)canvas_menuclose, + gensym("menuclose"), A_DEFFLOAT, 0); + class_addmethod(canvas_class, (t_method)canvas_cut, + gensym("cut"), A_NULL); + class_addmethod(canvas_class, (t_method)canvas_copy, + gensym("copy"), A_NULL); + class_addmethod(canvas_class, (t_method)canvas_paste, + gensym("paste"), A_NULL); + class_addmethod(canvas_class, (t_method)canvas_duplicate, + gensym("duplicate"), A_NULL); + class_addmethod(canvas_class, (t_method)canvas_selectall, + gensym("selectall"), A_NULL); + class_addmethod(canvas_class, (t_method)canvas_undo, + gensym("undo"), A_NULL); + class_addmethod(canvas_class, (t_method)canvas_redo, + gensym("redo"), A_NULL); + class_addmethod(canvas_class, (t_method)canvas_tidy, + gensym("tidy"), A_NULL); + class_addmethod(canvas_class, (t_method)canvas_texteditor, + gensym("texteditor"), A_NULL); + class_addmethod(canvas_class, (t_method)canvas_editmode, + gensym("editmode"), A_DEFFLOAT, A_NULL); + class_addmethod(canvas_class, (t_method)canvas_print, + gensym("print"), A_SYMBOL, A_NULL); + class_addmethod(canvas_class, (t_method)canvas_menufont, + gensym("menufont"), A_NULL); + class_addmethod(canvas_class, (t_method)canvas_font, + gensym("font"), A_FLOAT, A_FLOAT, A_FLOAT, A_NULL); + class_addmethod(canvas_class, (t_method)canvas_find, + gensym("find"), A_GIMME, A_NULL); + class_addmethod(canvas_class, (t_method)canvas_find_again, + gensym("findagain"), A_NULL); + class_addmethod(canvas_class, (t_method)canvas_find_parent, + gensym("findparent"), A_NULL); + class_addmethod(canvas_class, (t_method)canvas_done_popup, + gensym("done-popup"), A_FLOAT, A_FLOAT, A_FLOAT, A_NULL); + class_addmethod(canvas_class, (t_method)canvas_donecanvasdialog, + gensym("donecanvasdialog"), A_FLOAT, A_FLOAT, A_FLOAT, A_NULL); + class_addmethod(canvas_class, (t_method)glist_arraydialog, + gensym("arraydialog"), A_SYMBOL, A_FLOAT, A_FLOAT, A_FLOAT, A_NULL); + +/* -------------- connect method used in reading files ------------------ */ + class_addmethod(canvas_class, (t_method)canvas_connect, + gensym("connect"), A_FLOAT, A_FLOAT, A_FLOAT, A_FLOAT, A_NULL); + + class_addmethod(canvas_class, (t_method)canvas_disconnect, + gensym("disconnect"), A_FLOAT, A_FLOAT, A_FLOAT, A_FLOAT, A_NULL); +/* -------------- copy buffer ------------------ */ + copy_binbuf = binbuf_new(); +} |