/* Copyright (c) 1997 The Regents of the University of California.
* 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_imp.h"
#include "g_canvas.h"
#include <string.h>

/* LATER:
  how can we check that the font exists?
*/

typedef struct _canvasenvironment
{
    t_symbol *ce_dir;
    int ce_argc;
    t_atom *ce_argv;
} t_canvasenvironment;

struct _canvas
{
    t_glist x_glist;
    int x_font;
    struct _subcanvas *x_parent;
    struct _canvas *x_next;
    t_canvasenvironment *x_env;
    unsigned int x_dirty:1;
    unsigned int x_lastmoved:1;
    unsigned int x_vis:1;
    unsigned int x_willvis:1;
    unsigned int x_edit:1;
    unsigned int x_loading:1;
    unsigned int x_usedastemplate:1;
};

#define x_pd x_glist.gl_gobj.g_pd
#define x_gobj x_glist.gl_gobj
#define x_list x_glist.gl_list

typedef struct _subcanvas
{
    t_object x_ob;
    t_canvas *x_canvas;
    t_canvas *x_supercanvas;	/* zero if none */
} t_subcanvas;

/* ---------------------- variables --------------------------- */

t_class *canvas_class;
static t_class *subcanvas_class;

static t_canvas *canvas_list;	/* list of all visible canvases */
static int canvas_dspstate;

static t_symbol *canvas_newfilename = &s_;
static t_symbol *canvas_newdirectory = &s_;
static int canvas_newargc;
static t_atom *canvas_newargv;

t_canvas *canvas_editing;    /* last canvas to start text edting */ 

/* ------------------ forward function declarations --------------- */
static void canvas_start_dsp(void);
static void canvas_stop_dsp(void);

static void canvas_editmode(t_canvas *x, t_floatarg yesplease);
static void canvas_drawlines(t_canvas *x);
static void canvas_deletelinesforio(t_canvas *x, t_text *text,
    t_inlet *inp, t_outlet *outp);

/* --------- functions to handle the canvas environment ----------- */

void canvas_setargs(int argc, t_atom *argv)
{
    if (canvas_newargv) bug("canvas_setargs");
    canvas_newargc = argc;
    canvas_newargv = copybytes(argv, argc * sizeof(t_atom));
}

void glob_setfilename(void *dummy, t_symbol *filename, t_symbol *dirname)
{
    canvas_newfilename = filename;
    canvas_newdirectory = dirname;
}

t_canvas *canvas_getcurrent(void)
{
    return ((t_canvas *)pd_findbyclass(&s__X, canvas_class));
}

t_canvas *canvas_setcurrent(t_canvas *x)
{
    pd_pushsym(&x->x_glist.gl_pd);
}

t_canvas *canvas_unsetcurrent(t_canvas *x)
{
    pd_popsym(&x->x_glist.gl_pd);
}

static t_canvasenvironment *canvas_getenv(t_canvas *x)
{
    if (!x) bug("canvas_getenv");
    while (!x->x_env)
    	if (!(x = x->x_parent->x_supercanvas))
    	    bug("t_canvasenvironment");
    return (x->x_env);
}

void canvas_getargs(int *argcp, t_atom **argvp)
{
    t_canvasenvironment *e = canvas_getenv(canvas_getcurrent());
    *argcp = e->ce_argc;
    *argvp = e->ce_argv;
}

t_symbol *canvas_getcurrentdir(void)
{
    t_canvasenvironment *e = canvas_getenv(canvas_getcurrent());
    return (e->ce_dir);
}

static t_symbol *canvas_getdir(t_canvas *x)
{
    t_canvasenvironment *e = canvas_getenv(x);
    return (e->ce_dir);
}

void canvas_makefilename(t_canvas *x, char *file, char *result, int resultsize)
{
    char *dir = canvas_getenv(x)->ce_dir->s_name;
    if (*file == '/' || !*dir)
    {
    	strncpy(result, file, resultsize);
    	result[resultsize-1] = 0;
    }
    else
    {
    	int nleft;
    	strncpy(result, dir, resultsize);
    	result[resultsize-1] = 0;
    	nleft = resultsize - strlen(result) - 1;
    	if (nleft <= 0) return;
    	strcat(result, "/");
    	strncat(result, file, nleft);
    	result[resultsize-1] = 0;
    }    	
}

/* -------------------  editors ------------------------------ */

static t_editor *editor_new(void)
{
    t_editor *x = (t_editor *)getbytes(sizeof(*x));
    memset((char *)x, 0, sizeof(*x));
    x->e_connectbuf = binbuf_new();
    x->e_deleted = binbuf_new();
    return (x);
}

static void editor_free(t_editor *x, t_glist *y)
{
    glist_noselect(y);
    freebytes((void *)x, sizeof(*x));
}
    
/* ------------------------ managing the selection ----------------- */

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));
	    /* 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)
{
    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 0
	post("deselect %x", y);
	for (sel3 = x->gl_editor->e_selection; sel3; sel3 = sel3->sel_next)
	{
	     post("list %x->%x", sel3, sel3->sel_what);
	     if (count++ > 30) exit(1);
	}
#endif
	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);
    	    x->gl_editor->e_textedfor = 0;
    	}

    }
    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);
}

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;
    	}
    }
}


/* ---------------- 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, 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, 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, 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, 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, glist);
}

void gobj_vis(t_gobj *x, t_glist *glist, int flag)
{
    if (x->g_pd->c_wb)
    	(*(x->g_pd)->c_wb->w_visfn)(x, glist, flag);
}

void gobj_save(t_gobj *x, t_binbuf *b)
{
    if (x->g_pd->c_wb)
    	(*(x->g_pd)->c_wb->w_savefn)(x, b);
}

/* --------------- traversing the set of lines in a canvas ----------- */

typedef struct linetraverser
{
    t_canvas *tr_x;
    t_object *tr_ob;
    int tr_nout;
    int tr_outno;
    t_object *tr_ob2;
    t_outlet *tr_outlet;
    t_inlet *tr_inlet;
    int tr_nin;
    int tr_inno;
    int tr_x11, tr_y11, tr_x12, tr_y12;
    int tr_x21, tr_y21, tr_x22, tr_y22;
    int tr_lx1, tr_ly1, tr_lx2, tr_ly2;
    t_outconnect *tr_nextoc;
    int tr_nextoutno;
} t_linetraverser;

static void linetraverser_start(t_linetraverser *t, t_canvas *x)
{
    t->tr_ob = 0;
    t->tr_x = x;
    t->tr_nextoc = 0;
    t->tr_nextoutno = t->tr_nout = 0;
}

static t_outconnect *linetraverser_next(t_linetraverser *t)
{
    t_outconnect *rval = t->tr_nextoc;
    t_gobj *y;
    int outno;
    while (!rval)
    {
    	outno = t->tr_nextoutno;
	while (outno == t->tr_nout)
	{
    	    t_gobj *y;
    	    t_object *ob = 0;
    	    if (!t->tr_ob) y = t->tr_x->x_list;
    	    else y = t->tr_ob->ob_g.g_next;
    	    for (; y; y = y->g_next)
    		if (ob = pd_checkobject(&y->g_pd)) break;
    	    if (!ob) return (0);
    	    t->tr_ob = ob;
    	    t->tr_nout = obj_noutlets(ob);
    	    outno = 0;
   	    if (t->tr_x->x_vis)
   	    	gobj_getrect(y, &t->tr_x->x_glist,
   	    	    &t->tr_x11, &t->tr_y11, &t->tr_x12, &t->tr_y12);
   	    else t->tr_x11 = t->tr_y11 = t->tr_x12 = t->tr_y12 = 0;
	}
	t->tr_nextoutno = outno + 1;
	rval = obj_starttraverseoutlet(t->tr_ob, &t->tr_outlet, outno);
    	t->tr_outno = outno;
    }
    t->tr_nextoc = obj_nexttraverseoutlet(rval, &t->tr_ob2,
    	&t->tr_inlet, &t->tr_inno);
    t->tr_nin = obj_ninlets(t->tr_ob2);
    if (!t->tr_nin) bug("drawline");
    if (t->tr_x->x_vis)
    {
    	int inplus = (t->tr_nin == 1 ? 1 : t->tr_nin - 1);
    	int outplus = (t->tr_nout == 1 ? 1 : t->tr_nout - 1);
    	gobj_getrect(&t->tr_ob2->ob_g, &t->tr_x->x_glist,
   	    &t->tr_x21, &t->tr_y21, &t->tr_x22, &t->tr_y22);
    	t->tr_lx1 = t->tr_x11 +
   	    ((t->tr_x12 - t->tr_x11 - IOWIDTH) * t->tr_outno)/outplus + IOMIDDLE;
    	t->tr_ly1 = t->tr_y12;
    	t->tr_lx2 = t->tr_x21 +
   	    ((t->tr_x22 - t->tr_x21 - IOWIDTH) * t->tr_inno)/inplus +
   	    	IOMIDDLE;
    	t->tr_ly2 = t->tr_y21;
    }
    else
    {
    	t->tr_x21 = t->tr_y21 = t->tr_x22 = t->tr_y22 = 0;
    	t->tr_lx1 = t->tr_ly1 = t->tr_lx2 = t->tr_ly2 = 0;
    }
    
    return (rval);
}

static void linetraverser_skipobject(t_linetraverser *t)
{
    t->tr_nextoc = 0;
    t->tr_nextoutno = t->tr_nout;
}

/* -------------------- the canvas object -------------------------- */

void *canvas_new(t_symbol *sel, int argc, t_atom *argv)
{
    t_canvas *x = (t_canvas *)pd_new(canvas_class);
    t_subcanvas *y = (t_subcanvas *)pd_new(subcanvas_class);
    char buf[30];
    t_canvas *super = canvas_getcurrent();
    t_symbol *s = &s_;
    int vis, width, height;
    int xloc = 0, yloc = 0;
    int font = (super ? super->x_font : sys_defaultfont);
    if (argc == 2)
    {
    	if (argv->a_type == A_SYMBOL)   /* obsolete: name, vis */
    	{
    	    width = 600;
    	    height = 400;
    	    s = atom_getsymbolarg(0, argc, argv);
    	    vis = atom_getintarg(1, argc, argv);
    	}
    	else   /* current: width, height */
    	{
    	    width = atom_getintarg(0, argc, argv);;
    	    height = atom_getintarg(1, argc, argv);;
    	    if (width == 100) width = 600, height = 500;
    	    vis = 0;
    	}
    }
    else if (argc == 4)     /* width, height, name, vis */
    {
    	width = atom_getintarg(0, argc, argv);
    	height = atom_getintarg(1, argc, argv);
    	s = atom_getsymbolarg(2, argc, argv);
    	vis = atom_getintarg(3, argc, argv);
    	if (width == 100) width = 600, height = 500;
    }
    else if (!argc) /* all default */
    {
    	width = 600;
    	height = 500;
    	vis = 0;
    }
    else if (argc == 5)  /* toplevel in v0.19: x, y, w, h, font */
    {
    	char buf[21];
    	xloc = atom_getintarg(0, argc, argv);
    	yloc = atom_getintarg(1, argc, argv);
    	width = atom_getintarg(2, argc, argv);
    	height = atom_getintarg(3, argc, argv);
    	font = atom_getintarg(4, argc, argv);
    }
    else if (argc == 6)  /* subwindow in v0.19: x, y, w, h, name, vis */
    {
    	xloc = atom_getintarg(0, argc, argv);
    	yloc = atom_getintarg(1, argc, argv);
    	width = atom_getintarg(2, argc, argv);
    	height = atom_getintarg(3, argc, argv);
    	s = atom_getsymbolarg(4, argc, argv);
    	vis = atom_getintarg(5, argc, argv);
    }
    if (canvas_newdirectory)
    {
    	t_canvasenvironment *env = x->x_env =
    	    (t_canvasenvironment *)getbytes(sizeof(*x->x_env));
    	env->ce_dir = canvas_newdirectory;
    	env->ce_argc = canvas_newargc;
    	env->ce_argv = canvas_newargv;
    	canvas_newdirectory = 0;
    	canvas_newargc = 0;
    	canvas_newargv = 0;
    }
    else x->x_env = 0;
    glist_init(&x->x_glist);
    x->x_glist.gl_x1 = x->x_glist.gl_px1 = xloc;
    x->x_glist.gl_y1 = x->x_glist.gl_py1 = yloc;
    x->x_glist.gl_x2 = x->x_glist.gl_px2 = xloc + width;
    x->x_glist.gl_y2 = x->x_glist.gl_py2 = yloc + height;
    sprintf(buf, ".x%x", x);
    pd_bind(&x->x_pd, gensym(buf));
    x->x_glist.gl_name = (*s->s_name ? s : 
    	(canvas_newfilename ? canvas_newfilename : gensym("Pd")));
    pd_bind(&x->x_pd, x->x_glist.gl_name);

    x->x_dirty = 0;
    x->x_edit = !strncmp(x->x_glist.gl_name->s_name, "Untitled", 8);
    x->x_vis = 0;
    x->x_loading = 1;
    x->x_usedastemplate = 0;
    x->x_willvis = vis;
    x->x_font = font;
    pd_pushsym(&x->x_pd);

    y->x_canvas = x;
    y->x_supercanvas = super;
    x->x_parent = y;

    return(x);
}

extern t_pd *newest;

void canvas_reflecttitle(t_canvas *x)
{
    sys_vgui("wm title .x%x {%s%c - %s}\n", 
    	x, x->x_glist.gl_name->s_name, (x->x_dirty? '*' : ' '),
    	    canvas_getdir(x)->s_name);
}

void canvas_dirty(t_canvas *x, t_int n)
{
    t_canvas *x2 = canvas_getrootfor(x);
    if (n != x2->x_dirty)
    {
    	x2->x_dirty = n;
    	canvas_reflecttitle(x2);
    }
}

void canvas_vis(t_canvas *x, int flag)
{
    t_gobj *y;
    if (flag == x->x_vis) return;
    if (flag)
    {
    	x->x_glist.gl_editor = editor_new();
    	sys_vgui("pdtk_canvas_new .x%x %d %d +%d+%d\n", x,
    	    (int)(x->x_glist.gl_px2 - x->x_glist.gl_px1),
    	    (int)(x->x_glist.gl_py2 - x->x_glist.gl_py1),
    	    (int)(x->x_glist.gl_px1), (int)(x->x_glist.gl_py1));
    	canvas_reflecttitle(x);
    	for (y = x->x_list; y; y = y->g_next) gobj_vis(y, &x->x_glist, 1);
    	x->x_vis = flag;
    	canvas_drawlines(x);
    	    /* add to Pd window find menu */
    	sys_vgui(
    	    ".mbar.find.menu add command -label %s -command {raise .x%x}\n",
    	    	x->x_glist.gl_name->s_name, x);
    	    	/* add to the window list. */
    	x->x_next = canvas_list;
    	canvas_list = x;
    }
    else
    {
    	int i;
    	t_canvas *x2;
    	glist_noselect(&x->x_glist);
    	for (y = x->x_list; y; y = y->g_next) gobj_vis(y, &x->x_glist, 0);
    	editor_free(x->x_glist.gl_editor, &x->x_glist);
    	x->x_glist.gl_editor = 0;
   	sys_vgui("destroy .x%x\n", x);
    	for (i = 1, x2 = x; x2; x2 = x2->x_next, i++);
    	sys_vgui(".mbar.find.menu delete %d\n", i);
    	
    	    /* take it off the window list */
	if (x == canvas_list) canvas_list = x->x_next;
	else
	{
    	    t_canvas *y;
    	    for (y = canvas_list; y->x_next != x; y = y->x_next);
    	    y->x_next = x->x_next;
	}
    	x->x_vis = flag;
    }
}

int canvas_isvisible(t_canvas *x)
{
    return (x->x_vis);
}

int canvas_getfont(t_canvas *x)
{
    return (x->x_font);
}

void canvas_free(t_canvas *x)
{
    t_gobj *y;
    char buf[30];
    int dspstate = canvas_suspend_dsp();

    if (canvas_editing == x) canvas_editing = 0;
    sprintf(buf, ".x%x", x);
    glist_noselect(&x->x_glist);
    while (y = x->x_glist.gl_list) glist_delete(&x->x_glist, y);
    pd_unbind(&x->x_pd, gensym(buf));
    pd_unbind(&x->x_pd, x->x_glist.gl_name);
    canvas_vis(x, 0);
    canvas_resume_dsp(dspstate);
    glist_cleanup(&x->x_glist);
}

/* ------------------------ event handling ------------------------ */

static void canvas_setcursor(t_canvas *x, char *name)
{
    static t_canvas *xwas;
    static char *namewas;
    if (xwas != x || namewas != name)
    {
    	sys_vgui(".x%x configure -cursor %s\n", x, name);
    	xwas = x;
    	namewas = name;
    }
}

#define NOMOD 0
#define SHIFTMOD 1
#define CTRLMOD 2
#define ALTMOD 3
#define DBLMOD 4

t_gobj *canvas_hitbox(t_canvas *x, int xpos, int ypos,
    int *x1p, int *y1p, int *x2p, int *y2p)
{
    t_gobj *y, *rval = 0;
    for (y = x->x_list; y; y = y->g_next)
    {
    	int x1, y1, x2, y2;
    	gobj_getrect(y, &x->x_glist, &x1, &y1, &x2, &y2);
    	if (xpos >= x1 && xpos <= x2 && ypos >= y1 && ypos <= y2)
    	{
    	    *x1p = x1;
    	    *y1p = y1;
    	    *x2p = x2;
    	    *y2p = y2;
    	    rval = y;
    	}
    }
    return (rval);
}

    /* single mouse click */
void canvas_doclick(t_canvas *x, t_int xpos, t_int ypos, t_int which,
    t_int mod, t_int doit)
{

    	/* modifiers are handled in a very confused way here.  In effect,
    	we pretend that there's only one mod at a time, mostly to try to
    	_prevent_ comfusion; however, watch the peculiar role of the
    	"shifted" flag below, and the confusing aliasing of the "mod"
    	flag afterward... */
    	
    t_gobj *y;
    int shifted = (mod == SHIFTMOD);
    if (!x->x_glist.gl_editor)
    {
    	bug("editor");
    	return;
    }
    if (!x->x_edit) mod = CTRLMOD; 
    x->x_glist.gl_editor->e_lastmoved = 0;
    if (doit)
    {
    	x->x_glist.gl_editor->e_grab = 0;
    	x->x_glist.gl_editor->e_onmotion = MA_NONE;
    }
    /* post("click %d %d %d %d", xpos, ypos, which, mod); */
    
    if (x->x_glist.gl_editor->e_onmotion == MA_NONE)
    {
    	int x1, y1, x2, y2;
    	x->x_glist.gl_editor->e_xwas = xpos;
    	x->x_glist.gl_editor->e_ywas = ypos;
    	
    	if (doit && x->x_glist.gl_editor->e_textedfor)
    	    glist_noselect(&x->x_glist);
        if (y = canvas_hitbox(x, xpos, ypos, &x1, &y1, &x2, &y2))
    	{
    	    	/* hit a rectangle. */
    	    if (mod == NOMOD)
    	    {
    	    	    /* look for an outlet */
    	    	t_object *ob = pd_checkobject(&y->g_pd);
    	    	int noutlet;
    	    	if (ob && (noutlet = obj_noutlets(ob)) && ypos >= y2-3)
    	    	{
    	    	    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 && xpos < hotspot + IOWIDTH)
    	    	    {
    	    	    	if (doit)
    	    	    	{
    	    	    	    x->x_glist.gl_editor->e_onmotion = MA_CONNECT;
    	    	    	    x->x_glist.gl_editor->e_xwas = xpos;
    	    	    	    x->x_glist.gl_editor->e_ywas = ypos;
    	    	    	    sys_vgui(
    	    	    	    	".x%x.c create line %d %d %d %d -tags x\n",
    	    	    	    	    x, xpos, ypos, xpos, ypos);
    	    	    	}    	    	    	    	
    	    	    	else canvas_setcursor(x, "circle");
    	    	    	return;
    	    	    }
    	    	}
    	    	    /* not in an outlet; select and move */
    	    	if (doit)
    	    	{
    	    	    if (!glist_isselected(&x->x_glist, y))
    	    	    {
    	    		glist_noselect(&x->x_glist);
    	    		glist_select(&x->x_glist, y);
    	    	    }
    	    	    x->x_glist.gl_editor->e_onmotion = MA_MOVE;
    	    	}
    	    	else canvas_setcursor(x, "hand2"); 
    	    }
    	    else if (mod == SHIFTMOD)
    	    {
    	    	if (doit)
    	    	{
    	    	    if (glist_isselected(&x->x_glist, y))
    	    	    	glist_deselect(&x->x_glist, y);
    	    	    else glist_select(&x->x_glist, y);
    	    	}
    	    }
    	    else if (mod == CTRLMOD)
    	    {
    	    	if (doit && zgetfn(&y->g_pd, gensym("click")))
    	    	    vmess(&y->g_pd, gensym("click"),
    	    	    	"iii", xpos, ypos, shifted);
    	    	else canvas_setcursor(x, "left_ptr");
    	    }
    	    /* if (doit) canvas_setcursor(x, "left_ptr"); */
    	    return;
    	}
    	if (mod == CTRLMOD)
    	{
    	    canvas_setcursor(x, "left_ptr");
    	    return;
    	}
    	    /* having failed to find a box, we try lines now. */
	if (!mod || mod == DBLMOD)
	{
    	    t_linetraverser t;
    	    t_outconnect *oc;
    	    float fx = xpos, fy = ypos;
    	    linetraverser_start(&t, glist_getcanvas(&x->x_glist));
    	    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)
    		{
    	    	    sys_vgui(".x%x.c delete l%x\n",
    	    	    	glist_getcanvas(&x->x_glist), oc);
    	    	    obj_disconnect(t.tr_ob, t.tr_outno, t.tr_ob2, t.tr_inno);
    		}
    		else canvas_setcursor(x, "X_cursor");
    		return;
    	    }
	}
	canvas_setcursor(x, "hand2");
	if (doit)
	{
	    if (mod != SHIFTMOD) glist_noselect(&x->x_glist);
    	    sys_vgui(".x%x.c create rectangle %d %d %d %d -tags x\n",
    	    	  x, xpos, ypos, xpos, ypos);
    	    x->x_glist.gl_editor->e_onmotion = MA_REGION;
    	}
    }
    
}

void canvas_click(t_canvas *x, t_floatarg xpos, t_floatarg ypos,
    t_floatarg which, t_floatarg mod)
{
    canvas_doclick(x, xpos, ypos, which, mod, 1);
}

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->x_glist.gl_editor->e_xwas,
    	ywas = x->x_glist.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->x_glist.gl_editor->e_xwas,
    	    	x->x_glist.gl_editor->e_ywas, xpos, ypos);

    if ((y1 = canvas_hitbox(x, xwas, ywas, &x11, &y11, &x12, &y12))
    	&& (y2 = canvas_hitbox(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)) && ywas >= y12-3
    	    && (ninlet2 = obj_ninlets(ob2)))
    	{
    	    if (doit)
    	    {
    		int width1 = x12 - x11, closest1, hotspot1;
    		int width2 = x22 - x21, closest2, hotspot2;
    		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 &&
    	    	    xwas >= hotspot1 && xwas < hotspot1 + IOWIDTH &&
    	    	    closest2 < ninlet2)
    		{
    	    	    t_outconnect *oc =
    	    		obj_connect(ob1, closest1, ob2, closest2);
    	    	    int lx1 = x11 +
   	    		(noutlet1 > 1 ?
   	    		    ((x12-x11-IOWIDTH) * closest1)/(noutlet1-1) : 0)
   	    		    	 + IOMIDDLE;
    	    	    int ly1 = y12;
    	    	    int lx2 = x21 +
    	    	    	(ninlet2 > 1 ?
   	    		    ((x22-x21-IOWIDTH) * closest2)/(ninlet2-1) : 0)
   	    		    	+ IOMIDDLE;
    	    	    int ly2 = y21;

    	    	    sys_vgui(".x%x.c create line %d %d %d %d -tags l%x\n",
			glist_getcanvas(&x->x_glist),
		    	    lx1, ly1, lx2, ly2, oc);
    	    	}
    	    	else bug("canvas_doconnect");
    	    }
    	    else canvas_setcursor(x, "circle");
    	    return;
    	}
    }
    canvas_setcursor(x, "hand2");
}

void canvas_doregion(t_canvas *x, int xpos, int ypos, int doit)
{
    if (doit)
    {
    	t_gobj *y;
    	int lox, loy, hix, hiy;
    	if (x->x_glist.gl_editor->e_xwas < xpos)
    	    lox = x->x_glist.gl_editor->e_xwas, hix = xpos;
    	else hix = x->x_glist.gl_editor->e_xwas, lox = xpos;
    	if (x->x_glist.gl_editor->e_ywas < ypos)
    	    loy = x->x_glist.gl_editor->e_ywas, hiy = ypos;
    	else hiy = x->x_glist.gl_editor->e_ywas, loy = ypos;
    	for (y = x->x_list; y; y = y->g_next)
    	{
    	    int x1, y1, x2, y2;
    	    gobj_getrect(y, &x->x_glist, &x1, &y1, &x2, &y2);
    	    if (hix >= x1 && lox <= x2 && hiy >= y1 && loy <= y2
    	    	&& !glist_isselected(&x->x_glist, y))
    	    	    glist_select(&x->x_glist, y);
    	}
    	sys_vgui(".x%x.c delete x\n", x);
    	x->x_glist.gl_editor->e_onmotion = 0;
    }
    else sys_vgui(".x%x.c coords x %d %d %d %d\n",
    	    x, x->x_glist.gl_editor->e_xwas,
    	    	x->x_glist.gl_editor->e_ywas, xpos, ypos);
}

void canvas_mouseup(t_canvas *x,
    t_floatarg fxpos, t_floatarg fypos, t_floatarg fwhich)
{
    int xpos = fxpos, ypos = fypos, which = fwhich;
    /* post("mouseup %d %d %d", xpos, ypos, which); */
    if (!x->x_glist.gl_editor)
    {
    	bug("editor");
    	return;
    }
    if (x->x_glist.gl_editor->e_onmotion == MA_CONNECT)
    	canvas_doconnect(x, xpos, ypos, which, 1);
    else if (x->x_glist.gl_editor->e_onmotion == MA_REGION)
    	canvas_doregion(x, xpos, ypos, 1);
    else if (x->x_glist.gl_editor->e_onmotion == MA_MOVE)
    {
    	    /* after motion, if there's only one item selected, activate it */
    	if (x->x_glist.gl_editor->e_selection &&
    	    !(x->x_glist.gl_editor->e_selection->sel_next))
    	    	gobj_activate(x->x_glist.gl_editor->e_selection->sel_what,
    	    	    &x->x_glist, 1);
    }
    else if (x->x_glist.gl_editor->e_onmotion == MA_PASSOUT)
    	x->x_glist.gl_editor->e_onmotion = 0;
    x->x_glist.gl_editor->e_onmotion = MA_NONE;
}

void canvas_key(t_canvas *x, t_floatarg f)
{
    /* post("key %d %c", n, n); */
    int n = f;
    if (n == '\r') n = '\n';
    if (!x->x_glist.gl_editor)
    {
    	bug("editor");
    	return;
    }
    if (x->x_glist.gl_editor->e_grab)
    	vmess(&x->x_glist.gl_editor->e_grab->g_pd, gensym("key"), "i", n);
    else if (x->x_glist.gl_editor->e_textedfor)
    {
    	rtext_key(x->x_glist.gl_editor->e_textedfor, n);
    	x->x_glist.gl_editor->e_textdirty = 1;
    	canvas_dirty(x, 1);
    }
}

void canvas_motion(t_canvas *x, t_floatarg xpos, t_floatarg ypos)
{ 
    /* post("motion %d %d", xpos, ypos); */
    if (!x->x_glist.gl_editor)
    {
    	bug("editor");
    	return;
    }
    if (x->x_glist.gl_editor->e_onmotion == MA_MOVE)
    {
    	t_selection *y;
    	int resortin = 0, resortout = 0;
    	for (y = x->x_glist.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->x_glist,
    	    	xpos - x->x_glist.gl_editor->e_xwas,
    	    	ypos - x->x_glist.gl_editor->e_ywas);
    	    if (cl == vinlet_class) resortin = 1;
    	    else if (cl == voutlet_class) resortout = 1;
    	}
    	x->x_glist.gl_editor->e_xwas = xpos;
    	x->x_glist.gl_editor->e_ywas = ypos;
    	if (resortin) canvas_resortinlets(x);
    	if (resortout) canvas_resortoutlets(x);
    	canvas_dirty(x, 1);
    }
    else if (x->x_glist.gl_editor->e_onmotion == MA_REGION)
    	canvas_doregion(x, xpos, ypos, 0);
    else if (x->x_glist.gl_editor->e_onmotion == MA_CONNECT)
    	canvas_doconnect(x, xpos, ypos, 0, 0);
    else if (x->x_glist.gl_editor->e_onmotion == MA_PASSOUT)
    {
    	vmess(&x->x_glist.gl_editor->e_grab->g_pd, gensym("motion"), "ff",
    	    xpos - x->x_glist.gl_editor->e_xwas,
    	    ypos - x->x_glist.gl_editor->e_ywas);
    	x->x_glist.gl_editor->e_xwas = xpos;
    	x->x_glist.gl_editor->e_ywas = ypos;
    }
    else canvas_doclick(x, xpos, ypos, 0, 0, 0);
    
    x->x_glist.gl_editor->e_lastmoved = 1;
}

/* ----------------- lines ---------- */

static void canvas_drawlines(t_canvas *x)
{
    t_linetraverser t;
    t_outconnect *oc;
    {
    	linetraverser_start(&t, x);
    	while (oc = linetraverser_next(&t))
    	    sys_vgui(".x%x.c create line %d %d %d %d -tags l%x\n",
		    glist_getcanvas(&x->x_glist),
		    	t.tr_lx1, t.tr_ly1, t.tr_lx2, t.tr_ly2, oc);
    }
}

void canvas_fixlinesfor(t_canvas *x, t_text *text)
{
    t_linetraverser t;
    t_outconnect *oc;

    linetraverser_start(&t, x);
    while (oc = linetraverser_next(&t))
    {
    	if (t.tr_ob == text || t.tr_ob2 == text)
    	{
    	    sys_vgui(".x%x.c coords l%x %d %d %d %d\n",
		glist_getcanvas(&x->x_glist), oc,
		    t.tr_lx1, t.tr_ly1, t.tr_lx2, t.tr_ly2);
    	}
    }
}

    /* kill all lines for the object */
void canvas_deletelinesfor(t_canvas *x, t_text *text)
{
    t_linetraverser t;
    t_outconnect *oc;
    linetraverser_start(&t, x);
    while (oc = linetraverser_next(&t))
    {
    	if (t.tr_ob == text || t.tr_ob2 == text)
    	{
    	    if (x->x_glist.gl_editor)
    	    {
    	    	sys_vgui(".x%x.c delete l%x\n",
    	    	    glist_getcanvas(&x->x_glist), oc);
    	    }
    	    obj_disconnect(t.tr_ob, t.tr_outno, t.tr_ob2, t.tr_inno);
    	}
    }
}

    /* kill all lines for one inlet or outlet */
static void canvas_deletelinesforio(t_canvas *x, t_text *text,
    t_inlet *inp, t_outlet *outp)
{
    t_linetraverser t;
    t_outconnect *oc;
    linetraverser_start(&t, x);
    while (oc = linetraverser_next(&t))
    {
    	if ((t.tr_ob == text && t.tr_outlet == outp) ||
    	    (t.tr_ob2 == text && t.tr_inlet == inp))
    	{
	    if (x->x_glist.gl_editor)
	    {
    	    	sys_vgui(".x%x.c delete l%x\n",
    	    	    glist_getcanvas(&x->x_glist), oc);
    	    }
    	    obj_disconnect(t.tr_ob, t.tr_outno, t.tr_ob2, t.tr_inno);
    	}
    }
}

/* ----------------------------- 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_tobinbuf(t_canvas *x, t_binbuf *b)
{
    t_gobj *y;
    t_linetraverser t;
    t_outconnect *oc;
    if (x->x_parent->x_supercanvas && !x->x_env)
    	binbuf_addv(b, "ssiiiisi;", gensym("#N"), gensym("canvas"),
    	    (t_int)(x->x_glist.gl_px1),
    	    (t_int)(x->x_glist.gl_py1),
    	    (t_int)(x->x_glist.gl_px2 - x->x_glist.gl_px1),
    	    (t_int)(x->x_glist.gl_py2 - x->x_glist.gl_py1),
    	    	x->x_glist.gl_name, x->x_vis);
    else binbuf_addv(b, "ssiiiii;", gensym("#N"), gensym("canvas"),
    	    (t_int)(x->x_glist.gl_px1),
    	    (t_int)(x->x_glist.gl_py1),
    	    (t_int)(x->x_glist.gl_px2 - x->x_glist.gl_px1),
    	    (t_int)(x->x_glist.gl_py2 - x->x_glist.gl_py1),
    	    	x->x_font);
    for (y = x->x_list; y; y = y->g_next) gobj_save(y, b);
    linetraverser_start(&t, x);
    while (oc = linetraverser_next(&t))
    {
    	int srcno, sinkno;
    	for (srcno = 0, y = x->x_list; y && y != &t.tr_ob->ob_g; y = y->g_next)
    	    srcno++;
    	for (sinkno = 0, y = x->x_list; y && y != &t.tr_ob2->ob_g; y = y->g_next)
    	    sinkno++;
    	binbuf_addv(b, "ssiiii;", gensym("#X"), gensym("connect"),
    	    srcno, t.tr_outno, sinkno, t.tr_inno);
    }
}

void canvas_saveto(t_canvas *x, t_symbol *filename, t_symbol *dir)
{
    t_binbuf *b = binbuf_new();
    /* post("canvas_saveto %s %s", filename->s_name, dir->s_name); */
    canvas_tobinbuf(x, b);
    if (binbuf_write(b, filename->s_name, dir->s_name)) sys_ouch();
    else
    {
    	t_canvasenvironment *e = canvas_getenv(x);
    	pd_unbind(&x->x_pd, x->x_glist.gl_name);
    	x->x_glist.gl_name = filename;
    	pd_bind(&x->x_pd, x->x_glist.gl_name);
    	e->ce_dir = dir;
    	canvas_reflecttitle(x);
    	post("saved to: %s/%s", dir->s_name, filename->s_name);
    	canvas_dirty(x, 0);
    }
}

void canvas_menusaveas(t_canvas *x)
{
    t_canvas *x2 = canvas_getrootfor(x);
    sys_vgui("pdtk_canvas_saveas .x%x %s %s\n", x2,
    	x2->x_glist.gl_name->s_name, canvas_getenv(x2)->ce_dir->s_name);
}

void canvas_menuclose(t_canvas *x, t_floatarg force)
{
    if (x->x_parent->x_supercanvas) canvas_vis(x, 0);
    else if ((force != 0) || (!x->x_dirty)) pd_free(&x->x_pd);
    else sys_vgui("pdtk_check {This window has been modified.  Close anyway?}\
     {.x%x menuclose 1\\\n}\n", x);
}

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->x_glist.gl_editor) return;
    	/* split list to "selected" and "unselected" parts */ 
    for (y = x->x_glist.gl_list; y; y = y2)
    {
    	y2 = y->g_next;
    	if (glist_isselected(&x->x_glist, 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->x_glist.gl_list = selhead;
    else x->x_glist.gl_list = nonhead, nontail->g_next = selhead;

    	/* add connections to binbuf */
    binbuf_clear(x->x_glist.gl_editor->e_connectbuf);
    linetraverser_start(&t, x);
    while (oc = linetraverser_next(&t))
    {
    	int srcno = 0, sinkno = 0;
    	int s1 = glist_isselected(&x->x_glist, &t.tr_ob->ob_g);
    	int s2 = glist_isselected(&x->x_glist, &t.tr_ob2->ob_g);
    	if (s1 != s2)
    	{
    	    for (y = x->x_list; y && y != &t.tr_ob->ob_g; y = y->g_next)
    	    	srcno++;
    	    for (y = x->x_list; y && y != &t.tr_ob2->ob_g; y = y->g_next)
    	    	sinkno++;
    	    binbuf_addv(x->x_glist.gl_editor->e_connectbuf, "ssiiii;",
    	    	gensym("#X"), gensym("connect"),
    	    	    srcno, t.tr_outno, sinkno, t.tr_inno);
    	}
    }
}

void canvas_restoreconnections(t_canvas *x)
{
    pd_bind(&x->x_pd, gensym("#X"));
    binbuf_eval(x->x_glist.gl_editor->e_connectbuf, 0, 0, 0);
    pd_unbind(&x->x_pd, gensym("#X"));
}

static t_binbuf *copy_binbuf;

static void canvas_copy(t_canvas *x)
{
    t_gobj *y;
    t_linetraverser t;
    t_outconnect *oc;
    binbuf_clear(copy_binbuf);
    for (y = x->x_glist.gl_list; y; y = y->g_next)
    	if (glist_isselected(&x->x_glist, y))
    	    gobj_save(y, copy_binbuf);
    linetraverser_start(&t, x);
    while (oc = linetraverser_next(&t))
    {
    	int srcno = 0, sinkno = 0;
    	if (glist_isselected(&x->x_glist, &t.tr_ob->ob_g)
    	    && glist_isselected(&x->x_glist, &t.tr_ob2->ob_g))
    	{
    	    for (y = x->x_list; y && y != &t.tr_ob->ob_g; y = y->g_next)
    	    	if (glist_isselected(&x->x_glist, y)) srcno++;
    	    for (y = x->x_list; y && y != &t.tr_ob2->ob_g; y = y->g_next)
    	    	if (glist_isselected(&x->x_glist, y)) sinkno++;
    	    binbuf_addv(copy_binbuf, "ssiiii;", gensym("#X"),
    	    	gensym("connect"), srcno, t.tr_outno, sinkno, t.tr_inno);
    	}
    }
}

static void canvas_cut(t_canvas *x)
{
    t_gobj *y, *y2;
    int dspstate;

    canvas_copy(x);
    dspstate = canvas_suspend_dsp();
    while (1)	/* this is pretty wierd...  should rewrite it */
    {
    	for (y = x->x_glist.gl_list; y; y = y2)
    	{
    	    y2 = y->g_next;
    	    if (glist_isselected(&x->x_glist, y))
    	    {
    		glist_delete(&x->x_glist, 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 int paste_onset;
static t_canvas *paste_canvas;

static void canvas_paste(t_canvas *x)
{
    t_gobj *newgobj, *last, *g2;
    int dspstate = canvas_suspend_dsp(), nbox, count;

    canvas_editmode(x, 1.);
    glist_noselect(&x->x_glist);
    for (g2 = x->x_glist.gl_list, nbox = 0; g2; g2 = g2->g_next) nbox++;
    
    paste_onset = nbox;
    paste_canvas = x;
    
    pd_bind(&x->x_pd, gensym("#X"));
    binbuf_eval(copy_binbuf, 0, 0, 0);
    pd_unbind(&x->x_pd, gensym("#X"));
    for (g2 = x->x_glist.gl_list, count = 0; g2; g2 = g2->g_next, count++)
    	if (count >= nbox)
    	    glist_select(&x->x_glist, g2);
    paste_canvas = 0;
    canvas_resume_dsp(dspstate);
    canvas_dirty(x, 1);
}

static void canvas_duplicate(t_canvas *x)
{
    if (x->x_glist.gl_editor->e_onmotion == MA_NONE)
    {
    	t_selection *y;
    	canvas_copy(x);
     	canvas_paste(x);
    	for (y = x->x_glist.gl_editor->e_selection; y; y = y->sel_next)
    	    gobj_displace(y->sel_what, &x->x_glist,
    	    	10, 10);
    	canvas_dirty(x, 1);
    }
}

#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;
    	/* tidy horizontally */
    for (y = x->x_list; y; y = y->g_next)
    {
    	gobj_getrect(y, &x->x_glist, &ax1, &ay1, &ax2, &ay2);

    	for (y2 = x->x_list; y2; y2 = y2->g_next)
    	{
    	    gobj_getrect(y2, &x->x_glist, &bx1, &by1, &bx2, &by2);
    	    if (by1 <= ay1 + YTOLERANCE && by1 >= ay1 - YTOLERANCE &&
    	    	bx1 < ax1)
    	    	    goto nothorizhead;
    	}

    	for (y2 = x->x_list; y2; y2 = y2->g_next)
    	{
    	    gobj_getrect(y2, &x->x_glist, &bx1, &by1, &bx2, &by2);
    	    if (by1 <= ay1 + YTOLERANCE && by1 >= ay1 - YTOLERANCE
    	    	&& by1 != ay1)
    	    	    gobj_displace(y2, &x->x_glist, 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->x_list; y; y = y->g_next)
    {
    	gobj_getrect(y, &x->x_glist, &ax1, &ay1, &ax2, &ay2);
    	for (y2 = x->x_list; y2; y2 = y2->g_next)
    	{
    	    gobj_getrect(y2, &x->x_glist, &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->x_list; y; y = y->g_next)
    {
    	int keep = 1;
    	gobj_getrect(y, &x->x_glist, &ax1, &ay1, &ax2, &ay2);
    	for (y2 = x->x_list; y2; y2 = y2->g_next)
    	{
    	    gobj_getrect(y2, &x->x_glist, &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->x_list; y2; y2 = y2->g_next)
    	    {
    		gobj_getrect(y2, &x->x_glist, &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->x_glist, 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->x_glist.gl_editor->e_textedfor)
    	rtext_gettext(foo, &buf, &bufsize);
    else buf = "", bufsize = 0;
    sys_vgui("pdtk_pd_texteditor {%.*s}\n", bufsize, buf);
    
}

    /* remove every object from a canvas.  Experimental. */
static void canvas_clear(t_canvas *x)
{
    t_gobj *y, *y2;
    int dspstate = canvas_suspend_dsp();
    while (y = x->x_glist.gl_list) glist_delete(&x->x_glist, y);
    canvas_resume_dsp(dspstate);
}

void glob_key(void *dummy, t_floatarg fkey)
{
    static t_symbol *keysym;
    if (!keysym) keysym = gensym("#key");
    if (keysym->s_thing) pd_float(keysym->s_thing, fkey);
    if (canvas_editing) canvas_key(canvas_editing, fkey);
}

static void canvas_editmode(t_canvas *x, t_floatarg fyesplease)
{
    int yesplease = fyesplease;
    if (yesplease && x->x_edit) return;
    if (x->x_edit = !x->x_edit) canvas_setcursor(x, "hand2");
    else
    {
    	glist_noselect(&x->x_glist);
    	canvas_setcursor(x, "left_ptr");
    }
    sys_vgui("pdtk_canvas_editval .x%x %d\n",
    	glist_getcanvas(&x->x_glist), x->x_edit);
    if (yesplease) canvas_dirty(x, 1);
}

static void canvas_menusave(t_canvas *x)
{
    t_canvas *x2 = canvas_getrootfor(x);
    if (*x2->x_glist.gl_name->s_name &&
    	strncmp(x2->x_glist.gl_name->s_name, "Untitled", 8))
    	    canvas_saveto(x2, x2->x_glist.gl_name, canvas_getenv(x2)->ce_dir);
    else canvas_menusaveas(x2);
}

static void canvas_font(t_canvas *x, t_floatarg font)
{
    x->x_font = font;
}

static void canvas_pop(t_canvas *x, t_floatarg fvis)
{
    int vis = fvis;
    if (vis) canvas_vis(x, 1);
    pd_popsym(&x->x_pd);
    canvas_resortinlets(x);
    canvas_resortoutlets(x);
    x->x_loading = 0;
}

static void canvas_loadbang(t_canvas *x)
{
    t_gobj *y;
    t_symbol *s = gensym("loadbang");
    for (y = x->x_list; y; y = y->g_next)
    	if (pd_class(&y->g_pd) == subcanvas_class)
    	    canvas_loadbang(((t_subcanvas *)y)->x_canvas);
    for (y = x->x_list; y; y = y->g_next)
    	if ((pd_class(&y->g_pd) != canvas_class) &&
    	    zgetfn(&y->g_pd, s))
    	    	vmess(&y->g_pd, s, "");
}

    /* the following are the MEASURED size difference between a window and
    its canvas.  I don't know how to get TK or X to divulge the size of the
    canvas, so we get the size of the "toplevel" window and subtract the
    measured margin size.  Bad bad bad */

#ifdef NT
#define HORIZBORDER 21
#define VERTBORDER 50
#else
#define HORIZBORDER 23
#define VERTBORDER 55
#endif

static void canvas_relocate(t_canvas *x, t_symbol *s)
{
    t_int xpix;
    t_int ypix;
    t_int w;
    t_int h;
    if (sscanf(s->s_name, "%dx%d+%d+%d", &w, &h, &xpix, &ypix) < 4)
    	bug("canvas_relocate");
    x->x_glist.gl_px1 = xpix;
    x->x_glist.gl_py1 = ypix;
    x->x_glist.gl_px2 = xpix + w - HORIZBORDER;
    x->x_glist.gl_py2 = ypix + h - VERTBORDER;
}

void canvas_popabstraction(t_canvas *x)
{
    newest = &x->x_parent->x_ob.ob_pd;
    pd_popsym(&x->x_pd);
    x->x_loading = 0;
    canvas_resortinlets(x);
    canvas_resortoutlets(x);
}


static 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, *sink;
    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->x_list; whoout; src = src->g_next, whoout--)
    	if (!src) goto bad;
    for (sink = x->x_list; whoin; sink = sink->g_next, whoin--)
    	if (!sink) 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 (x->x_vis)
    {
    	sys_vgui(".x%x.c create line %d %d %d %d -tags l%x\n",
	    glist_getcanvas(&x->x_glist), 0, 0, 0, 0, oc);
	canvas_fixlinesfor(x, objsrc);
    }
    return;

bad:
    post("%d %d %d %d; connection failed", nout, outno, nin, inno);
}

void canvas_logerror(t_object *y)
{
#ifdef LATER
    canvas_vis(x, 1);
    if (!glist_isselected(&x->x_glist, &y->ob_g))
    	glist_select(&x->x_glist, &y->ob_g);
#endif
}

/* -------------------------- subcanvases ---------------------- */


static void *subcanvas_new(t_symbol *s)
{
    t_atom a[4];
    t_canvas *x, *z = canvas_getcurrent();
    t_subcanvas *y;
    if (!*s->s_name) s = gensym("/SUBPATCH/");
    SETFLOAT(a, 600);
    SETFLOAT(a+1, 400);
    SETSYMBOL(a+2, s);
    SETFLOAT(a+3, 1);
    x = canvas_new(0, 4, a);
    y = x->x_parent;
    y->x_supercanvas = z;
    canvas_pop(x, 1);
    return (y);
}

    /* this seems odd -- why can't we go straight to the canvas? */
static void subcanvas_saveto(t_subcanvas *x, t_binbuf *b)
{
    canvas_tobinbuf(x->x_canvas, b);
}

static void subcanvas_click(t_subcanvas *x,
    t_floatarg xpos, t_floatarg ypos, t_floatarg down)
{
    if (x->x_canvas->x_vis)
    	sys_vgui("raise .x%x\n", glist_getcanvas(&x->x_canvas->x_glist));
    else canvas_vis(x->x_canvas, 1);
}

static void subcanvas_free(t_subcanvas *x)
{
    x->x_supercanvas = 0;
    pd_free(&x->x_canvas->x_pd);
}

void canvas_objfor(t_glist *gl, t_text *x, int argc, t_atom *argv);

void canvas_restore(t_canvas *x, t_symbol *s, int argc, t_atom *argv)
{
    t_subcanvas *y = x->x_parent;
    t_pd *z;
    canvas_pop(x, x->x_willvis);

    if (!(z = gensym("#X")->s_thing)) error("canvas_restore: out of context");
    else if (*z != canvas_class) error("canvas_restore: wasn't a canvas");
    else
    {
    	t_canvas *x2 = (t_canvas *)z;
    	y->x_supercanvas = x2;
    	canvas_objfor(&x2->x_glist, &y->x_ob, argc, argv);
    }
}

t_inlet *canvas_addinlet(t_canvas *x, t_pd *who, t_symbol *s)
{
    t_inlet *ip = inlet_new(&x->x_parent->x_ob, who, s, 0);
    if (!x->x_loading &&
    	x->x_parent->x_supercanvas && x->x_parent->x_supercanvas->x_vis)
    {
    	gobj_vis(&x->x_parent->x_ob.ob_g,
    	    &x->x_parent->x_supercanvas->x_glist, 0);
    	gobj_vis(&x->x_parent->x_ob.ob_g,
    	    &x->x_parent->x_supercanvas->x_glist, 1);
    	canvas_fixlinesfor(x->x_parent->x_supercanvas, &x->x_parent->x_ob);
    }
    if (!x->x_loading) canvas_resortinlets(x);
    return (ip);
}

void canvas_rminlet(t_canvas *x, t_inlet *ip)
{
    t_canvas *super = x->x_parent->x_supercanvas;
    int redraw = (super && super->x_vis);
    
    if (super) canvas_deletelinesforio(super, &x->x_parent->x_ob, ip, 0);
    if (redraw)
    {
    	gobj_vis(&x->x_parent->x_ob.ob_g,
    	    &x->x_parent->x_supercanvas->x_glist, 0);
    }
    inlet_free(ip);
    if (redraw)
    {
    	gobj_vis(&x->x_parent->x_ob.ob_g,
    	    &x->x_parent->x_supercanvas->x_glist, 1);
    	canvas_fixlinesfor(x->x_parent->x_supercanvas, &x->x_parent->x_ob);
    }
}

extern t_inlet *vinlet_getit(t_pd *x);
extern void obj_moveinletfirst(t_object *x, t_inlet *i);

void canvas_resortinlets(t_canvas *x)
{
    int ninlets = 0, i, j, xmax;
    t_gobj *y, **vec, **vp, **maxp;
    
    for (ninlets = 0, y = x->x_list; y; y = y->g_next)
    	if (pd_class(&y->g_pd) == vinlet_class) ninlets++;

    if (ninlets < 2) return;
    
    vec = (t_gobj **)getbytes(ninlets * sizeof(*vec));
    
    for (y = x->x_list, vp = vec; y; y = y->g_next)
    	if (pd_class(&y->g_pd) == vinlet_class) *vp++ = y;
    
    for (i = ninlets; i--;)
    {
    	t_inlet *ip;
    	t_gobj *y;
	for (vp = vec, xmax = -0x7fffffff, maxp = 0, j = ninlets;
    	    j--; vp++)
	{
    	    int x1, y1, x2, y2;
    	    t_gobj *g = *vp;
    	    if (!g) continue;
    	    gobj_getrect(g, &x->x_glist, &x1, &y1, &x2, &y2);
    	    if (x1 > xmax) xmax = x1, maxp = vp;
    	}
    	if (!maxp) break;
    	y = *maxp;
    	*maxp = 0;
    	ip = vinlet_getit(&y->g_pd);
    	
	obj_moveinletfirst(&x->x_parent->x_ob, ip);
    }
    freebytes(vec, ninlets * sizeof(*vec));
    if (x->x_parent->x_supercanvas && x->x_parent->x_supercanvas->x_vis)
    {
    	canvas_fixlinesfor(x->x_parent->x_supercanvas, &x->x_parent->x_ob);
    }
}

t_outlet *canvas_addoutlet(t_canvas *x, t_pd *who, t_symbol *s)
{
    t_outlet *op = outlet_new(&x->x_parent->x_ob, s);
    if (!x->x_loading &&
    	x->x_parent->x_supercanvas && x->x_parent->x_supercanvas->x_vis)
    {
    	gobj_vis(&x->x_parent->x_ob.ob_g,
    	    &x->x_parent->x_supercanvas->x_glist, 0);
    	gobj_vis(&x->x_parent->x_ob.ob_g,
    	    &x->x_parent->x_supercanvas->x_glist, 1);
    	canvas_fixlinesfor(x->x_parent->x_supercanvas, &x->x_parent->x_ob);
    }
    if (!x->x_loading) canvas_resortoutlets(x);
    return (op);
}

void canvas_rmoutlet(t_canvas *x, t_outlet *op)
{
    t_canvas *super = x->x_parent->x_supercanvas;
    int redraw = (super && super->x_vis);
    
    if (super) canvas_deletelinesforio(super, &x->x_parent->x_ob, 0, op);
    if (redraw)
    {
    	gobj_vis(&x->x_parent->x_ob.ob_g,
    	    &x->x_parent->x_supercanvas->x_glist, 0);
    }
    outlet_free(op);
    if (redraw)
    {
    	gobj_vis(&x->x_parent->x_ob.ob_g,
    	    &x->x_parent->x_supercanvas->x_glist, 1);
    	canvas_fixlinesfor(x->x_parent->x_supercanvas, &x->x_parent->x_ob);
    }
}

extern t_outlet *voutlet_getit(t_pd *x);
extern void obj_moveoutletfirst(t_object *x, t_outlet *i);

void canvas_resortoutlets(t_canvas *x)
{
    int noutlets = 0, i, j, xmax;
    t_gobj *y, **vec, **vp, **maxp;
    
    for (noutlets = 0, y = x->x_list; y; y = y->g_next)
    	if (pd_class(&y->g_pd) == voutlet_class) noutlets++;

    if (noutlets < 2) return;
    
    vec = (t_gobj **)getbytes(noutlets * sizeof(*vec));
    
    for (y = x->x_list, vp = vec; y; y = y->g_next)
    	if (pd_class(&y->g_pd) == voutlet_class) *vp++ = y;
    
    for (i = noutlets; i--;)
    {
    	t_outlet *ip;
    	t_gobj *y;
	for (vp = vec, xmax = -0x7fffffff, maxp = 0, j = noutlets;
    	    j--; vp++)
	{
    	    int x1, y1, x2, y2;
    	    t_gobj *g = *vp;
    	    if (!g) continue;
    	    gobj_getrect(g, &x->x_glist, &x1, &y1, &x2, &y2);
    	    if (x1 > xmax) xmax = x1, maxp = vp;
    	}
    	if (!maxp) break;
    	y = *maxp;
    	*maxp = 0;
    	ip = voutlet_getit(&y->g_pd);
    	
	obj_moveoutletfirst(&x->x_parent->x_ob, ip);
    }
    freebytes(vec, noutlets * sizeof(*vec));
    if (x->x_parent->x_supercanvas && x->x_parent->x_supercanvas->x_vis)
    {
    	canvas_fixlinesfor(x->x_parent->x_supercanvas, &x->x_parent->x_ob);
    }
}

static int subcanvas_isabstraction(t_subcanvas *x)
{
    return (x->x_canvas->x_env != 0);
}

static void canvas_dsp(t_canvas *x, int toplevel, t_signal **sp);
static void subcanvas_dsp(t_subcanvas *x, t_signal **sp)
{
    canvas_dsp(x->x_canvas, 0, sp);
}

int text_isabstraction(t_text *x)
{
    return ((pd_class(&x->te_pd) == subcanvas_class) && 
    	subcanvas_isabstraction((t_subcanvas *)x));
}

t_subcanvas *canvas_getparent(t_canvas *x)
{
    return (x->x_parent);
}

    /* get the document containing this canvas */
t_canvas *canvas_getrootfor(t_canvas *x)
{
    if ((!x->x_parent) || subcanvas_isabstraction(x->x_parent)) return (x);
    else return (canvas_getrootfor(x->x_parent->x_supercanvas));
}

/* ------------------------- DSP chain handling ------------------------- */

EXTERN_STRUCT _dspcontext;
#define t_dspcontext struct _dspcontext

void ugen_start(void);
void ugen_stop(void);

t_dspcontext *ugen_start_graph(int toplevel, t_signal **sp,
    int ninlets, int noutlets);
void ugen_add(t_dspcontext *dc, t_object *x);
void ugen_connect(t_dspcontext *dc, t_object *x1, int outno,
    t_object *x2, int inno);
void ugen_done_graph(t_dspcontext *dc);

int obj_issignaloutlet(t_object *x, int outno);
int obj_nsiginlets(t_object *x);
int obj_nsigoutlets(t_object *x);

    /* schedule one canvas for DSP.  This is called below for all "root"
    canvases, but is also called from the "dsp" method for sub-
    canvases, which are treated like any other tilde object.  LATER,
    we'll handle signal inlets and outlets to subcanvases here. */

static void canvas_dsp(t_canvas *x, int toplevel, t_signal **sp)
{
    t_linetraverser t;
    t_outconnect *oc;
    t_gobj *y;
    t_object *ob;
    t_symbol *dspsym = gensym("dsp");
    t_dspcontext *dc;    
    
    dc = ugen_start_graph(toplevel, sp,
    	obj_nsiginlets(&x->x_parent->x_ob),
    	obj_nsigoutlets(&x->x_parent->x_ob));

    	/* find all the "dsp" boxes. */

    	/* LATER, check if we can re-use signals on the
    	other side of inlet~ and outlet~ to avoid the
    	unnecessary copying of signals in and out of the patch. */
    
    for (y = x->x_list; y; y = y->g_next)
    	if ((ob = pd_checkobject(&y->g_pd)) && zgetfn(&y->g_pd, dspsym))
    	    ugen_add(dc, ob);

    	/* ... and dsp interconnections ... */
    linetraverser_start(&t, x);
    while (oc = linetraverser_next(&t))
    	if (obj_issignaloutlet(t.tr_ob, t.tr_outno))
    	    ugen_connect(dc, t.tr_ob, t.tr_outno, t.tr_ob2, t.tr_inno);

    ugen_done_graph(dc);
}

    /* this routine starts DSP for all root canvases. */
static void canvas_start_dsp(void)
{
    t_canvas *x;
    if (canvas_dspstate) ugen_stop();
    else sys_gui("pdtk_pd_dsp ON\n");
    ugen_start();
    
    for (x = canvas_list; x; x = x->x_next)
    	if (!x->x_parent->x_supercanvas)
    	    canvas_dsp(x, 1, 0);
    
    canvas_dspstate = 1;
}

static void canvas_stop_dsp(void)
{
    if (canvas_dspstate)
    {
    	ugen_stop();
    	sys_gui("pdtk_pd_dsp OFF\n");
    	canvas_dspstate = 0;
    }
}

    /* DSP can be suspended before, and resumed after, operations which
    might affect the DSP chain.  For example, we suspend before loading and
    resume afterward, so that DSP doesn't get resorted for every DSP object
    int the patch. */

int canvas_suspend_dsp(void)
{
    int rval = canvas_dspstate;
    if (rval) canvas_stop_dsp();
    return (rval);
}

void canvas_resume_dsp(int oldstate)
{
    if (oldstate) canvas_start_dsp();
}

    /* this is equivalent to suspending and resuming in one step. */
void canvas_update_dsp(void)
{
    if (canvas_dspstate) canvas_start_dsp();
}

void glob_dsp(void *dummy, t_symbol *s, int argc, t_atom *argv)
{
    int newstate;
    if (argc)
    {
    	newstate = atom_getintarg(0, argc, argv);
    	if (newstate && !canvas_dspstate)
    	    canvas_start_dsp();
    	else if (!newstate && canvas_dspstate)
    	    canvas_stop_dsp();
    }
    else post("dsp state %d", canvas_dspstate);
}

/* ------------------------ fields ------------------------ */

int canvas_template_size(t_canvas *x)
{
    int size = 0;
    t_gobj *g;
    if (!x) return (sizeof(t_float));
    for (g = x->x_glist.gl_list; g; g = g->g_next)
    {
    	if (g->g_pd == field_class)
    	    size += field_size((t_field *)g);
    }
    return (size);
}

extern t_field *field_default(void);

t_field *canvas_find_field(t_canvas *x, t_symbol *name, int *onsetp)
{
    int onset = 0;
    t_gobj *g;
    if (!x)
    {
    	if (strcmp(name->s_name, "y")) return (0);
    	*onsetp = 0;
    	return (field_default());
    }
    for (g = x->x_glist.gl_list; g; g = g->g_next)
    {
    	if (g->g_pd == field_class)
    	{
    	    if (field_name((t_field *)g) == name)
    	    {
    	    	*onsetp = onset;
    	    	return ((t_field *)g);
    	    }
    	    onset += field_size((t_field *)g);
    	}
    }
    return (0);
}

t_float canvas_getfloat(t_canvas *x, t_symbol *fieldname, t_word *wp, int loud)
{
    int onset;
    t_field *field = canvas_find_field(x, fieldname, &onset);
    float val = 0;
    if (field)
    {
    	int type = field_type(field);
    	if (type == DT_FLOAT) val = *(t_float *)(((char *)wp) + onset);
    	else if (loud) error("%s.%s: not a number",
    	    x->x_glist.gl_name->s_name, fieldname->s_name);
    }
    else if (loud) error("%s.%s: no such field",
    	x->x_glist.gl_name->s_name, fieldname->s_name);
    return (val);
}

void canvas_setfloat(t_canvas *x, t_symbol *fieldname, t_word *wp, 
    t_float f, int loud)
{
    int onset;
    t_field *field = canvas_find_field(x, fieldname, &onset);
    if (field)
    {
    	int type = field_type(field);
    	if (type == DT_FLOAT)
    	{
    	    *(t_float *)(((char *)wp) + onset) = f;
    	}
    	else if (loud) error("%s.%s: not a number",
    	    x->x_glist.gl_name->s_name, fieldname->s_name);
    }
    else if (loud) error("%s.%s: no such field",
    	x->x_glist.gl_name->s_name, fieldname->s_name);
}

    /* LATER replace this with a queueing scheme */
void glist_redrawitem(t_glist *owner, t_gobj *gobj)
{
    if (glist_isvisible(owner))
    {
    	gobj_vis(gobj, owner, 0);
    	gobj_vis(gobj, owner, 1);
    }
}

t_datatype *canvas_getdatatypes(t_canvas *x, int *np)
{
    t_datatype *rval = (t_datatype *)getbytes(0), *rnext;
    t_gobj *g;
    int size = 0, n = 0;
    for (g = x->x_glist.gl_list; g; g = g->g_next)
    	if (g->g_pd == field_class)
    {
    	int newsize = size + sizeof(*rval);
    	t_field *f = (t_field *)g;
    	rval = (t_datatype *)t_resizebytes(rval, size, newsize);
    	rnext = (t_datatype *)(((char *)rval) + size);
    	rnext->dt_name = field_name(f);
    	rnext->dt_type = field_type(f);
    	rnext->dt_arraytemplate = field_arraytemplate(f);
    	size = newsize;
    	n++;
    }
    *np = n;
    return (rval);
}

    /* redraw all "scalars" (do this if a drawing command is changed.) 
    LATER we'll use the "template" information to select which ones we
    redraw.  */
static void glist_redrawall(t_glist *gl)
{
    t_gobj *g;
    int vis = glist_isvisible(gl);
    for (g = gl->gl_list; g; g = g->g_next)
    {
    	t_class *cl;
    	if (vis && g->g_pd == scalar_class)
    	    glist_redrawitem(gl, g);
    	else if (g->g_pd == graph_class)
    	    glist_redrawall((t_glist *)g);
    	else if (g->g_pd == subcanvas_class)
    	    glist_redrawall(&((t_subcanvas *)g)->x_canvas->x_glist);
    }
}

    /* public interface for above */
void canvas_redrawallfortemplate(t_canvas *template)
{
    t_canvas *x;
    if (!template->x_usedastemplate) return;
    	/* find all root canvases */
    for (x = canvas_list; x; x = x->x_next)
    	if (!x->x_parent->x_supercanvas)
    	    glist_redrawall(&x->x_glist);
}

    /* Same as above but just zap them.  Call this if a template
    is changed by adding or removing a field.  LATER we'll just
    modify all the items appropriately.  */
static void glist_zapall(t_glist *gl)
{
    t_gobj *g;
    for (g = gl->gl_list; g; g = g->g_next)
    {
    	t_class *cl;
    	if (g->g_pd == graph_class)
    	    glist_zapall((t_glist *)g);
    	else if (g->g_pd == subcanvas_class)
    	    glist_zapall(&((t_subcanvas *)g)->x_canvas->x_glist);
    }
    	/* do we have any scalars? */
    for (g = gl->gl_list; g; g = g->g_next)
    {
    	if (g->g_pd == scalar_class)
    	    break;
    }
    if (!g) return;
    	/* delete all the scalars.  This is inefficient if for some reason
    	 you've mixed scalars with other items in a single glist. */
    while (1)
    {
    	for (g = gl->gl_list; g; g = g->g_next)
    	{
    	    if (g->g_pd == scalar_class)
    	    {
    	    	glist_delete(gl, g);
    	    	break;
    	    }
    	}
    	if (!g) break;
    }
}

    /* public interface for above */
void canvas_zapallfortemplate(t_canvas *template)
{
    t_canvas *x;
    if (!template->x_usedastemplate) return;
    	/* find all root canvases */
    for (x = canvas_list; x; x = x->x_next)
    	if (!x->x_parent->x_supercanvas)
    	    glist_zapall(&x->x_glist);
}

    /* warn a canvas that some datum has used it as a template.  If a
    canvas has no data associated with it (at load time, for instance)
    we don't have to search through the world for instances as it changes. */
void canvas_setusedastemplate(t_canvas *x)
{
    x->x_usedastemplate = 1;
}

/* ------------------------------- setup routine ------------------------ */

    /* why are some of these "glist" and others "canvas"? */
extern void glist_text(t_glist *x, t_symbol *s, int argc, t_atom *argv);
extern void canvas_obj(t_glist *gl, t_symbol *s, int argc, t_atom *argv);
extern void canvas_msg(t_glist *gl, t_symbol *s, int argc, t_atom *argv);
extern void canvas_floatatom(t_glist *gl,
    t_symbol *s, int argc, t_atom *argv);
extern void canvas_symbolatom(t_glist *gl,
    t_symbol *s, int argc, t_atom *argv);
extern void glist_graph(t_glist *g,
    t_symbol *s, int argc, t_atom *argv);
extern void canvas_array(t_glist *canvas);
extern void glist_scalar(t_glist *canvas,
    t_symbol *s, int argc, t_atom *argv);

void g_canvas_setup()
{
    canvas_class = class_new(gensym("canvas"), canvas_new, canvas_free,
    	sizeof(t_canvas), CLASS_PD, A_GIMME, 0);

/* -------------------------- objects ----------------------------- */
    class_addmethod(canvas_class, (t_method)canvas_obj,
    	gensym("obj"), A_GIMME, A_NULL);
    class_addmethod(canvas_class, (t_method)canvas_msg,
    	gensym("msg"), A_GIMME, A_NULL);
    class_addmethod(canvas_class, (t_method)canvas_floatatom,
    	gensym("floatatom"), A_GIMME, A_NULL);
    class_addmethod(canvas_class, (t_method)canvas_symbolatom,
    	gensym("symbolatom"), A_GIMME, A_NULL);
    class_addmethod(canvas_class, (t_method)glist_text,
    	gensym("text"), A_GIMME, A_NULL);
    class_addmethod(canvas_class, (t_method)glist_graph, gensym("graph"),
    	A_GIMME, A_NULL);
    class_addmethod(canvas_class, (t_method)canvas_array,
    	gensym("array"), A_NULL);
    class_addmethod(canvas_class, (t_method)glist_scalar,
    	gensym("scalar"), A_GIMME, A_NULL);

/* ---------------------- methods for reading files ------------------ */
    class_addmethod(canvas_class, (t_method)canvas_font,
    	gensym("font"), A_FLOAT, A_NULL);
    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_restore,
    	gensym("restore"), A_GIMME, 0);

/* ------------------------ events ---------------------------------- */
    class_addmethod(canvas_class, (t_method)canvas_click, gensym("click"),
    	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_FLOAT, A_NULL);
    class_addmethod(canvas_class, (t_method)canvas_motion, gensym("motion"),
    	A_FLOAT, A_FLOAT, A_NULL);

/* ------------------------ window stuff --------------------------- */
    class_addmethod(canvas_class, (t_method)canvas_print, gensym("print"),
    	A_SYMBOL, A_NULL);
    class_addmethod(canvas_class, (t_method)canvas_menusave,
    	gensym("menusave"), 0);
    class_addmethod(canvas_class, (t_method)canvas_menusaveas,
    	gensym("menusaveas"), 0);
    class_addmethod(canvas_class, (t_method)canvas_menuclose,
    	gensym("menuclose"), A_DEFFLOAT, 0);
    class_addmethod(canvas_class, (t_method)canvas_saveto, gensym("saveto"), 
    	A_SYMBOL, A_SYMBOL, 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_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_pop, gensym("pop"),
    	A_DEFFLOAT, A_NULL);
    class_addmethod(canvas_class, (t_method)canvas_loadbang, gensym("loadbang"), A_NULL);
    class_addmethod(canvas_class, (t_method)canvas_relocate, gensym("relocate"),
    	A_SYMBOL, A_NULL);

/* ---------------------- list handling ------------------------ */
    class_addmethod(canvas_class, (t_method)canvas_clear, gensym("clear"), A_NULL);

/* -------------- subcanvases, currently named "pd" ------------------ */
    subcanvas_class = class_new(gensym("pd"), subcanvas_new, 
    	subcanvas_free, sizeof(t_subcanvas), CLASS_NOINLET, A_DEFSYM, 0);

    class_addcreator(subcanvas_new, gensym("page"),  A_DEFSYM, 0);
    class_addmethod(subcanvas_class, (t_method)subcanvas_saveto,
    	gensym("saveto"), A_CANT, 0);
    class_addmethod(subcanvas_class, (t_method)subcanvas_click,
    	gensym("click"), A_FLOAT, A_FLOAT, A_FLOAT, 0);
    class_addmethod(subcanvas_class, (t_method)subcanvas_dsp,
    	gensym("dsp"), 0);

/* -------------- obsolete 0.18 ------------------ */

    class_addmethod(canvas_class, (t_method)canvas_floatatom,
    	gensym("intatom"), A_GIMME, A_NULL);

/* -------------- way obsolete ------------------ */

    class_addmethod(canvas_class, (t_method)canvas_floatatom,
    	gensym("atom"), A_GIMME, A_NULL);

/* -------------- copy buffer ------------------ */
    copy_binbuf = binbuf_new();
}
