/* 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.  */

/* This file deals with the behavior of glists as either "text objects" or
"graphs" inside another glist.  LATER move the inlet/outlet code of g_canvas.c 
to this file... */

#ifdef ROCKBOX
#include "plugin.h"
#include "../../pdbox.h"
#include "m_pd.h"
#include "g_canvas.h"
#define snprintf rb->snprintf
#define atof rb_atof
#else /* ROCKBOX */
#include <stdlib.h>
#include "m_pd.h"
#include "t_tk.h"
#include "g_canvas.h"
#include <stdio.h>
#include <string.h>
#endif /* ROCKBOX */

/* ---------------------- forward definitions ----------------- */

static void graph_vis(t_gobj *gr, t_glist *unused_glist, int vis);
static void graph_graphrect(t_gobj *z, t_glist *glist,
    int *xp1, int *yp1, int *xp2, int *yp2);
static void graph_getrect(t_gobj *z, t_glist *glist,
    int *xp1, int *yp1, int *xp2, int *yp2);

/* -------------------- maintaining the list -------------------- */

void glist_add(t_glist *x, t_gobj *y)
{
    t_object *ob;
    y->g_next = 0;
    if (!x->gl_list) x->gl_list = y;
    else
    {
    	t_gobj *y2;
    	for (y2 = x->gl_list; y2->g_next; y2 = y2->g_next);
    	y2->g_next = y;
    }
    if (x->gl_editor && (ob = pd_checkobject(&y->g_pd)))
    	rtext_new(x, ob);
    if (glist_isvisible(x))
    	gobj_vis(y, x, 1);
    if (class_isdrawcommand(y->g_pd)) 
    	canvas_redrawallfortemplate(glist_getcanvas(x));
}

    /* this is to protect against a hairy problem in which deleting
    a sub-canvas might delete an inlet on a box, after the box had
    been invisible-ized, so that we have to protect against redrawing it! */
int canvas_setdeleting(t_canvas *x, int flag)
{
    int ret = x->gl_isdeleting;
    x->gl_isdeleting = flag;
    return (ret);
}

    /* delete an object from a glist and free it */
void glist_delete(t_glist *x, t_gobj *y)
{
    t_gobj *g;
    t_object *ob;
    t_gotfn chkdsp = zgetfn(&y->g_pd, gensym("dsp"));
    t_canvas *canvas = glist_getcanvas(x);
    int drawcommand = class_isdrawcommand(y->g_pd);
    int wasdeleting;
    
    wasdeleting = canvas_setdeleting(canvas, 1);
    if (x->gl_editor)
    {
    	if (x->gl_editor->e_grab == y) x->gl_editor->e_grab = 0;
    	if (glist_isselected(x, y)) glist_deselect(x, y);

    	    /* HACK -- we had phantom outlets not getting erased on the
	    screen because the canvas_setdeleting() mechanism is too
	    crude.  LATER carefully set up rules for when the rtexts
	    should exist, so that they stay around until all the
	    steps of becoming invisible are done.  In the meantime, just
	    zap the inlets and outlets here... */
	if (pd_class(&y->g_pd) == canvas_class)
	{
	    t_glist *gl = (t_glist *)y;
	    if (gl->gl_isgraph)
	    {
	    	char tag[80];
#ifdef ROCKBOX
                snprintf(tag, sizeof(tag), "graph%lx",
                                           (unsigned long) (intptr_t) gl);
#else /* ROCKBOX */
		sprintf(tag, "graph%x", (int)gl);
#endif /* ROCKBOX */
	    	glist_eraseiofor(x, &gl->gl_obj, tag);
	    }
	    else
	    {
	    	text_eraseborder(&gl->gl_obj, x,
		    rtext_gettag(glist_findrtext(x, &gl->gl_obj)));
	    }
	}
    }
    gobj_delete(y, x);
    if (glist_isvisible(canvas))
    	gobj_vis(y, x, 0);
    if (x->gl_editor && (ob = pd_checkobject(&y->g_pd)))
    	rtext_new(x, ob);
    if (x->gl_list == y) x->gl_list = y->g_next;
    else for (g = x->gl_list; g; g = g->g_next)
    	if (g->g_next == y)
    {
    	g->g_next = y->g_next;
    	break;
    }
    pd_free(&y->g_pd);
    if (chkdsp) canvas_update_dsp();
    if (drawcommand) canvas_redrawallfortemplate(canvas);
    canvas_setdeleting(canvas, wasdeleting);
    x->gl_valid = ++glist_valid;
}

    /* remove every object from a glist.  Experimental. */
void glist_clear(t_glist *x)
{
#ifdef ROCKBOX
    t_gobj *y;
#else
    t_gobj *y, *y2;
#endif
    int dspstate = canvas_suspend_dsp();
    while((y = x->gl_list))
    	glist_delete(x, y);
    canvas_resume_dsp(dspstate);
}

void glist_retext(t_glist *glist, t_text *y)
{
#ifdef ROCKBOX
    glist_getcanvas(glist);
#else
    t_canvas *c = glist_getcanvas(glist);
#endif
    	/* check that we have built rtexts yet.  LATER need a better test. */
    if (glist->gl_editor && glist->gl_editor->e_rtext)
    {
    	t_rtext *rt = glist_findrtext(glist, y);
    	if (rt)
    	    rtext_retext(rt);
    }
}

void glist_grab(t_glist *x, t_gobj *y, t_glistmotionfn motionfn,
    t_glistkeyfn keyfn, int xpos, int ypos)
{
    t_glist *x2 = glist_getcanvas(x);
    if (motionfn)
    	x2->gl_editor->e_onmotion = MA_PASSOUT;
    else x2->gl_editor->e_onmotion = 0;
    x2->gl_editor->e_grab = y;
    x2->gl_editor->e_motionfn = motionfn;
    x2->gl_editor->e_keyfn = keyfn;
    x2->gl_editor->e_xwas = xpos;
    x2->gl_editor->e_ywas = ypos;
}

t_canvas *glist_getcanvas(t_glist *x)
{
    while (x->gl_owner && !x->gl_havewindow && x->gl_isgraph)
    	x = x->gl_owner;
    return((t_canvas *)x);
}

static float gobj_getxforsort(t_gobj *g)
{
    if (pd_class(&g->g_pd) == scalar_class)
    {
    	float x1, y1;
    	scalar_getbasexy((t_scalar *)g, &x1, &y1);
	return(x1);
    }
    else return (0);
}

static t_gobj *glist_merge(t_glist *x, t_gobj *g1, t_gobj *g2)
{
    t_gobj *g = 0, *g9 = 0;
    float f1 = 0, f2 = 0;
#ifdef ROCKBOX
    (void) x;
#endif
    if (g1)
    	f1 = gobj_getxforsort(g1);
    if (g2)
    	f2 = gobj_getxforsort(g2);
    while (1)
    {
    	if (g1)
	{
	    if (g2)
	    {
	    	if (f1 <= f2)
		    goto put1;
		else goto put2;
	    }
	    else goto put1;	
	}
	else if (g2)
	    goto put2;
	else break;
    put1:
    	if (g9)
	    g9->g_next = g1, g9 = g1;
	else g9 = g = g1;
	if((g1 = g1->g_next))
	    f1 = gobj_getxforsort(g1);
    	g9->g_next = 0;
	continue;
    put2:
    	if (g9)
	    g9->g_next = g2, g9 = g2;
	else g9 = g = g2;
	if((g2 = g2->g_next))
	    f2 = gobj_getxforsort(g2);
    	g9->g_next = 0;
	continue;
    }
    return (g);
}

static t_gobj *glist_dosort(t_glist *x,
    t_gobj *g, int nitems)
{
    if (nitems < 2)
    	return (g);
    else
    {
    	int n1 = nitems/2, n2 = nitems - n1, i;
	t_gobj *g2, *g3;
	for (g2 = g, i = n1-1; i--; g2 = g2->g_next)
	    ;
	g3 = g2->g_next;
	g2->g_next = 0;
	g = glist_dosort(x, g, n1);
	g3 = glist_dosort(x, g3, n2);
	return (glist_merge(x, g, g3));
    }
}

void glist_sort(t_glist *x)
{
    int nitems = 0, foo = 0;
    float lastx = -1e37;
    t_gobj *g;
    for (g = x->gl_list; g; g = g->g_next)
    {
    	float x1 = gobj_getxforsort(g);
	if (x1 < lastx)
	    foo = 1;
    	lastx = x1;
	nitems++;
    }
    if (foo)
    	x->gl_list = glist_dosort(x, x->gl_list, nitems);
}

void glist_cleanup(t_glist *x)
{
    freebytes(x->gl_xlabel, x->gl_nxlabels * sizeof(*(x->gl_xlabel)));
    freebytes(x->gl_ylabel, x->gl_nylabels * sizeof(*(x->gl_ylabel)));
    gstub_cutoff(x->gl_stub);
}

void glist_free(t_glist *x)
{
    glist_cleanup(x);
    freebytes(x, sizeof(*x));
}

/* --------------- inlets and outlets  ----------- */


t_inlet *canvas_addinlet(t_canvas *x, t_pd *who, t_symbol *s)
{
    t_inlet *ip = inlet_new(&x->gl_obj, who, s, 0);
    if (!x->gl_loading && x->gl_owner && glist_isvisible(x->gl_owner))
    {
    	gobj_vis(&x->gl_gobj, x->gl_owner, 0);
    	gobj_vis(&x->gl_gobj, x->gl_owner, 1);
    	canvas_fixlinesfor(x->gl_owner, &x->gl_obj);
    }
    if (!x->gl_loading) canvas_resortinlets(x);
    return (ip);
}

void canvas_rminlet(t_canvas *x, t_inlet *ip)
{
    t_canvas *owner = x->gl_owner;
    int redraw = (owner && glist_isvisible(owner) && (!owner->gl_isdeleting)
    	&& glist_istoplevel(owner));
    
    if (owner) canvas_deletelinesforio(owner, &x->gl_obj, ip, 0);
    if (redraw)
    	gobj_vis(&x->gl_gobj, x->gl_owner, 0);
    inlet_free(ip);
    if (redraw)
    {
    	gobj_vis(&x->gl_gobj, x->gl_owner, 1);
    	canvas_fixlinesfor(x->gl_owner, &x->gl_obj);
    }
}

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->gl_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->gl_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;
	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, &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->gl_obj, ip);
    }
    freebytes(vec, ninlets * sizeof(*vec));
    if (x->gl_owner && glist_isvisible(x->gl_owner))
    	canvas_fixlinesfor(x->gl_owner, &x->gl_obj);
}

t_outlet *canvas_addoutlet(t_canvas *x, t_pd *who, t_symbol *s)
{
    t_outlet *op = outlet_new(&x->gl_obj, s);
#ifdef ROCKBOX
    (void) who;
#endif
    if (!x->gl_loading && x->gl_owner && glist_isvisible(x->gl_owner))
    {
    	gobj_vis(&x->gl_gobj, x->gl_owner, 0);
    	gobj_vis(&x->gl_gobj, x->gl_owner, 1);
    	canvas_fixlinesfor(x->gl_owner, &x->gl_obj);
    }
    if (!x->gl_loading) canvas_resortoutlets(x);
    return (op);
}

void canvas_rmoutlet(t_canvas *x, t_outlet *op)
{
    t_canvas *owner = x->gl_owner;
    int redraw = (owner && glist_isvisible(owner) && (!owner->gl_isdeleting)
    	&& glist_istoplevel(owner));
    
    if (owner) canvas_deletelinesforio(owner, &x->gl_obj, 0, op);
    if (redraw)
    	gobj_vis(&x->gl_gobj, x->gl_owner, 0);

    outlet_free(op);
    if (redraw)
    {
    	gobj_vis(&x->gl_gobj, x->gl_owner, 1);
    	canvas_fixlinesfor(x->gl_owner, &x->gl_obj);
    }
}

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->gl_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->gl_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;
	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, &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->gl_obj, ip);
    }
    freebytes(vec, noutlets * sizeof(*vec));
    if (x->gl_owner && glist_isvisible(x->gl_owner))
    	canvas_fixlinesfor(x->gl_owner, &x->gl_obj);
}

/* ----------calculating coordinates and controlling appearance --------- */


static void graph_bounds(t_glist *x, t_floatarg x1, t_floatarg y1,
    t_floatarg x2, t_floatarg y2)
{
    x->gl_x1 = x1;
    x->gl_x2 = x2;
    x->gl_y1 = y1;
    x->gl_y2 = y2;
    if (x->gl_x2 == x->gl_x1 ||
    	x->gl_y2 == x->gl_y1)
    {
	error("graph: empty bounds rectangle");
	x1 = y1 = 0;
	x2 = y2 = 1;
    }
    glist_redraw(x);
}

static void graph_xticks(t_glist *x,
    t_floatarg point, t_floatarg inc, t_floatarg f)
{
    x->gl_xtick.k_point = point;
    x->gl_xtick.k_inc = inc;
    x->gl_xtick.k_lperb = f;
    glist_redraw(x);
}

static void graph_yticks(t_glist *x,
    t_floatarg point, t_floatarg inc, t_floatarg f)
{
    x->gl_ytick.k_point = point;
    x->gl_ytick.k_inc = inc;
    x->gl_ytick.k_lperb = f;
    glist_redraw(x);
}

static void graph_xlabel(t_glist *x, t_symbol *s, int argc, t_atom *argv)
{
    int i;
#ifdef ROCKBOX
    (void) s;
#endif
    if (argc < 1) error("graph_xlabel: no y value given");
    else
    {
    	x->gl_xlabely = atom_getfloat(argv);
    	argv++; argc--;
    	x->gl_xlabel = (t_symbol **)t_resizebytes(x->gl_xlabel, 
    	    x->gl_nxlabels * sizeof (t_symbol *), argc * sizeof (t_symbol *));
    	x->gl_nxlabels = argc;
    	for (i = 0; i < argc; i++) x->gl_xlabel[i] = atom_gensym(&argv[i]);
    }
    glist_redraw(x);
}
    
static void graph_ylabel(t_glist *x, t_symbol *s, int argc, t_atom *argv)
{
    int i;
#ifdef ROCKBOX
    (void) s;
#endif
    if (argc < 1) error("graph_ylabel: no x value given");
    else
    {
    	x->gl_ylabelx = atom_getfloat(argv);
    	argv++; argc--;
    	x->gl_ylabel = (t_symbol **)t_resizebytes(x->gl_ylabel, 
    	    x->gl_nylabels * sizeof (t_symbol *), argc * sizeof (t_symbol *));
    	x->gl_nylabels = argc;
    	for (i = 0; i < argc; i++) x->gl_ylabel[i] = atom_gensym(&argv[i]);
    }
    glist_redraw(x);
}

/****** routines to convert pixels to X or Y value and vice versa ******/

    /* convert an x pixel value to an x coordinate value */
float glist_pixelstox(t_glist *x, float xpix)
{
    	/* if we appear as a text box on parent, our range in our
	coordinates (x1, etc.) specifies the coordinate range
	of a one-pixel square at top left of the window. */
    if (!x->gl_isgraph)
	return (x->gl_x1 + (x->gl_x2 - x->gl_x1) * xpix);

	/* if we're a graph when shown on parent, but own our own
	window right now, our range in our coordinates (x1, etc.) is spread
	over the visible window size, given by screenx1, etc. */  
    else if (x->gl_isgraph && x->gl_havewindow)
    	return (x->gl_x1 + (x->gl_x2 - x->gl_x1) * 
            (xpix) / (x->gl_screenx2 - x->gl_screenx1));

    	/* otherwise, we appear in a graph within a parent glist,
	 so get our screen rectangle on parent and transform. */
    else 
    {
    	int x1, y1, x2, y2;
	if (!x->gl_owner)
    	    bug("glist_pixelstox");	    
    	graph_graphrect(&x->gl_gobj, x->gl_owner, &x1, &y1, &x2, &y2);
	return (x->gl_x1 + (x->gl_x2 - x->gl_x1) * 
            (xpix - x1) / (x2 - x1));
    }
}

float glist_pixelstoy(t_glist *x, float ypix)
{
    if (!x->gl_isgraph)
    	return (x->gl_y1 + (x->gl_y2 - x->gl_y1) * ypix);
    else if (x->gl_isgraph && x->gl_havewindow)
    	return (x->gl_y1 + (x->gl_y2 - x->gl_y1) * 
            	(ypix) / (x->gl_screeny2 - x->gl_screeny1));
    else 
    {
    	int x1, y1, x2, y2;
	if (!x->gl_owner)
    	    bug("glist_pixelstox");
    	graph_graphrect(&x->gl_gobj, x->gl_owner, &x1, &y1, &x2, &y2);
	return (x->gl_y1 + (x->gl_y2 - x->gl_y1) * 
            (ypix - y1) / (y2 - y1));
    }
}

    /* convert an x coordinate value to an x pixel location in window */
float glist_xtopixels(t_glist *x, float xval)
{
    if (!x->gl_isgraph)
    	return ((xval - x->gl_x1) / (x->gl_x2 - x->gl_x1));
    else if (x->gl_isgraph && x->gl_havewindow)
    	return (x->gl_screenx2 - x->gl_screenx1) * 
    	    (xval - x->gl_x1) / (x->gl_x2 - x->gl_x1);
    else
    {
    	int x1, y1, x2, y2;
	if (!x->gl_owner)
    	    bug("glist_pixelstox");
    	graph_graphrect(&x->gl_gobj, x->gl_owner, &x1, &y1, &x2, &y2);
    	return (x1 + (x2 - x1) * (xval - x->gl_x1) / (x->gl_x2 - x->gl_x1));
    }
}

float glist_ytopixels(t_glist *x, float yval)
{
    if (!x->gl_isgraph)
    	return ((yval - x->gl_y1) / (x->gl_y2 - x->gl_y1));
    else if (x->gl_isgraph && x->gl_havewindow)
    	return (x->gl_screeny2 - x->gl_screeny1) * 
            	(yval - x->gl_y1) / (x->gl_y2 - x->gl_y1);
    else 
    {
    	int x1, y1, x2, y2;
	if (!x->gl_owner)
    	    bug("glist_pixelstox");
    	graph_graphrect(&x->gl_gobj, x->gl_owner, &x1, &y1, &x2, &y2);
    	return (y1 + (y2 - y1) * (yval - x->gl_y1) / (x->gl_y2 - x->gl_y1));
    }
}

    /* convert an X screen distance to an X coordinate increment.
      This is terribly inefficient;
      but probably not a big enough CPU hog to warrant optimizing. */
float glist_dpixtodx(t_glist *x, float dxpix)
{ 
    return (dxpix * (glist_pixelstox(x, 1) - glist_pixelstox(x, 0)));
}

float glist_dpixtody(t_glist *x, float dypix)
{
    return (dypix * (glist_pixelstoy(x, 1) - glist_pixelstoy(x, 0)));
}

    /* get the window location in pixels of a "text" object.  The
    object's x and y positions are in pixels when the glist they're
    in is toplevel.  If it's not, we convert to pixels on the parent
    window. */
int text_xpix(t_text *x, t_glist *glist)
{
    if (glist->gl_havewindow || !glist->gl_isgraph)
    	return (x->te_xpix);
    else return (glist_xtopixels(glist, 
	    glist->gl_x1 + (glist->gl_x2 - glist->gl_x1) * 
            	x->te_xpix / (glist->gl_screenx2 - glist->gl_screenx1)));
}

int text_ypix(t_text *x, t_glist *glist)
{
    if (glist->gl_havewindow || !glist->gl_isgraph)
    	return (x->te_ypix);
    else return (glist_ytopixels(glist, 
	    glist->gl_y1 + (glist->gl_y2 - glist->gl_y1) * 
            	x->te_ypix / (glist->gl_screeny2 - glist->gl_screeny1)));
}

    /* redraw all the items in a glist.  We construe this to mean
    redrawing in its own window and on parent, as needed in each case.
    This is too conservative -- for instance, when you draw an "open"
    rectangle on the parent, you shouldn't have to redraw the window!  */
void glist_redraw(t_glist *x)
{  
    if (glist_isvisible(x))
    {
    	    /* LATER fix the graph_vis() code to handle both cases */
    	if (glist_istoplevel(x))
	{
	    t_gobj *g;
	    t_linetraverser t;
	    t_outconnect *oc;
     	    for (g = x->gl_list; g; g = g->g_next)
	    {
     	    	gobj_vis(g, x, 0);
		gobj_vis(g, x, 1);
	    }
	    	/* redraw all the lines */
    	    linetraverser_start(&t, x);
    	    while((oc = linetraverser_next(&t)))
#ifdef ROCKBOX
                ;
#else /* ROCKBOX */
    		sys_vgui(".x%x.c coords l%x %d %d %d %d\n",
		    glist_getcanvas(x), oc,
			t.tr_lx1, t.tr_ly1, t.tr_lx2, t.tr_ly2);
#endif /* ROCKBOX */
	}
	if (x->gl_owner)
	{
    	    graph_vis(&x->gl_gobj, x->gl_owner, 0); 
    	    graph_vis(&x->gl_gobj, x->gl_owner, 1);
	}
    }
}

/* --------------------------- widget behavior  ------------------- */

extern t_widgetbehavior text_widgetbehavior;

    /* Note that some code in here would also be useful for drawing
    graph decorations in toplevels... */
static void graph_vis(t_gobj *gr, t_glist *parent_glist, int vis)
{
    t_glist *x = (t_glist *)gr;
    char tag[50];
    t_gobj *g;
    int x1, y1, x2, y2;
    	/* ordinary subpatches: just act like a text object */
    if (!x->gl_isgraph)
    {
    	text_widgetbehavior.w_visfn(gr, parent_glist, vis);
	return;
    }

    if (vis && canvas_showtext(x))
    	rtext_draw(glist_findrtext(parent_glist, &x->gl_obj));
    graph_getrect(gr, parent_glist, &x1, &y1, &x2, &y2);
    if (!vis)
    	rtext_erase(glist_findrtext(parent_glist, &x->gl_obj));

#ifdef ROCKBOX
    snprintf(tag, sizeof(tag), "graph%lx", (unsigned long) (intptr_t) x);
#else
    sprintf(tag, "graph%x", (int)x);
#endif
    if (vis)
    	glist_drawiofor(parent_glist, &x->gl_obj, 1,
	    tag, x1, y1, x2, y2);
    else glist_eraseiofor(parent_glist, &x->gl_obj, tag);
    	/* if we look like a graph but have been moved to a toplevel,
	just show the bounding rectangle */
    if (x->gl_havewindow)
    {
#ifndef ROCKBOX
    	if (vis)
	{
    	    sys_vgui(".x%x.c create polygon\
 %d %d %d %d %d %d %d %d %d %d -tags %s -fill #c0c0c0\n",
	    	glist_getcanvas(x->gl_owner),
    	    	x1, y1, x1, y2, x2, y2, x2, y1, x1, y1, tag);
	}
	else
	{
    	    sys_vgui(".x%x.c delete %s\n",
    	    	glist_getcanvas(x->gl_owner), tag);
	}
#endif
	return;
    }
    	/* otherwise draw (or erase) us as a graph inside another glist. */
    if (vis)
    {
    	int i;
    	float f;

    	    /* draw a rectangle around the graph */
#ifndef ROCKBOX
    	sys_vgui(".x%x.c create line\
    	    %d %d %d %d %d %d %d %d %d %d -tags %s\n",
	    glist_getcanvas(x->gl_owner),
    	    x1, y1, x1, y2, x2, y2, x2, y1, x1, y1, tag);
#endif

    	    /* draw ticks on horizontal borders.  If lperb field is
    	    zero, this is disabled. */
    	if (x->gl_xtick.k_lperb)
    	{
	    float upix, lpix;
	    if (y2 < y1)
		upix = y1, lpix = y2;
	    else upix = y2, lpix = y1;
    	    for (i = 0, f = x->gl_xtick.k_point;
    		f < 0.99 * x->gl_x2 + 0.01*x->gl_x1; i++,
    		    f += x->gl_xtick.k_inc)
    	    {
#ifndef ROCKBOX
    		int tickpix = (i % x->gl_xtick.k_lperb ? 2 : 4);
    		sys_vgui(".x%x.c create line %d %d %d %d -tags %s\n",
	    	    glist_getcanvas(x->gl_owner),
	    	    (int)glist_xtopixels(x, f), (int)upix,
	    	    (int)glist_xtopixels(x, f), (int)upix - tickpix, tag);
    		sys_vgui(".x%x.c create line %d %d %d %d -tags %s\n",
	    	    glist_getcanvas(x->gl_owner),
	    	    (int)glist_xtopixels(x, f), (int)lpix,
	    	    (int)glist_xtopixels(x, f), (int)lpix + tickpix, tag);
#endif
    	    }
    	    for (i = 1, f = x->gl_xtick.k_point - x->gl_xtick.k_inc;
    		f > 0.99 * x->gl_x1 + 0.01*x->gl_x2;
    		    i++, f -= x->gl_xtick.k_inc)
    	    {
#ifndef ROCKBOX
    		int tickpix = (i % x->gl_xtick.k_lperb ? 2 : 4);
    		sys_vgui(".x%x.c create line %d %d %d %d -tags %s\n",
	    	    glist_getcanvas(x->gl_owner),
	    	    (int)glist_xtopixels(x, f), (int)upix,
	    	    (int)glist_xtopixels(x, f), (int)upix - tickpix, tag);
    		sys_vgui(".x%x.c create line %d %d %d %d -tags %s\n",
	    	    glist_getcanvas(x->gl_owner),
	    	    (int)glist_xtopixels(x, f), (int)lpix,
	    	    (int)glist_xtopixels(x, f), (int)lpix + tickpix, tag);
#endif
    	    }
    	}

    	    /* draw ticks in vertical borders*/
    	if (x->gl_ytick.k_lperb)
    	{
	    float ubound, lbound;
	    if (x->gl_y2 < x->gl_y1)
		ubound = x->gl_y1, lbound = x->gl_y2;
	    else ubound = x->gl_y2, lbound = x->gl_y1;
    	    for (i = 0, f = x->gl_ytick.k_point;
    		f < 0.99 * ubound + 0.01 * lbound;
    		    i++, f += x->gl_ytick.k_inc)
    	    {
#ifndef ROCKBOX
    		int tickpix = (i % x->gl_ytick.k_lperb ? 2 : 4);
    		sys_vgui(".x%x.c create line %d %d %d %d -tags %s\n",
	    	    glist_getcanvas(x->gl_owner),
	    	    x1, (int)glist_ytopixels(x, f), 
	    	    x1 + tickpix, (int)glist_ytopixels(x, f), tag);
    		sys_vgui(".x%x.c create line %d %d %d %d -tags %s\n",
	    	    glist_getcanvas(x->gl_owner),
	    	    x2, (int)glist_ytopixels(x, f), 
	    	    x2 - tickpix, (int)glist_ytopixels(x, f), tag);
#endif
    	    }
    	    for (i = 1, f = x->gl_ytick.k_point - x->gl_ytick.k_inc;
    		f > 0.99 * lbound + 0.01 * ubound;
    		    i++, f -= x->gl_ytick.k_inc)
    	    {
#ifndef ROCKBOX
    		int tickpix = (i % x->gl_ytick.k_lperb ? 2 : 4);
    		sys_vgui(".x%x.c create line %d %d %d %d -tags %s\n",
	    	    glist_getcanvas(x->gl_owner),
	    	    x1, (int)glist_ytopixels(x, f), 
	    	    x1 + tickpix, (int)glist_ytopixels(x, f), tag);
    		sys_vgui(".x%x.c create line %d %d %d %d -tags %s\n",
	    	    glist_getcanvas(x->gl_owner),
	    	    x2, (int)glist_ytopixels(x, f), 
	    	    x2 - tickpix, (int)glist_ytopixels(x, f), tag);
#endif
    	    }
    	}
    	    /* draw x labels */
    	for (i = 0; i < x->gl_nxlabels; i++)
#ifdef ROCKBOX
            ;
#else /* ROCKBOX */
    	    sys_vgui(".x%x.c create text\
    	%d %d -text {%s} -font -*-courier-bold--normal--%d-* -tags %s\n",
    	    	glist_getcanvas(x),
	    	(int)glist_xtopixels(x, atof(x->gl_xlabel[i]->s_name)),
	    	(int)glist_ytopixels(x, x->gl_xlabely), x->gl_xlabel[i]->s_name,
	    	glist_getfont(x), tag);
#endif /* ROCKBOX */

    	    /* draw y labels */
    	for (i = 0; i < x->gl_nylabels; i++)
#ifdef ROCKBOX
            ;
#else /* ROCKBOX */
    	    sys_vgui(".x%x.c create text\
    	%d %d -text {%s} -font -*-courier-bold--normal--%d-* -tags %s\n",
    	    	glist_getcanvas(x),
	    	(int)glist_xtopixels(x, x->gl_ylabelx),
	    	(int)glist_ytopixels(x, atof(x->gl_ylabel[i]->s_name)),
	    	x->gl_ylabel[i]->s_name,
	    	glist_getfont(x), tag);
#endif /* ROCKBOX */

    	    /* draw contents of graph as glist */
     	for (g = x->gl_list; g; g = g->g_next)
     	    gobj_vis(g, x, 1);
    }
    else
    {
#ifndef ROCKBOX
    	sys_vgui(".x%x.c delete %s\n",
    	    glist_getcanvas(x->gl_owner), tag);
#endif
     	for (g = x->gl_list; g; g = g->g_next)
     	    gobj_vis(g, x, 0);
    }
}

    /* get the graph's rectangle, not counting extra swelling for controls
    to keep them inside the graph.  This is the "logical" pixel size. */

static void graph_graphrect(t_gobj *z, t_glist *glist,
    int *xp1, int *yp1, int *xp2, int *yp2)
{
    t_glist *x = (t_glist *)z;
    int x1 = text_xpix(&x->gl_obj, glist);
    int y1 = text_ypix(&x->gl_obj, glist);
    int x2, y2;
#if 0	/* this used to adjust graph size when it was in another graph;
    	    now we just preserve the size. */
    	/* same logic here as in text_xpix(): */
    if (glist->gl_havewindow)
    {
	x2 = x1 + x->gl_pixwidth;
	y2 = y1 + x->gl_pixheight;
    }
    else
    {
    	x2 = glist_xtopixels(glist, 
	    glist->gl_x1 + (glist->gl_x2 - glist->gl_x1) * 
            	(x->gl_obj.te_xpix + x->gl_pixwidth) /
		    (glist->gl_screenx2 - glist->gl_screenx1));
    	y2 = glist_ytopixels(glist, 
	    glist->gl_y1 + (glist->gl_y2 - glist->gl_y1) * 
            	(x->gl_obj.te_ypix + x->gl_pixheight) /
		    (glist->gl_screeny2 - glist->gl_screeny1));
    }
#endif
    x2 = x1 + x->gl_pixwidth;
    y2 = y1 + x->gl_pixheight;

    *xp1 = x1;
    *yp1 = y1;
    *xp2 = x2;
    *yp2 = y2;
}

    /* get the rectangle, enlarged to contain all the "contents" --
    meaning their formal bounds rectangles. */
static void graph_getrect(t_gobj *z, t_glist *glist,
    int *xp1, int *yp1, int *xp2, int *yp2)
{
    int x1 = 0x7fffffff, y1 = 0x7fffffff, x2 = -0x7fffffff, y2 = -0x7fffffff;
    t_glist *x = (t_glist *)z;
    if (x->gl_isgraph)
    {
    	int hadwindow;
	t_gobj *g;
	t_text *ob;
	int x21, y21, x22, y22;

    	graph_graphrect(z, glist, &x1, &y1, &x2, &y2);
	if (canvas_showtext(x))
	{
	    text_widgetbehavior.w_getrectfn(z, glist, &x21, &y21, &x22, &y22);
    	    if (x22 > x2) 
		x2 = x22;
    	    if (y22 > y2) 
		y2 = y22;
	}
	    /* lie about whether we have our own window to affect gobj_getrect
	    calls below.  (LATER add argument to gobj_getrect()?) */
	hadwindow = x->gl_havewindow;
	x->gl_havewindow = 0;
	for (g = x->gl_list; g; g = g->g_next)
	    if ((!(ob = pd_checkobject(&g->g_pd))) || text_shouldvis(ob, x))
	{
	    	/* don't do this for arrays, just let them hang outsize the
		box. */
	    if (pd_class(&g->g_pd) == garray_class)
	    	continue;
    	    gobj_getrect(g, x, &x21, &y21, &x22, &y22);
    	    if (x22 > x2) 
	    	x2 = x22;
    	    if (y22 > y2) 
	    	y2 = y22;
    	}
	x->gl_havewindow = hadwindow;
    }
    else text_widgetbehavior.w_getrectfn(z, glist, &x1, &y1, &x2, &y2);
    *xp1 = x1;
    *yp1 = y1;
    *xp2 = x2;
    *yp2 = y2;
}

static void graph_displace(t_gobj *z, t_glist *glist, int dx, int dy)
{
    t_glist *x = (t_glist *)z;
    if (!x->gl_isgraph)
    	text_widgetbehavior.w_displacefn(z, glist, dx, dy);
    else
    {
    	x->gl_obj.te_xpix += dx;
    	x->gl_obj.te_ypix += dy;
    	glist_redraw(x);
    	canvas_fixlinesfor(glist_getcanvas(glist), &x->gl_obj);
    }
}

static void graph_select(t_gobj *z, t_glist *glist, int state)
{
    t_glist *x = (t_glist *)z;
    if (!x->gl_isgraph)
    	text_widgetbehavior.w_selectfn(z, glist, state);
    else
    {
	t_rtext *y = glist_findrtext(glist, &x->gl_obj);
    	if (canvas_showtext(x))
	    rtext_select(y, state);
#ifndef ROCKBOX
	sys_vgui(".x%x.c itemconfigure %sR -fill %s\n", glist, 
    	rtext_gettag(y), (state? "blue" : "black"));
    	sys_vgui(".x%x.c itemconfigure graph%x -fill %s\n",
    	    glist_getcanvas(glist), z, (state? "blue" : "black"));
#endif
    }
}

static void graph_activate(t_gobj *z, t_glist *glist, int state)
{
    t_glist *x = (t_glist *)z;
    if (canvas_showtext(x))
    	text_widgetbehavior.w_activatefn(z, glist, state);
}

#if 0
static void graph_delete(t_gobj *z, t_glist *glist)
{
    t_glist *x = (t_glist *)z;
    if (!x->gl_isgraph)
    	text_widgetbehavior.w_deletefn(z, glist);
    else
    {
	t_gobj *y;
	while (y = x->gl_list) glist_delete(x, y);
#if 0	    /* I think this was just wrong. */
	if (glist_isvisible(x))
    	    sys_vgui(".x%x.c delete graph%x\n", glist_getcanvas(glist), x);
#endif
    }
}
#endif

static void graph_delete(t_gobj *z, t_glist *glist)
{
    t_glist *x = (t_glist *)z;
    t_gobj *y;
    text_widgetbehavior.w_deletefn(z, glist);
    while((y = x->gl_list))
    	glist_delete(x, y);
}

#ifndef ROCKBOX
static float graph_lastxpix, graph_lastypix;

static void graph_motion(void *z, t_floatarg dx, t_floatarg dy)
{
    t_glist *x = (t_glist *)z;
    float newxpix = graph_lastxpix + dx, newypix = graph_lastypix + dy;
    t_garray *a = (t_garray *)(x->gl_list);
    int oldx = 0.5 + glist_pixelstox(x, graph_lastxpix);
    int newx = 0.5 + glist_pixelstox(x, newxpix);
    t_sample *vec;
    int nelem, i;
    float oldy = glist_pixelstoy(x, graph_lastypix);
    float newy = glist_pixelstoy(x, newypix);
    graph_lastxpix = newxpix;
    graph_lastypix = newypix;
    	/* verify that the array is OK */
    if (!a || pd_class((t_pd *)a) != garray_class)
    	return;
    if (!garray_getfloatarray(a, &nelem, &vec))
    	return;
    if (oldx < 0) oldx = 0;
    if (oldx >= nelem)
    	oldx = nelem - 1;
    if (newx < 0) newx = 0;
    if (newx >= nelem)
    	newx = nelem - 1;
    if (oldx < newx - 1)
    {
    	for (i = oldx + 1; i <= newx; i++)
	    vec[i] = newy + (oldy - newy) *
	    	((float)(newx - i))/(float)(newx - oldx);
    }
    else if (oldx > newx + 1)
    {
    	for (i = oldx - 1; i >= newx; i--)
	    vec[i] = newy + (oldy - newy) *
	    	((float)(newx - i))/(float)(newx - oldx);
    }
    else vec[newx] = newy;
    garray_redraw(a);
}
#endif

static int graph_click(t_gobj *z, struct _glist *glist,
    int xpix, int ypix, int shift, int alt, int dbl, int doit)
{
    t_glist *x = (t_glist *)z;
    t_gobj *y;
    int clickreturned = 0;
    if (!x->gl_isgraph)
    	return (text_widgetbehavior.w_clickfn(z, glist,
	    xpix, ypix, shift, alt, dbl, doit));
    else if (x->gl_havewindow)
    	return (0);
    else
    {
	for (y = x->gl_list; y; y = y->g_next)
	{
    	    int x1, y1, x2, y2;
		/* check if the object wants to be clicked */
    	    if (canvas_hitbox(x, y, xpix, ypix, &x1, &y1, &x2, &y2)
		&&  (clickreturned = gobj_click(y, x, xpix, ypix,
		    shift, alt, 0, doit)))
    	    		break;
	}
	if (!doit)
	{
	    if (y)
		canvas_setcursor(glist_getcanvas(x), clickreturned);
	    else canvas_setcursor(glist_getcanvas(x), CURSOR_RUNMODE_NOTHING);
	}
	return (clickreturned);	
    }
}

void garray_properties(t_garray *x);

t_widgetbehavior graph_widgetbehavior =
{
    graph_getrect,
    graph_displace,
    graph_select,
    graph_activate,
    graph_delete,
    graph_vis,
    graph_click,
};

void graph_properties(t_gobj *z, t_glist *owner)
{
    t_glist *x = (t_glist *)z;
    {
	t_gobj *y;
#ifdef ROCKBOX
        (void) owner;
#else /* ROCKBOX */
	char graphbuf[200];

	sprintf(graphbuf, "pdtk_graph_dialog %%s %g %g %g %g %d %d\n",
	    x->gl_x1, x->gl_y1, x->gl_x2, x->gl_y2,
		x->gl_pixwidth, x->gl_pixheight);
	gfxstub_new(&x->gl_pd, x, graphbuf);
#endif /* ROCKBOX */

	for (y = x->gl_list; y; y = y->g_next)
    	    if (pd_class(&y->g_pd) == garray_class) 
		garray_properties((t_garray *)y);
    }
}

    /* find the graph most recently added to this glist;
    	if none exists, return 0. */

t_glist *glist_findgraph(t_glist *x)
{
    t_gobj *y = 0, *z;
    for (z = x->gl_list; z; z = z->g_next)
    	if (pd_class(&z->g_pd) == canvas_class && ((t_glist *)z)->gl_isgraph)
	    y = z;
    return ((t_glist *)y);
}

    /* message back from dialog GUI to set parameters.  Args are:
    	1-4: bounds in our coordinates; 5-6: size in parent */
static void graph_dialog(t_glist *x, t_symbol *s, int argc, t_atom *argv)
{
    t_float x1 = atom_getfloatarg(0, argc, argv);
    t_float y1 = atom_getfloatarg(1, argc, argv);
    t_float x2 = atom_getfloatarg(2, argc, argv);
    t_float y2 = atom_getfloatarg(3, argc, argv);
    t_float xpix = atom_getfloatarg(4, argc, argv);
    t_float ypix = atom_getfloatarg(5, argc, argv);
#ifdef ROCKBOX
    (void) s;
#endif
    if (x1 != x->gl_x1 || x2 != x->gl_x2 ||
    	y1 != x->gl_y1 || y2 != x->gl_y2)
    	    graph_bounds(x, x1, y1, x2, y2);
    if (xpix != x->gl_pixwidth || ypix != x->gl_pixheight)
    {
    	x->gl_pixwidth = xpix;
	x->gl_pixheight = ypix;
	glist_redraw(x);
	if (x->gl_owner)
	    canvas_fixlinesfor(x->gl_owner, &x->gl_obj);
    }
}

extern void canvas_menuarray(t_glist *canvas);

void g_graph_setup(void)
{
    class_setwidget(canvas_class, &graph_widgetbehavior);
    class_addmethod(canvas_class, (t_method)graph_bounds, gensym("bounds"),
    	A_FLOAT, A_FLOAT, A_FLOAT, A_FLOAT, 0);
    class_addmethod(canvas_class, (t_method)graph_xticks, gensym("xticks"),
    	A_FLOAT, A_FLOAT, A_FLOAT, 0);
    class_addmethod(canvas_class, (t_method)graph_xlabel, gensym("xlabel"),
    	A_GIMME, 0);
    class_addmethod(canvas_class, (t_method)graph_yticks, gensym("yticks"),
    	A_FLOAT, A_FLOAT, A_FLOAT, 0);
    class_addmethod(canvas_class, (t_method)graph_ylabel, gensym("ylabel"),
    	A_GIMME, 0);
    class_addmethod(canvas_class, (t_method)graph_array, gensym("array"),
    	A_SYMBOL, A_FLOAT, A_SYMBOL, A_DEFFLOAT, A_NULL);
    class_addmethod(canvas_class, (t_method)canvas_menuarray,
    	gensym("menuarray"), A_NULL);
    class_addmethod(canvas_class, (t_method)graph_dialog, gensym("dialog"),
    	A_GIMME, 0);
    class_addmethod(canvas_class, (t_method)glist_arraydialog,
    	gensym("arraydialog"), A_SYMBOL, A_FLOAT, A_FLOAT, A_FLOAT, A_NULL);
    class_addmethod(canvas_class, (t_method)glist_sort,
    	gensym("sort"), A_NULL);
}

