|  | #include	<u.h> | 
|  | #include	<libc.h> | 
|  | #include	<ctype.h> | 
|  | #include	<draw.h> | 
|  | #include	<event.h> | 
|  | #include	<cursor.h> | 
|  | #include	<stdio.h> | 
|  |  | 
|  | #define Never	0xffffffff	/* Maximum ulong */ | 
|  | #define LOG2  0.301029995664 | 
|  | #define Button_bit(b)	(1 << ((b)-1)) | 
|  |  | 
|  | enum { | 
|  | But1	= Button_bit(1),/* mouse buttons for events */ | 
|  | But2	= Button_bit(2), | 
|  | But3	= Button_bit(3) | 
|  | }; | 
|  | int cantmv = 1;			/* disallow rotate and move? 0..1 */ | 
|  | int top_border, bot_border, lft_border, rt_border; | 
|  | int lft_border0;		/* lft_border for y-axis labels >0 */ | 
|  | int top_left, top_right;	/* edges of top line free space */ | 
|  | int Mv_delay = 400;		/* msec for button click vs. button hold down */ | 
|  | int Dotrad = 2;			/* dot radius in pixels */ | 
|  | int framewd=1;			/* line thickness for frame (pixels) */ | 
|  | int framesep=1;			/* distance between frame and surrounding text */ | 
|  | int outersep=1;			/* distance: surrounding text to screen edge */ | 
|  | Point sdigit;			/* size of a digit in the font */ | 
|  | Point smaxch;			/* assume any character in font fits in this */ | 
|  | double underscan = .05;		/* fraction of frame initially unused per side */ | 
|  | double fuzz = 6;		/* selection tolerance in pixels */ | 
|  | int tick_len = 15;		/* length of axis label tick mark in pixels */ | 
|  | FILE* logfil = 0;		/* dump selected points here if nonzero */ | 
|  |  | 
|  | #define labdigs  3		/* allow this many sig digits in axis labels */ | 
|  | #define digs10pow 1000		/* pow(10,labdigs) */ | 
|  | #define axis_color  clr_im(DLtblue) | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  | /********************************* Utilities  *********************************/ | 
|  |  | 
|  | /* Prepend string s to null-terminated string in n-byte buffer buf[], truncating if | 
|  | necessary and using a space to separate s from the rest of buf[]. | 
|  | */ | 
|  | char* str_insert(char* buf, char* s, int n) | 
|  | { | 
|  | int blen, slen = strlen(s) + 1; | 
|  | if (slen >= n) | 
|  | {strncpy(buf,s,n); buf[n-1]='\0'; return buf;} | 
|  | blen = strlen(buf); | 
|  | if (blen >= n-slen) | 
|  | buf[blen=n-slen-1] = '\0'; | 
|  | memmove(buf+slen, buf, slen+blen+1); | 
|  | memcpy(buf, s, slen-1); | 
|  | buf[slen-1] = ' '; | 
|  | return buf; | 
|  | } | 
|  |  | 
|  | /* Alter string smain (without lengthening it) so as to remove the first occurrence of | 
|  | ssub, assuming ssub is ASCII.  Return nonzero (true) if string smain had to be changed. | 
|  | In spite of the ASCII-centric appearance, I think this can handle UTF in smain. | 
|  | */ | 
|  | int remove_substr(char* smain, char* ssub) | 
|  | { | 
|  | char *ss, *s = strstr(smain, ssub); | 
|  | int n = strlen(ssub); | 
|  | if (s==0) | 
|  | return 0; | 
|  | if (islower((uchar)s[n])) | 
|  | s[0] ^= 32;			/* probably tolower(s[0]) or toupper(s[0]) */ | 
|  | else { | 
|  | for (ss=s+n; *ss!=0; s++, ss++) | 
|  | *s = *ss; | 
|  | *s = '\0'; | 
|  | } | 
|  | return 1; | 
|  | } | 
|  |  | 
|  | void adjust_border(Font* f) | 
|  | { | 
|  | int sep = framesep + outersep; | 
|  | sdigit = stringsize(f, "8"); | 
|  | smaxch = stringsize(f, "MMMg"); | 
|  | smaxch.x = (smaxch.x + 3)/4; | 
|  | lft_border0 = (1+labdigs)*sdigit.x + framewd + sep; | 
|  | rt_border = (lft_border0 - sep)/2 + outersep; | 
|  | bot_border = sdigit.y + framewd + sep; | 
|  | top_border = smaxch.y + framewd + sep; | 
|  | lft_border = lft_border0;		/* this gets reset later */ | 
|  | } | 
|  |  | 
|  |  | 
|  | int is_off_screen(Point p) | 
|  | { | 
|  | const Rectangle* r = &(screen->r); | 
|  | return p.x-r->min.x<lft_border || r->max.x-p.x<rt_border | 
|  | || p.y-r->min.y<=top_border || r->max.y-p.y<=bot_border; | 
|  | } | 
|  |  | 
|  |  | 
|  | Cursor	bullseye = | 
|  | { | 
|  | {-7, -7}, | 
|  | { | 
|  | 0x1F, 0xF8, 0x3F, 0xFC, 0x7F, 0xFE, 0xFB, 0xDF, | 
|  | 0xF3, 0xCF, 0xE3, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF, | 
|  | 0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xC7, 0xF3, 0xCF, | 
|  | 0x7B, 0xDF, 0x7F, 0xFE, 0x3F, 0xFC, 0x1F, 0xF8, | 
|  | }, | 
|  | { | 
|  | 0x00, 0x00, 0x0F, 0xF0, 0x31, 0x8C, 0x21, 0x84, | 
|  | 0x41, 0x82, 0x41, 0x82, 0x41, 0x82, 0x7F, 0xFE, | 
|  | 0x7F, 0xFE, 0x41, 0x82, 0x41, 0x82, 0x41, 0x82, | 
|  | 0x21, 0x84, 0x31, 0x8C, 0x0F, 0xF0, 0x00, 0x00, | 
|  | } | 
|  | }; | 
|  |  | 
|  | int get_1click(int but, Mouse* m, Cursor* curs) | 
|  | { | 
|  | if (curs) | 
|  | esetcursor(curs); | 
|  | while (m->buttons==0) | 
|  | *m = emouse(); | 
|  | if (curs) | 
|  | esetcursor(0); | 
|  | return (m->buttons==Button_bit(but)); | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Wait until but goes up or until a mouse event's msec passes tlimit. | 
|  | Return a boolean result that tells whether the button went up. | 
|  | */ | 
|  | int lift_button(int but, Mouse* m, int tlimit) | 
|  | { | 
|  | do {	*m = emouse(); | 
|  | if (m->msec >= tlimit) | 
|  | return 0; | 
|  | } while (m->buttons & Button_bit(but)); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Set *m to the last pending mouse event, or the first one where but is up. | 
|  | If no mouse events are pending, wait for the next one. | 
|  | */ | 
|  | void latest_mouse(int but, Mouse* m) | 
|  | { | 
|  | int bbit = Button_bit(but); | 
|  | do {	*m = emouse(); | 
|  | } while ((m->buttons & bbit) && ecanmouse()); | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | /*********************************** Colors ***********************************/ | 
|  |  | 
|  | #define DOrange	0xFFAA00FF | 
|  | #define Dgray		0xBBBBBBFF | 
|  | #define DDkgreen	0x009900FF | 
|  | #define DDkred	0xCC0000FF | 
|  | #define DViolet		0x990099FF | 
|  | #define DDkyellow	0xAAAA00FF | 
|  | #define DLtblue	0xAAAAFFFF | 
|  | #define DPink		0xFFAAAAFF | 
|  |  | 
|  | /* draw.h sets DBlack, DBlue, DRed, DYellow, DGreen, | 
|  | DCyan, DMagenta, DWhite */ | 
|  |  | 
|  | typedef struct color_ref { | 
|  | ulong c;			/* RGBA pixel color */ | 
|  | char* nam;			/* ASCII name (matched to input, used in output)*/ | 
|  | Image* im;			/* replicated solid-color image */ | 
|  | } color_ref; | 
|  |  | 
|  | color_ref clrtab[] = { | 
|  | DRed,		"Red",		0, | 
|  | DPink,		"Pink",		0, | 
|  | DDkred,		"Dkred",	0, | 
|  | DOrange,	"Orange",	0, | 
|  | DYellow,	"Yellow",	0, | 
|  | DDkyellow,	"Dkyellow",	0, | 
|  | DGreen,		"Green",	0, | 
|  | DDkgreen,	"Dkgreen",	0, | 
|  | DCyan,		"Cyan",		0, | 
|  | DBlue,		"Blue",		0, | 
|  | DLtblue,	"Ltblue",	0, | 
|  | DMagenta,	"Magenta",	0, | 
|  | DViolet,	"Violet",	0, | 
|  | Dgray,		"Gray",		0, | 
|  | DBlack,		"Black",	0, | 
|  | DWhite,		"White",	0, | 
|  | DNofill,	0,		0	/* DNofill means "end of data" */ | 
|  | }; | 
|  |  | 
|  |  | 
|  | void  init_clrtab(void) | 
|  | { | 
|  | int i; | 
|  | Rectangle r = Rect(0,0,1,1); | 
|  | for (i=0; clrtab[i].c!=DNofill; i++) | 
|  | clrtab[i].im = allocimage(display, r, CMAP8, 1, clrtab[i].c); | 
|  | /* should check for 0 result? */ | 
|  | } | 
|  |  | 
|  |  | 
|  | int clrim_id(Image* clr) | 
|  | { | 
|  | int i; | 
|  | for (i=0; clrtab[i].im!=clr; i++) | 
|  | if (clrtab[i].c==DNofill) | 
|  | exits("bad image color"); | 
|  | return i; | 
|  | } | 
|  |  | 
|  | int clr_id(int clr) | 
|  | { | 
|  | int i; | 
|  | for (i=0; clrtab[i].c!=clr; i++) | 
|  | if (clrtab[i].c==DNofill) | 
|  | exits("bad color"); | 
|  | return i; | 
|  | } | 
|  |  | 
|  | #define clr_im(clr)	clrtab[clr_id(clr)].im | 
|  |  | 
|  |  | 
|  | /* This decides what color to use for a polyline based on the label it has in the | 
|  | input file.  Whichever color name comes first is the winner, otherwise return black. | 
|  | */ | 
|  | Image* nam2clr(const char* nam, int *idxdest) | 
|  | { | 
|  | char *c, *cbest=(char*)nam; | 
|  | int i, ibest=-1; | 
|  | if (*nam!=0) | 
|  | for (i=0; clrtab[i].nam!=0; i++) { | 
|  | c = strstr(nam,clrtab[i].nam); | 
|  | if (c!=0 && (ibest<0 || c<cbest)) | 
|  | {ibest=i; cbest=c;} | 
|  | } | 
|  | if (idxdest!=0) | 
|  | *idxdest = (ibest<0) ? clr_id(DBlack) : ibest; | 
|  | return (ibest<0) ? clr_im(DBlack) : clrtab[ibest].im; | 
|  | } | 
|  |  | 
|  | /* A polyline is initial drawn in thick mode iff its label in the file contains "Thick" */ | 
|  | int nam2thick(const char* nam) | 
|  | { | 
|  | return strstr(nam,"Thick")==0 ? 0 : 1; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Alter string nam so that nam2thick() and nam2clr() agree with th and clr, using | 
|  | buf[] (a buffer of length bufn) to store the result if it differs from nam. | 
|  | We go to great pains to perform this alteration in a manner that will seem natural | 
|  | to the user, i.e., we try removing a suitably isolated color name before inserting | 
|  | a new one. | 
|  | */ | 
|  | char* nam_with_thclr(char* nam, int th, Image* clr, char* buf, int bufn) | 
|  | { | 
|  | int clr0i, th0=nam2thick(nam); | 
|  | Image* clr0 = nam2clr(nam, &clr0i); | 
|  | char *clr0s; | 
|  | if (th0==th && clr0==clr) | 
|  | return nam; | 
|  | clr0s = clrtab[clr0i].nam; | 
|  | if (strlen(nam)<bufn) strcpy(buf,nam); | 
|  | else {strncpy(buf,nam,bufn); buf[bufn-1]='\0';} | 
|  | if (clr0 != clr) | 
|  | remove_substr(buf, clr0s); | 
|  | if (th0 > th) | 
|  | while (remove_substr(buf, "Thick")) | 
|  | /* do nothing */; | 
|  | if (nam2clr(buf,0) != clr) | 
|  | str_insert(buf, clrtab[clrim_id(clr)].nam, bufn); | 
|  | if (th0 < th) | 
|  | str_insert(buf, "Thick", bufn); | 
|  | return buf; | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | /****************************** Data structures  ******************************/ | 
|  |  | 
|  | Image* mv_bkgd;				/* Background image (usually 0) */ | 
|  |  | 
|  | typedef struct fpoint { | 
|  | double x, y; | 
|  | } fpoint; | 
|  |  | 
|  | typedef struct frectangle { | 
|  | fpoint min, max; | 
|  | } frectangle; | 
|  |  | 
|  | frectangle empty_frect = {1e30, 1e30, -1e30, -1e30}; | 
|  |  | 
|  |  | 
|  | /* When *r2 is transformed by y=y-x*slant, might it intersect *r1 ? | 
|  | */ | 
|  | int fintersects(const frectangle* r1, const frectangle* r2, double slant) | 
|  | { | 
|  | double x2min=r2->min.x, x2max=r2->max.x; | 
|  | if (r1->max.x <= x2min || x2max <= r1->min.x) | 
|  | return 0; | 
|  | if (slant >=0) | 
|  | {x2min*=slant; x2max*=slant;} | 
|  | else	{double t=x2min*slant; x2min=x2max*slant; x2max=t;} | 
|  | return r1->max.y > r2->min.y-x2max && r2->max.y-x2min > r1->min.y; | 
|  | } | 
|  |  | 
|  | int fcontains(const frectangle* r, fpoint p) | 
|  | { | 
|  | return r->min.x <=p.x && p.x<= r->max.x && r->min.y <=p.y && p.y<= r->max.y; | 
|  | } | 
|  |  | 
|  |  | 
|  | void grow_bb(frectangle* dest, const frectangle* r) | 
|  | { | 
|  | if (r->min.x < dest->min.x) dest->min.x=r->min.x; | 
|  | if (r->min.y < dest->min.y) dest->min.y=r->min.y; | 
|  | if (r->max.x > dest->max.x) dest->max.x=r->max.x; | 
|  | if (r->max.y > dest->max.y) dest->max.y=r->max.y; | 
|  | } | 
|  |  | 
|  |  | 
|  | void slant_frect(frectangle *r, double sl) | 
|  | { | 
|  | r->min.y += sl*r->min.x; | 
|  | r->max.y += sl*r->max.x; | 
|  | } | 
|  |  | 
|  |  | 
|  | fpoint fcenter(const frectangle* r) | 
|  | { | 
|  | fpoint c; | 
|  | c.x = .5*(r->max.x + r->min.x); | 
|  | c.y = .5*(r->max.y + r->min.y); | 
|  | return c; | 
|  | } | 
|  |  | 
|  |  | 
|  | typedef struct fpolygon { | 
|  | fpoint* p;			/* a malloc'ed array */ | 
|  | int n;				/* p[] has n elements: p[0..n] */ | 
|  | frectangle bb;			/* bounding box */ | 
|  | char* nam;			/* name of this polygon (malloc'ed) */ | 
|  | int thick;			/* use 1+2*thick pixel wide lines */ | 
|  | Image* clr;			/* Color to use when drawing this */ | 
|  | struct fpolygon* link; | 
|  | } fpolygon; | 
|  |  | 
|  | typedef struct fpolygons { | 
|  | fpolygon* p;			/* the head of a linked list */ | 
|  | frectangle bb;			/* overall bounding box */ | 
|  | frectangle disp;		/* part being mapped onto screen->r */ | 
|  | double slant_ht;		/* controls how disp is slanted */ | 
|  | } fpolygons; | 
|  |  | 
|  |  | 
|  | fpolygons univ = {			/* everything there is to display */ | 
|  | 0, | 
|  | 1e30, 1e30, -1e30, -1e30, | 
|  | 0, 0, 0, 0, | 
|  | 2*1e30 | 
|  | }; | 
|  |  | 
|  |  | 
|  | void set_default_clrs(fpolygons* fps, fpolygon* fpstop) | 
|  | { | 
|  | fpolygon* fp; | 
|  | for (fp=fps->p; fp!=0 && fp!=fpstop; fp=fp->link) { | 
|  | fp->clr = nam2clr(fp->nam,0); | 
|  | fp->thick = nam2thick(fp->nam); | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | void fps_invert(fpolygons* fps) | 
|  | { | 
|  | fpolygon *p, *r=0; | 
|  | for (p=fps->p; p!=0;) { | 
|  | fpolygon* q = p; | 
|  | p = p->link; | 
|  | q->link = r; | 
|  | r = q; | 
|  | } | 
|  | fps->p = r; | 
|  | } | 
|  |  | 
|  |  | 
|  | void fp_remove(fpolygons* fps, fpolygon* fp) | 
|  | { | 
|  | fpolygon *q, **p = &fps->p; | 
|  | while (*p!=fp) | 
|  | if (*p==0) | 
|  | return; | 
|  | else	p = &(*p)->link; | 
|  | *p = fp->link; | 
|  | fps->bb = empty_frect; | 
|  | for (q=fps->p; q!=0; q=q->link) | 
|  | grow_bb(&fps->bb, &q->bb); | 
|  | } | 
|  |  | 
|  |  | 
|  | /* The transform maps abstract fpoint coordinates (the ones used in the input) | 
|  | to the current screen coordinates.  The do_untransform() macros reverses this. | 
|  | If univ.slant_ht is not the height of univ.disp, the actual region in the | 
|  | abstract coordinates is a parallelogram inscribed in univ.disp with two | 
|  | vertical edges and two slanted slanted edges: slant_ht>0 means that the | 
|  | vertical edges have height slant_ht and the parallelogram touches the lower | 
|  | left and upper right corners of univ.disp; slant_ht<0 refers to a parallelogram | 
|  | of height -slant_ht that touches the other two corners of univ.disp. | 
|  | NOTE: the ytransform macro assumes that tr->sl times the x coordinate has | 
|  | already been subtracted from yy. | 
|  | */ | 
|  | typedef struct transform { | 
|  | double sl; | 
|  | fpoint o, sc;		/* (x,y):->(o.x+sc.x*x, o.y+sc.y*y+sl*x) */ | 
|  | } transform; | 
|  |  | 
|  | #define do_transform(d,tr,s)	((d)->x = (tr)->o.x + (tr)->sc.x*(s)->x,  \ | 
|  | (d)->y = (tr)->o.y + (tr)->sc.y*(s)->y    \ | 
|  | + (tr)->sl*(s)->x) | 
|  | #define do_untransform(d,tr,s)	((d)->x = (.5+(s)->x-(tr)->o.x)/(tr)->sc.x,    \ | 
|  | (d)->y = (.5+(s)->y-(tr)->sl*(d)->x-(tr)->o.y) \ | 
|  | /(tr)->sc.y) | 
|  | #define xtransform(tr,xx)	((tr)->o.x + (tr)->sc.x*(xx)) | 
|  | #define ytransform(tr,yy)	((tr)->o.y + (tr)->sc.y*(yy)) | 
|  | #define dxuntransform(tr,xx)	((xx)/(tr)->sc.x) | 
|  | #define dyuntransform(tr,yy)	((yy)/(tr)->sc.y) | 
|  |  | 
|  |  | 
|  | transform cur_trans(void) | 
|  | { | 
|  | transform t; | 
|  | Rectangle d = screen->r; | 
|  | const frectangle* s = &univ.disp; | 
|  | double sh = univ.slant_ht; | 
|  | d.min.x += lft_border; | 
|  | d.min.y += top_border; | 
|  | d.max.x -= rt_border; | 
|  | d.max.y -= bot_border; | 
|  | t.sc.x = (d.max.x - d.min.x)/(s->max.x - s->min.x); | 
|  | t.sc.y = -(d.max.y - d.min.y)/fabs(sh); | 
|  | if (sh > 0) { | 
|  | t.sl = -t.sc.y*(s->max.y-s->min.y-sh)/(s->max.x - s->min.x); | 
|  | t.o.y = d.min.y - t.sc.y*s->max.y - t.sl*s->max.x; | 
|  | } else { | 
|  | t.sl = t.sc.y*(s->max.y-s->min.y+sh)/(s->max.x - s->min.x); | 
|  | t.o.y = d.min.y - t.sc.y*s->max.y - t.sl*s->min.x; | 
|  | } | 
|  | t.o.x = d.min.x - t.sc.x*s->min.x; | 
|  | return t; | 
|  | } | 
|  |  | 
|  |  | 
|  | double u_slant_amt(fpolygons *u) | 
|  | { | 
|  | double sh=u->slant_ht, dy=u->disp.max.y - u->disp.min.y; | 
|  | double dx = u->disp.max.x - u->disp.min.x; | 
|  | return (sh>0) ? (dy-sh)/dx : -(dy+sh)/dx; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Set *y0 and *y1 to the lower and upper bounds of the set of y-sl*x values that | 
|  | *u says to display, where sl is the amount of slant. | 
|  | */ | 
|  | double set_unslanted_y(fpolygons *u, double *y0, double *y1) | 
|  | { | 
|  | double yy1, sl=u_slant_amt(u); | 
|  | if (u->slant_ht > 0) { | 
|  | *y0 = u->disp.min.y - sl*u->disp.min.x; | 
|  | yy1 = *y0 + u->slant_ht; | 
|  | } else { | 
|  | yy1 = u->disp.max.y - sl*u->disp.min.x; | 
|  | *y0 = yy1 + u->slant_ht; | 
|  | } | 
|  | if (y1 != 0) | 
|  | *y1 = yy1; | 
|  | return sl; | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  | /*************************** The region to display ****************************/ | 
|  |  | 
|  | void nontrivial_interval(double *lo, double *hi) | 
|  | { | 
|  | if (*lo >= *hi) { | 
|  | double mid = .5*(*lo + *hi); | 
|  | double tweak = 1e-6 + 1e-6*fabs(mid); | 
|  | *lo = mid - tweak; | 
|  | *hi = mid + tweak; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | void init_disp(void) | 
|  | { | 
|  | double dw = (univ.bb.max.x - univ.bb.min.x)*underscan; | 
|  | double dh = (univ.bb.max.y - univ.bb.min.y)*underscan; | 
|  | univ.disp.min.x = univ.bb.min.x - dw; | 
|  | univ.disp.min.y = univ.bb.min.y - dh; | 
|  | univ.disp.max.x = univ.bb.max.x + dw; | 
|  | univ.disp.max.y = univ.bb.max.y + dh; | 
|  | nontrivial_interval(&univ.disp.min.x, &univ.disp.max.x); | 
|  | nontrivial_interval(&univ.disp.min.y, &univ.disp.max.y); | 
|  | univ.slant_ht = univ.disp.max.y - univ.disp.min.y;	/* means no slant */ | 
|  | } | 
|  |  | 
|  |  | 
|  | void recenter_disp(Point c) | 
|  | { | 
|  | transform tr = cur_trans(); | 
|  | fpoint cc, off; | 
|  | do_untransform(&cc, &tr, &c); | 
|  | off.x = cc.x - .5*(univ.disp.min.x + univ.disp.max.x); | 
|  | off.y = cc.y - .5*(univ.disp.min.y + univ.disp.max.y); | 
|  | univ.disp.min.x += off.x; | 
|  | univ.disp.min.y += off.y; | 
|  | univ.disp.max.x += off.x; | 
|  | univ.disp.max.y += off.y; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Find the upper-left and lower-right corners of the bounding box of the | 
|  | parallelogram formed by untransforming the rectangle rminx, rminy, ... (given | 
|  | in screen coordinates), and return the height of the parallelogram (negated | 
|  | if it slopes downward). | 
|  | */ | 
|  | double untransform_corners(double rminx, double rminy, double rmaxx, double rmaxy, | 
|  | fpoint *ul, fpoint *lr) | 
|  | { | 
|  | fpoint r_ur, r_ul, r_ll, r_lr;	/* corners of the given recangle */ | 
|  | fpoint ur, ll;			/* untransformed versions of r_ur, r_ll */ | 
|  | transform tr = cur_trans(); | 
|  | double ht; | 
|  | r_ur.x=rmaxx;  r_ur.y=rminy; | 
|  | r_ul.x=rminx;  r_ul.y=rminy; | 
|  | r_ll.x=rminx;  r_ll.y=rmaxy; | 
|  | r_lr.x=rmaxx;  r_lr.y=rmaxy; | 
|  | do_untransform(ul, &tr, &r_ul); | 
|  | do_untransform(lr, &tr, &r_lr); | 
|  | do_untransform(&ur, &tr, &r_ur); | 
|  | do_untransform(&ll, &tr, &r_ll); | 
|  | ht = ur.y - lr->y; | 
|  | if (ll.x < ul->x) | 
|  | ul->x = ll.x; | 
|  | if (ur.y > ul->y) | 
|  | ul->y = ur.y; | 
|  | else	ht = -ht; | 
|  | if (ur.x > lr->x) | 
|  | lr->x = ur.x; | 
|  | if (ll.y < lr->y) | 
|  | lr->y = ll.y; | 
|  | return ht; | 
|  | } | 
|  |  | 
|  |  | 
|  | void disp_dozoom(double rminx, double rminy, double rmaxx, double rmaxy) | 
|  | { | 
|  | fpoint ul, lr; | 
|  | double sh = untransform_corners(rminx, rminy, rmaxx, rmaxy, &ul, &lr); | 
|  | if (ul.x==lr.x || ul.y==lr.y) | 
|  | return; | 
|  | univ.slant_ht = sh; | 
|  | univ.disp.min.x = ul.x; | 
|  | univ.disp.max.y = ul.y; | 
|  | univ.disp.max.x = lr.x; | 
|  | univ.disp.min.y = lr.y; | 
|  | nontrivial_interval(&univ.disp.min.x, &univ.disp.max.x); | 
|  | nontrivial_interval(&univ.disp.min.y, &univ.disp.max.y); | 
|  | } | 
|  |  | 
|  |  | 
|  | void disp_zoomin(Rectangle r) | 
|  | { | 
|  | disp_dozoom(r.min.x, r.min.y, r.max.x, r.max.y); | 
|  | } | 
|  |  | 
|  |  | 
|  | void disp_zoomout(Rectangle r) | 
|  | { | 
|  | double qminx, qminy, qmaxx, qmaxy; | 
|  | double scx, scy; | 
|  | Rectangle s = screen->r; | 
|  | if (r.min.x==r.max.x || r.min.y==r.max.y) | 
|  | return; | 
|  | s.min.x += lft_border; | 
|  | s.min.y += top_border; | 
|  | s.max.x -= rt_border; | 
|  | s.max.y -= bot_border; | 
|  | scx = (s.max.x - s.min.x)/(r.max.x - r.min.x); | 
|  | scy = (s.max.y - s.min.y)/(r.max.y - r.min.y); | 
|  | qminx = s.min.x + scx*(s.min.x - r.min.x); | 
|  | qmaxx = s.max.x + scx*(s.max.x - r.max.x); | 
|  | qminy = s.min.y + scy*(s.min.y - r.min.y); | 
|  | qmaxy = s.max.y + scy*(s.max.y - r.max.y); | 
|  | disp_dozoom(qminx, qminy, qmaxx, qmaxy); | 
|  | } | 
|  |  | 
|  |  | 
|  | void expand2(double* a, double* b, double f) | 
|  | { | 
|  | double mid = .5*(*a + *b); | 
|  | *a = mid + f*(*a - mid); | 
|  | *b = mid + f*(*b - mid); | 
|  | } | 
|  |  | 
|  | void disp_squareup(void) | 
|  | { | 
|  | double dx = univ.disp.max.x - univ.disp.min.x; | 
|  | double dy = univ.disp.max.y - univ.disp.min.y; | 
|  | dx /= screen->r.max.x - lft_border - screen->r.min.x - rt_border; | 
|  | dy /= screen->r.max.y - bot_border - screen->r.min.y - top_border; | 
|  | if (dx > dy) | 
|  | expand2(&univ.disp.min.y, &univ.disp.max.y, dx/dy); | 
|  | else	expand2(&univ.disp.min.x, &univ.disp.max.x, dy/dx); | 
|  | univ.slant_ht = univ.disp.max.y - univ.disp.min.y; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Slant so that p and q appear at the same height on the screen and the | 
|  | screen contains the smallest possible superset of what its previous contents. | 
|  | */ | 
|  | void slant_disp(fpoint p, fpoint q) | 
|  | { | 
|  | double yll, ylr, yul, yur;	/* corner y coords of displayed parallelogram */ | 
|  | double sh, dy; | 
|  | if (p.x == q.x) | 
|  | return; | 
|  | sh = univ.slant_ht; | 
|  | if (sh > 0) { | 
|  | yll=yul=univ.disp.min.y;  yul+=sh; | 
|  | ylr=yur=univ.disp.max.y;  ylr-=sh; | 
|  | } else { | 
|  | yll=yul=univ.disp.max.y;  yll+=sh; | 
|  | ylr=yur=univ.disp.min.y;  yur-=sh; | 
|  | } | 
|  | dy = (univ.disp.max.x-univ.disp.min.x)*(q.y - p.y)/(q.x - p.x); | 
|  | dy -= ylr - yll; | 
|  | if (dy > 0) | 
|  | {yll-=dy; yur+=dy;} | 
|  | else	{yul-=dy; ylr+=dy;} | 
|  | if (ylr > yll) { | 
|  | univ.disp.min.y = yll; | 
|  | univ.disp.max.y = yur; | 
|  | univ.slant_ht = yur - ylr; | 
|  | } else { | 
|  | univ.disp.max.y = yul; | 
|  | univ.disp.min.y = ylr; | 
|  | univ.slant_ht = ylr - yur; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  | /******************************** Ascii input  ********************************/ | 
|  |  | 
|  | void set_fbb(fpolygon* fp) | 
|  | { | 
|  | fpoint lo=fp->p[0], hi=fp->p[0]; | 
|  | const fpoint *q, *qtop; | 
|  | for (qtop=(q=fp->p)+fp->n; ++q<=qtop;) { | 
|  | if (q->x < lo.x) lo.x=q->x; | 
|  | if (q->y < lo.y) lo.y=q->y; | 
|  | if (q->x > hi.x) hi.x=q->x; | 
|  | if (q->y > hi.y) hi.y=q->y; | 
|  | } | 
|  | fp->bb.min = lo; | 
|  | fp->bb.max = hi; | 
|  | } | 
|  |  | 
|  | char* mystrdup(char* s) | 
|  | { | 
|  | char *r, *t = strrchr(s,'"'); | 
|  | if (t==0) { | 
|  | t = s + strlen(s); | 
|  | while (t>s && (t[-1]=='\n' || t[-1]=='\r')) | 
|  | t--; | 
|  | } | 
|  | r = malloc(1+(t-s)); | 
|  | memcpy(r, s, t-s); | 
|  | r[t-s] = 0; | 
|  | return r; | 
|  | } | 
|  |  | 
|  | int is_valid_label(char* lab) | 
|  | { | 
|  | char* t; | 
|  | if (lab[0]=='"') | 
|  | return (t=strrchr(lab,'"'))!=0 && t!=lab && strspn(t+1," \t\r\n")==strlen(t+1); | 
|  | return strcspn(lab," \t")==strlen(lab); | 
|  | } | 
|  |  | 
|  | /* Read a polyline and update the number of lines read.  A zero result indicates bad | 
|  | syntax if *lineno increases; otherwise it indicates end of file. | 
|  | */ | 
|  | fpolygon* rd_fpoly(FILE* fin, int *lineno) | 
|  | { | 
|  | char buf[1024], junk[2]; | 
|  | fpoint q; | 
|  | fpolygon* fp; | 
|  | int allocn; | 
|  | if (!fgets(buf,sizeof buf,fin)) | 
|  | return 0; | 
|  | (*lineno)++; | 
|  | if (sscanf(buf,"%lg%lg%1s",&q.x,&q.y,junk) != 2) | 
|  | return 0; | 
|  | fp = malloc(sizeof(fpolygon)); | 
|  | allocn = 16; | 
|  | fp->p = malloc(allocn*sizeof(fpoint)); | 
|  | fp->p[0] = q; | 
|  | fp->n = 0; | 
|  | fp->nam = ""; | 
|  | fp->thick = 0; | 
|  | fp->clr = clr_im(DBlack); | 
|  | while (fgets(buf,sizeof buf,fin)) { | 
|  | (*lineno)++; | 
|  | if (sscanf(buf,"%lg%lg%1s",&q.x,&q.y,junk) != 2) { | 
|  | if (!is_valid_label(buf)) | 
|  | {free(fp->p); free(fp); return 0;} | 
|  | fp->nam = (buf[0]=='"') ? buf+1 : buf; | 
|  | break; | 
|  | } | 
|  | if (++(fp->n) == allocn) | 
|  | fp->p = realloc(fp->p, (allocn<<=1)*sizeof(fpoint)); | 
|  | fp->p[fp->n] = q; | 
|  | } | 
|  | fp->nam = mystrdup(fp->nam); | 
|  | set_fbb(fp); | 
|  | fp->link = 0; | 
|  | return fp; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Read input into *fps and return 0 or a line number where there's a syntax error */ | 
|  | int rd_fpolys(FILE* fin, fpolygons* fps) | 
|  | { | 
|  | fpolygon *fp, *fp0=fps->p; | 
|  | int lineno=0, ok_upto=0; | 
|  | while ((fp=rd_fpoly(fin,&lineno)) != 0) { | 
|  | ok_upto = lineno; | 
|  | fp->link = fps->p; | 
|  | fps->p = fp; | 
|  | grow_bb(&fps->bb, &fp->bb); | 
|  | } | 
|  | set_default_clrs(fps, fp0); | 
|  | return (ok_upto==lineno) ? 0 : lineno; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Read input from file fnam and return an error line no., -1 for "can't open" | 
|  | or 0 for success. | 
|  | */ | 
|  | int doinput(char* fnam) | 
|  | { | 
|  | FILE* fin = strcmp(fnam,"-")==0 ? stdin : fopen(fnam, "r"); | 
|  | int errline_or0; | 
|  | if (fin==0) | 
|  | return -1; | 
|  | errline_or0 = rd_fpolys(fin, &univ); | 
|  | fclose(fin); | 
|  | return errline_or0; | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | /******************************** Ascii output ********************************/ | 
|  |  | 
|  | fpolygon* fp_reverse(fpolygon* fp) | 
|  | { | 
|  | fpolygon* r = 0; | 
|  | while (fp!=0) { | 
|  | fpolygon* q = fp->link; | 
|  | fp->link = r; | 
|  | r = fp; | 
|  | fp = q; | 
|  | } | 
|  | return r; | 
|  | } | 
|  |  | 
|  | void wr_fpoly(FILE* fout, const fpolygon* fp) | 
|  | { | 
|  | char buf[1024]; | 
|  | int i; | 
|  | for (i=0; i<=fp->n; i++) | 
|  | fprintf(fout,"%.12g\t%.12g\n", fp->p[i].x, fp->p[i].y); | 
|  | fprintf(fout,"\"%s\"\n", nam_with_thclr(fp->nam, fp->thick, fp->clr, buf, 256)); | 
|  | } | 
|  |  | 
|  | void wr_fpolys(FILE* fout, fpolygons* fps) | 
|  | { | 
|  | fpolygon* fp; | 
|  | fps->p = fp_reverse(fps->p); | 
|  | for (fp=fps->p; fp!=0; fp=fp->link) | 
|  | wr_fpoly(fout, fp); | 
|  | fps->p = fp_reverse(fps->p); | 
|  | } | 
|  |  | 
|  |  | 
|  | int dooutput(char* fnam) | 
|  | { | 
|  | FILE* fout = fopen(fnam, "w"); | 
|  | if (fout==0) | 
|  | return 0; | 
|  | wr_fpolys(fout, &univ); | 
|  | fclose(fout); | 
|  | return 1; | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  | /************************ Clipping to screen rectangle ************************/ | 
|  |  | 
|  | /* Find the t values, 0<=t<=1 for which x0+t*(x1-x0) is between xlo and xhi, | 
|  | or return 0 to indicate no such t values exist.  If returning 1, set *t0 and | 
|  | *t1 to delimit the t interval. | 
|  | */ | 
|  | int do_xory(double x0, double x1, double xlo, double xhi, double* t0, double* t1) | 
|  | { | 
|  | *t1 = 1.0; | 
|  | if (x0<xlo) { | 
|  | if (x1<xlo) return 0; | 
|  | *t0 = (xlo-x0)/(x1-x0); | 
|  | if (x1>xhi) *t1 = (xhi-x0)/(x1-x0); | 
|  | } else if (x0>xhi) { | 
|  | if (x1>xhi) return 0; | 
|  | *t0 = (xhi-x0)/(x1-x0); | 
|  | if (x1<xlo) *t1 = (xlo-x0)/(x1-x0); | 
|  | } else { | 
|  | *t0 = 0.0; | 
|  | if (x1>xhi) *t1 = (xhi-x0)/(x1-x0); | 
|  | else if (x1<xlo) *t1 = (xlo-x0)/(x1-x0); | 
|  | else *t1 = 1.0; | 
|  | } | 
|  | return 1; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* After mapping y to y-slope*x, what initial fraction of the *p to *q edge is | 
|  | outside of *r?  Note that the edge could start outside *r, pass through *r, | 
|  | and wind up outside again. | 
|  | */ | 
|  | double frac_outside(const fpoint* p, const fpoint* q, const frectangle* r, | 
|  | double slope) | 
|  | { | 
|  | double t0, t1, tt0, tt1; | 
|  | double px=p->x, qx=q->x; | 
|  | if (!do_xory(px, qx, r->min.x, r->max.x, &t0, &t1)) | 
|  | return 1; | 
|  | if (!do_xory(p->y-slope*px, q->y-slope*qx, r->min.y, r->max.y, &tt0, &tt1)) | 
|  | return 1; | 
|  | if (tt0 > t0) | 
|  | t0 = tt0; | 
|  | if (t1<=t0 || tt1<=t0) | 
|  | return 1; | 
|  | return t0; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Think of p0..pn as piecewise-linear function F(t) for t=0..pn-p0, and find | 
|  | the maximum tt such that F(0..tt) is all inside of r, assuming p0 is inside. | 
|  | Coordinates are transformed by y=y-x*slope before testing against r. | 
|  | */ | 
|  | double in_length(const fpoint* p0, const fpoint* pn, frectangle r, double slope) | 
|  | { | 
|  | const fpoint* p = p0; | 
|  | double px, py; | 
|  | do if (++p > pn) | 
|  | return pn - p0; | 
|  | while (r.min.x<=(px=p->x) && px<=r.max.x | 
|  | && r.min.y<=(py=p->y-slope*px) && py<=r.max.y); | 
|  | return (p - p0) - frac_outside(p, p-1, &r, slope); | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Think of p0..pn as piecewise-linear function F(t) for t=0..pn-p0, and find | 
|  | the maximum tt such that F(0..tt) is all outside of *r.  Coordinates are | 
|  | transformed by y=y-x*slope before testing against r. | 
|  | */ | 
|  | double out_length(const fpoint* p0, const fpoint* pn, frectangle r, double slope) | 
|  | { | 
|  | const fpoint* p = p0; | 
|  | double fr; | 
|  | do {	if (p->x < r.min.x) | 
|  | do if (++p>pn) return pn-p0; | 
|  | while (p->x <= r.min.x); | 
|  | else if (p->x > r.max.x) | 
|  | do if (++p>pn) return pn-p0; | 
|  | while (p->x >= r.max.x); | 
|  | else if (p->y-slope*p->x < r.min.y) | 
|  | do if (++p>pn) return pn-p0; | 
|  | while (p->y-slope*p->x <= r.min.y); | 
|  | else if (p->y-slope*p->x > r.max.y) | 
|  | do if (++p>pn) return pn-p0; | 
|  | while (p->y-slope*p->x >= r.max.y); | 
|  | else return p - p0; | 
|  | } while ((fr=frac_outside(p-1,p,&r,slope)) == 1); | 
|  | return (p - p0) + fr-1; | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | /*********************** Drawing frame and axis labels  ***********************/ | 
|  |  | 
|  | #define Nthous  7 | 
|  | #define Len_thous  30			/* bound on strlen(thous_nam[i]) */ | 
|  | char* thous_nam[Nthous] = { | 
|  | "one", "thousand", "million", "billion", | 
|  | "trillion", "quadrillion", "quintillion" | 
|  | }; | 
|  |  | 
|  |  | 
|  | typedef struct lab_interval { | 
|  | double sep;			/* separation between tick marks */ | 
|  | double unit;		/* power of 1000 divisor */ | 
|  | int logunit;		/* log base 1000 of of this divisor */ | 
|  | double off;			/* offset to subtract before dividing */ | 
|  | } lab_interval; | 
|  |  | 
|  |  | 
|  | char* abbrev_num(double x, const lab_interval* iv) | 
|  | { | 
|  | static char buf[16]; | 
|  | double dx = x - iv->off; | 
|  | dx = iv->sep * floor(dx/iv->sep + .5); | 
|  | sprintf(buf,"%g", dx/iv->unit); | 
|  | return buf; | 
|  | } | 
|  |  | 
|  |  | 
|  | double lead_digits(double n, double r)	/* n truncated to power of 10 above r */ | 
|  | { | 
|  | double rr = pow(10, ceil(log10(r))); | 
|  | double nn = (n<rr) ? 0.0 : rr*floor(n/rr); | 
|  | if (n+r-nn >= digs10pow) { | 
|  | rr /= 10; | 
|  | nn = (n<rr) ? 0.0 : rr*floor(n/rr); | 
|  | } | 
|  | return nn; | 
|  | } | 
|  |  | 
|  |  | 
|  | lab_interval next_larger(double s0, double xlo, double xhi) | 
|  | { | 
|  | double nlo, nhi; | 
|  | lab_interval r; | 
|  | r.logunit = (int) floor(log10(s0) + LOG2); | 
|  | r.unit = pow(10, r.logunit); | 
|  | nlo = xlo/r.unit; | 
|  | nhi = xhi/r.unit; | 
|  | if (nhi >= digs10pow) | 
|  | r.off = r.unit*lead_digits(nlo, nhi-nlo); | 
|  | else if (nlo <= -digs10pow) | 
|  | r.off = -r.unit*lead_digits(-nhi, nhi-nlo); | 
|  | else	r.off = 0; | 
|  | r.sep = (s0<=r.unit) ? r.unit : (s0<2*r.unit ? 2*r.unit : 5*r.unit); | 
|  | switch (r.logunit%3) { | 
|  | case 1:	r.unit*=.1; r.logunit--; | 
|  | break; | 
|  | case -1: case 2: | 
|  | r.unit*=10; r.logunit++; | 
|  | break; | 
|  | case -2: r.unit*=100; r.logunit+=2; | 
|  | } | 
|  | r.logunit /= 3; | 
|  | return r; | 
|  | } | 
|  |  | 
|  |  | 
|  | double min_hsep(const transform* tr) | 
|  | { | 
|  | double s = (2+labdigs)*sdigit.x; | 
|  | double ss = (univ.disp.min.x<0) ? s+sdigit.x : s; | 
|  | return dxuntransform(tr, ss); | 
|  | } | 
|  |  | 
|  |  | 
|  | lab_interval mark_x_axis(const transform* tr) | 
|  | { | 
|  | fpoint p = univ.disp.min; | 
|  | Point q, qtop, qbot, tmp; | 
|  | double x0=univ.disp.min.x, x1=univ.disp.max.x; | 
|  | double seps0, nseps, seps; | 
|  | lab_interval iv = next_larger(min_hsep(tr), x0, x1); | 
|  | set_unslanted_y(&univ, &p.y, 0); | 
|  | q.y = ytransform(tr, p.y) + .5; | 
|  | qtop.y = q.y - tick_len; | 
|  | qbot.y = q.y + framewd + framesep; | 
|  | seps0 = ceil(x0/iv.sep); | 
|  | for (seps=0, nseps=floor(x1/iv.sep)-seps0; seps<=nseps; seps+=1) { | 
|  | char* num = abbrev_num((p.x=iv.sep*(seps0+seps)), &iv); | 
|  | Font* f = display->defaultfont; | 
|  | q.x = qtop.x = qbot.x = xtransform(tr, p.x); | 
|  | line(screen, qtop, q, Enddisc, Enddisc, 0, axis_color, q); | 
|  | tmp = stringsize(f, num); | 
|  | qbot.x -= tmp.x/2; | 
|  | string(screen, qbot, display->black, qbot, f, num); | 
|  | } | 
|  | return iv; | 
|  | } | 
|  |  | 
|  |  | 
|  | lab_interval mark_y_axis(const transform* tr) | 
|  | { | 
|  | Font* f = display->defaultfont; | 
|  | fpoint p = univ.disp.min; | 
|  | Point q, qrt, qlft; | 
|  | double y0, y1, seps0, nseps, seps; | 
|  | lab_interval iv; | 
|  | set_unslanted_y(&univ, &y0, &y1); | 
|  | iv = next_larger(dyuntransform(tr,-f->height), y0, y1); | 
|  | q.x = xtransform(tr, p.x) - .5; | 
|  | qrt.x = q.x + tick_len; | 
|  | qlft.x = q.x - (framewd + framesep); | 
|  | seps0 = ceil(y0/iv.sep); | 
|  | for (seps=0, nseps=floor(y1/iv.sep)-seps0; seps<=nseps; seps+=1) { | 
|  | char* num = abbrev_num((p.y=iv.sep*(seps0+seps)), &iv); | 
|  | Point qq = stringsize(f, num); | 
|  | q.y = qrt.y = qlft.y = ytransform(tr, p.y); | 
|  | line(screen, qrt, q, Enddisc, Enddisc, 0, axis_color, q); | 
|  | qq.x = qlft.x - qq.x; | 
|  | qq.y = qlft.y - qq.y/2; | 
|  | string(screen, qq, display->black, qq, f, num); | 
|  | } | 
|  | return iv; | 
|  | } | 
|  |  | 
|  |  | 
|  | void lab_iv_info(const lab_interval *iv, double slant, char* buf, int *n) | 
|  | { | 
|  | if (iv->off > 0) | 
|  | (*n) += sprintf(buf+*n,"-%.12g",iv->off); | 
|  | else if (iv->off < 0) | 
|  | (*n) += sprintf(buf+*n,"+%.12g",-iv->off); | 
|  | if (slant>0) | 
|  | (*n) += sprintf(buf+*n,"-%.6gx", slant); | 
|  | else if (slant<0) | 
|  | (*n) += sprintf(buf+*n,"+%.6gx", -slant); | 
|  | if (abs(iv->logunit) >= Nthous) | 
|  | (*n) += sprintf(buf+*n," in 1e%d units", 3*iv->logunit); | 
|  | else if (iv->logunit > 0) | 
|  | (*n) += sprintf(buf+*n," in %ss", thous_nam[iv->logunit]); | 
|  | else if (iv->logunit < 0) | 
|  | (*n) += sprintf(buf+*n," in %sths", thous_nam[-iv->logunit]); | 
|  | } | 
|  |  | 
|  |  | 
|  | void draw_xy_ranges(const lab_interval *xiv, const lab_interval *yiv) | 
|  | { | 
|  | Point p; | 
|  | char buf[2*(19+Len_thous+8)+50]; | 
|  | int bufn = 0; | 
|  | buf[bufn++] = 'x'; | 
|  | lab_iv_info(xiv, 0, buf, &bufn); | 
|  | bufn += sprintf(buf+bufn, "; y"); | 
|  | lab_iv_info(yiv, u_slant_amt(&univ), buf, &bufn); | 
|  | buf[bufn] = '\0'; | 
|  | p = stringsize(display->defaultfont, buf); | 
|  | top_left = screen->r.min.x + lft_border; | 
|  | p.x = top_right = screen->r.max.x - rt_border - p.x; | 
|  | p.y = screen->r.min.y + outersep; | 
|  | string(screen, p, display->black, p, display->defaultfont, buf); | 
|  | } | 
|  |  | 
|  |  | 
|  | transform draw_frame(void) | 
|  | { | 
|  | lab_interval x_iv, y_iv; | 
|  | transform tr; | 
|  | Rectangle r = screen->r; | 
|  | lft_border = (univ.disp.min.y<0) ? lft_border0+sdigit.x : lft_border0; | 
|  | tr = cur_trans(); | 
|  | r.min.x += lft_border; | 
|  | r.min.y += top_border; | 
|  | r.max.x -= rt_border; | 
|  | r.max.y -= bot_border; | 
|  | border(screen, r, -framewd, axis_color, r.min); | 
|  | x_iv = mark_x_axis(&tr); | 
|  | y_iv = mark_y_axis(&tr); | 
|  | draw_xy_ranges(&x_iv, &y_iv); | 
|  | return tr; | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | /*************************** Finding the selection  ***************************/ | 
|  |  | 
|  | typedef struct pt_on_fpoly { | 
|  | fpoint p;			/* the point */ | 
|  | fpolygon* fp;			/* the fpolygon it lies on */ | 
|  | double t;			/* how many knots from the beginning */ | 
|  | } pt_on_fpoly; | 
|  |  | 
|  |  | 
|  | static double myx, myy; | 
|  | #define mydist(p,o,sl,xwt,ywt)	(myx=(p).x-(o).x, myy=(p).y-sl*(p).x-(o).y,	\ | 
|  | xwt*myx*myx + ywt*myy*myy) | 
|  |  | 
|  | /* At what fraction of the way from p0[0] to p0[1] is mydist(p,ctr,slant,xwt,ywt) | 
|  | minimized? | 
|  | */ | 
|  | double closest_time(const fpoint* p0, const fpoint* ctr, double slant, | 
|  | double xwt, double ywt) | 
|  | { | 
|  | double p00y=p0[0].y-slant*p0[0].x, p01y=p0[1].y-slant*p0[1].x; | 
|  | double dx=p0[1].x-p0[0].x, dy=p01y-p00y; | 
|  | double x0=p0[0].x-ctr->x, y0=p00y-ctr->y; | 
|  | double bot = xwt*dx*dx + ywt*dy*dy; | 
|  | if (bot==0) | 
|  | return 0; | 
|  | return -(xwt*x0*dx + ywt*y0*dy)/bot; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Scan the polygonal path of length len knots starting at p0, and find the | 
|  | point that the transformation y=y-x*slant makes closest to the center of *r, | 
|  | where *r itself defines the distance metric.  Knots get higher priority than | 
|  | points between knots.  If psel->t is negative, always update *psel; otherwise | 
|  | update *psel only if the scan can improve it.  Return a boolean that says | 
|  | whether *psel was updated. | 
|  | Note that *r is a very tiny rectangle (tiny when converted screen pixels) | 
|  | such that anything in *r is considered close enough to match the mouse click. | 
|  | The purpose of this routine is to be careful in case there is a lot of hidden | 
|  | detail in the tiny rectangle *r. | 
|  | */ | 
|  | int improve_pt(fpoint* p0, double len, const frectangle* r, double slant, | 
|  | pt_on_fpoly* psel) | 
|  | { | 
|  | fpoint ctr = fcenter(r); | 
|  | double x_wt=2/(r->max.x-r->min.x), y_wt=2/(r->max.y-r->min.y); | 
|  | double xwt=x_wt*x_wt, ywt=y_wt*y_wt; | 
|  | double d, dbest = (psel->t <0) ? 1e30 : mydist(psel->p,ctr,slant,xwt,ywt); | 
|  | double tt, dbest0 = dbest; | 
|  | fpoint pp; | 
|  | int ilen = (int) len; | 
|  | if (len==0 || ilen>0) { | 
|  | int i; | 
|  | for (i=(len==0 ? 0 : 1); i<=ilen; i++) { | 
|  | d = mydist(p0[i], ctr, slant, xwt, ywt); | 
|  | if (d < dbest) | 
|  | {psel->p=p0[i]; psel->t=i; dbest=d;} | 
|  | } | 
|  | return (dbest < dbest0); | 
|  | } | 
|  | tt = closest_time(p0, &ctr, slant, xwt, ywt); | 
|  | if (tt > len) | 
|  | tt = len; | 
|  | pp.x = p0[0].x + tt*(p0[1].x - p0[0].x); | 
|  | pp.y = p0[0].y + tt*(p0[1].y - p0[0].y); | 
|  | if (mydist(pp, ctr, slant, xwt, ywt) < dbest) { | 
|  | psel->p = pp; | 
|  | psel->t = tt; | 
|  | return 1; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Test *fp against *r after transforming by y=y-x*slope, and set *psel accordingly. | 
|  | */ | 
|  | void select_in_fpoly(fpolygon* fp, const frectangle* r, double slant, | 
|  | pt_on_fpoly* psel) | 
|  | { | 
|  | fpoint *p0=fp->p, *pn=fp->p+fp->n; | 
|  | double l1, l2; | 
|  | if (p0==pn) | 
|  | {improve_pt(p0, 0, r, slant, psel); psel->fp=fp; return;} | 
|  | while ((l1=out_length(p0,pn,*r,slant)) < pn-p0) { | 
|  | fpoint p0sav; | 
|  | int i1 = (int) l1; | 
|  | p0+=i1; l1-=i1; | 
|  | p0sav = *p0; | 
|  | p0[0].x += l1*(p0[1].x - p0[0].x); | 
|  | p0[0].y += l1*(p0[1].y - p0[0].y); | 
|  | l2 = in_length(p0, pn, *r, slant); | 
|  | if (improve_pt(p0, l2, r, slant, psel)) { | 
|  | if (l1==0 && psel->t!=((int) psel->t)) { | 
|  | psel->t = 0; | 
|  | psel->p = *p0; | 
|  | } else if (psel->t < 1) | 
|  | psel->t += l1*(1 - psel->t); | 
|  | psel->t += p0 - fp->p; | 
|  | psel->fp = fp; | 
|  | } | 
|  | *p0 = p0sav; | 
|  | p0 += (l2>0) ? ((int) ceil(l2)) : 1; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Test all the fpolygons against *r after transforming by y=y-x*slope, and return | 
|  | the resulting selection, if any. | 
|  | */ | 
|  | pt_on_fpoly* select_in_univ(const frectangle* r, double slant) | 
|  | { | 
|  | static pt_on_fpoly answ; | 
|  | fpolygon* fp; | 
|  | answ.t = -1; | 
|  | for (fp=univ.p; fp!=0; fp=fp->link) | 
|  | if (fintersects(r, &fp->bb, slant)) | 
|  | select_in_fpoly(fp, r, slant, &answ); | 
|  | if (answ.t < 0) | 
|  | return 0; | 
|  | return &answ; | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | /**************************** Using the selection  ****************************/ | 
|  |  | 
|  | pt_on_fpoly cur_sel;			/* current selection if cur_sel.t>=0 */ | 
|  | pt_on_fpoly prev_sel;			/* previous selection if prev_sel.t>=0 (for slant) */ | 
|  | Image* sel_bkg = 0;			/* what's behind the red dot */ | 
|  |  | 
|  |  | 
|  | void clear_txt(void) | 
|  | { | 
|  | Rectangle r; | 
|  | r.min = screen->r.min; | 
|  | r.min.x += lft_border; | 
|  | r.min.y += outersep; | 
|  | r.max.x = top_left; | 
|  | r.max.y = r.min.y + smaxch.y; | 
|  | draw(screen, r, display->white, display->opaque, r.min); | 
|  | top_left = r.min.x; | 
|  | } | 
|  |  | 
|  |  | 
|  | Rectangle sel_dot_box(const transform* tr) | 
|  | { | 
|  | Point ctr; | 
|  | Rectangle r; | 
|  | if (tr==0) | 
|  | ctr.x = ctr.y = Dotrad; | 
|  | else	do_transform(&ctr, tr, &cur_sel.p); | 
|  | r.min.x=ctr.x-Dotrad;  r.max.x=ctr.x+Dotrad+1; | 
|  | r.min.y=ctr.y-Dotrad;  r.max.y=ctr.y+Dotrad+1; | 
|  | return r; | 
|  | } | 
|  |  | 
|  |  | 
|  | void unselect(const transform* tr) | 
|  | { | 
|  | transform tra; | 
|  | if (sel_bkg==0) | 
|  | sel_bkg = allocimage(display, sel_dot_box(0), CMAP8, 0, DWhite); | 
|  | clear_txt(); | 
|  | if (cur_sel.t < 0) | 
|  | return; | 
|  | prev_sel = cur_sel; | 
|  | if (tr==0) | 
|  | {tra=cur_trans(); tr=&tra;} | 
|  | draw(screen, sel_dot_box(tr), sel_bkg, display->opaque, ZP); | 
|  | cur_sel.t = -1; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Text at top right is written first and this low-level routine clobbers it if | 
|  | the new top-left text would overwrite it.  However, users of this routine should | 
|  | try to keep the new text short enough to avoid this. | 
|  | */ | 
|  | void show_mytext(char* msg) | 
|  | { | 
|  | Point tmp, pt = screen->r.min; | 
|  | int siz; | 
|  | tmp = stringsize(display->defaultfont, msg); | 
|  | siz = tmp.x; | 
|  | pt.x=top_left;  pt.y+=outersep; | 
|  | if (top_left+siz > top_right) { | 
|  | Rectangle r; | 
|  | r.min.y = pt.y; | 
|  | r.min.x = top_right; | 
|  | r.max.y = r.min.y + smaxch.y; | 
|  | r.max.x = top_left+siz; | 
|  | draw(screen, r, display->white, display->opaque, r.min); | 
|  | top_right = top_left+siz; | 
|  | } | 
|  | string(screen, pt, display->black, ZP, display->defaultfont, msg); | 
|  | top_left += siz; | 
|  | } | 
|  |  | 
|  |  | 
|  | double rnd(double x, double tol)	/* round to enough digits for accuracy tol */ | 
|  | { | 
|  | double t = pow(10, floor(log10(tol))); | 
|  | return t * floor(x/t + .5); | 
|  | } | 
|  |  | 
|  | double t_tol(double xtol, double ytol) | 
|  | { | 
|  | int t = (int) floor(cur_sel.t); | 
|  | fpoint* p = cur_sel.fp->p; | 
|  | double dx, dy; | 
|  | if (t==cur_sel.t) | 
|  | return 1; | 
|  | dx = fabs(p[t+1].x - p[t].x); | 
|  | dy = fabs(p[t+1].y - p[t].y); | 
|  | xtol /= (xtol>dx) ? xtol : dx; | 
|  | ytol /= (ytol>dy) ? ytol : dy; | 
|  | return (xtol<ytol) ? xtol : ytol; | 
|  | } | 
|  |  | 
|  | void say_where(const transform* tr) | 
|  | { | 
|  | double xtol=dxuntransform(tr,1), ytol=dyuntransform(tr,-1); | 
|  | char buf[100]; | 
|  | int n, nmax = (top_right - top_left)/smaxch.x; | 
|  | if (nmax >= 100) | 
|  | nmax = 100-1; | 
|  | n = sprintf(buf,"(%.14g,%.14g) at t=%.14g", | 
|  | rnd(cur_sel.p.x,xtol), rnd(cur_sel.p.y,ytol), | 
|  | rnd(cur_sel.t, t_tol(xtol,ytol))); | 
|  | if (cur_sel.fp->nam[0] != 0) | 
|  | sprintf(buf+n," %.*s", nmax-n-1, cur_sel.fp->nam); | 
|  | show_mytext(buf); | 
|  | } | 
|  |  | 
|  |  | 
|  | void reselect(const transform* tr)	/* uselect(); set cur_sel; call this */ | 
|  | { | 
|  | Point pt2, pt3; | 
|  | fpoint p2; | 
|  | transform tra; | 
|  | if (cur_sel.t < 0) | 
|  | return; | 
|  | if (tr==0) | 
|  | {tra=cur_trans(); tr=&tra;} | 
|  | do_transform(&p2, tr, &cur_sel.p); | 
|  | if (fabs(p2.x)+fabs(p2.y)>1e8 || (pt2.x=p2.x, pt2.y=p2.y, is_off_screen(pt2))) | 
|  | {cur_sel.t= -1; return;} | 
|  | pt3.x=pt2.x-Dotrad;  pt3.y=pt2.y-Dotrad; | 
|  | draw(sel_bkg, sel_dot_box(0), screen, display->opaque, pt3); | 
|  | fillellipse(screen, pt2, Dotrad, Dotrad, clr_im(DRed), pt2); | 
|  | say_where(tr); | 
|  | } | 
|  |  | 
|  |  | 
|  | void do_select(Point pt) | 
|  | { | 
|  | transform tr = cur_trans(); | 
|  | fpoint pt1, pt2, ctr; | 
|  | frectangle r; | 
|  | double slant; | 
|  | pt_on_fpoly* psel; | 
|  | unselect(&tr); | 
|  | do_untransform(&ctr, &tr, &pt); | 
|  | pt1.x=pt.x-fuzz;  pt1.y=pt.y+fuzz; | 
|  | pt2.x=pt.x+fuzz;  pt2.y=pt.y-fuzz; | 
|  | do_untransform(&r.min, &tr, &pt1); | 
|  | do_untransform(&r.max, &tr, &pt2); | 
|  | slant = u_slant_amt(&univ); | 
|  | slant_frect(&r, -slant); | 
|  | psel = select_in_univ(&r, slant); | 
|  | if (psel==0) | 
|  | return; | 
|  | if (logfil!=0) { | 
|  | fprintf(logfil,"%.14g\t%.14g\n", psel->p.x, psel->p.y); | 
|  | fflush(logfil); | 
|  | } | 
|  | cur_sel = *psel; | 
|  | reselect(&tr); | 
|  | } | 
|  |  | 
|  |  | 
|  | /***************************** Prompting for text *****************************/ | 
|  |  | 
|  | void unshow_mytext(char* msg) | 
|  | { | 
|  | Rectangle r; | 
|  | Point siz = stringsize(display->defaultfont, msg); | 
|  | top_left -= siz.x; | 
|  | r.min.y = screen->r.min.y + outersep; | 
|  | r.min.x = top_left; | 
|  | r.max.y = r.min.y + siz.y; | 
|  | r.max.x = r.min.x + siz.x; | 
|  | draw(screen, r, display->white, display->opaque, r.min); | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Show the given prompt and read a line of user input.  The text appears at the | 
|  | top left.  If it runs into the top right text, we stop echoing but let the user | 
|  | continue typing blind if he wants to. | 
|  | */ | 
|  | char* prompt_text(char* prompt) | 
|  | { | 
|  | static char buf[200]; | 
|  | int n0, n=0, nshown=0; | 
|  | Rune c; | 
|  | unselect(0); | 
|  | show_mytext(prompt); | 
|  | while (n<200-1-UTFmax && (c=ekbd())!='\n') { | 
|  | if (c=='\b') { | 
|  | buf[n] = 0; | 
|  | if (n > 0) | 
|  | do n--; | 
|  | while (n>0 && (buf[n-1]&0xc0)==0x80); | 
|  | if (n < nshown) | 
|  | {unshow_mytext(buf+n); nshown=n;} | 
|  | } else { | 
|  | n0 = n; | 
|  | n += runetochar(buf+n, &c); | 
|  | buf[n] = 0; | 
|  | if (nshown==n0 && top_right-top_left >= smaxch.x) | 
|  | {show_mytext(buf+n0); nshown=n;} | 
|  | } | 
|  | } | 
|  | buf[n] = 0; | 
|  | while (ecanmouse()) | 
|  | emouse(); | 
|  | return buf; | 
|  | } | 
|  |  | 
|  |  | 
|  | /**************************** Redrawing the screen ****************************/ | 
|  |  | 
|  | /* Let p0 and its successors define a piecewise-linear function of a paramter t, | 
|  | and draw the 0<=t<=n1 portion using transform *tr. | 
|  | */ | 
|  | void draw_fpts(const fpoint* p0, double n1, const transform* tr, int thick, | 
|  | Image* clr) | 
|  | { | 
|  | int n = (int) n1; | 
|  | const fpoint* p = p0 + n; | 
|  | fpoint pp; | 
|  | Point qq, q; | 
|  | if (n1 > n) { | 
|  | pp.x = p[0].x + (n1-n)*(p[1].x - p[0].x); | 
|  | pp.y = p[0].y + (n1-n)*(p[1].y - p[0].y); | 
|  | } else	pp = *p--; | 
|  | do_transform(&qq, tr, &pp); | 
|  | if (n1==0) | 
|  | fillellipse(screen, qq, 1+thick, 1+thick, clr, qq); | 
|  | for (; p>=p0; p--) { | 
|  | do_transform(&q, tr, p); | 
|  | line(screen, qq, q, Enddisc, Enddisc, thick, clr, qq); | 
|  | qq = q; | 
|  | } | 
|  | } | 
|  |  | 
|  | void draw_1fpoly(const fpolygon* fp, const transform* tr, Image* clr, | 
|  | const frectangle *udisp, double slant) | 
|  | { | 
|  | fpoint *p0=fp->p, *pn=fp->p+fp->n; | 
|  | double l1, l2; | 
|  | if (p0==pn && fcontains(udisp,*p0)) | 
|  | {draw_fpts(p0, 0, tr, fp->thick, clr); return;} | 
|  | while ((l1=out_length(p0,pn,*udisp,slant)) < pn-p0) { | 
|  | fpoint p0sav; | 
|  | int i1 = (int) l1; | 
|  | p0+=i1; l1-=i1; | 
|  | p0sav = *p0; | 
|  | p0[0].x += l1*(p0[1].x - p0[0].x); | 
|  | p0[0].y += l1*(p0[1].y - p0[0].y); | 
|  | l2 = in_length(p0, pn, *udisp, slant); | 
|  | draw_fpts(p0, l2, tr, fp->thick, clr); | 
|  | *p0 = p0sav; | 
|  | p0 += (l2>0) ? ((int) ceil(l2)) : 1; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | double get_clip_data(const fpolygons *u, frectangle *r) | 
|  | { | 
|  | double slant = set_unslanted_y((fpolygons*)u, &r->min.y, &r->max.y); | 
|  | r->min.x = u->disp.min.x; | 
|  | r->max.x = u->disp.max.x; | 
|  | return slant; | 
|  | } | 
|  |  | 
|  |  | 
|  | void draw_fpoly(const fpolygon* fp, const transform* tr, Image* clr) | 
|  | { | 
|  | frectangle r; | 
|  | double slant = get_clip_data(&univ, &r); | 
|  | draw_1fpoly(fp, tr, clr, &r, slant); | 
|  | } | 
|  |  | 
|  |  | 
|  | void eresized(int new) | 
|  | { | 
|  | transform tr; | 
|  | fpolygon* fp; | 
|  | frectangle clipr; | 
|  | double slant; | 
|  | if(new && getwindow(display, Refmesg) < 0) { | 
|  | fprintf(stderr,"can't reattach to window\n"); | 
|  | exits("reshap"); | 
|  | } | 
|  | draw(screen, screen->r, display->white, display->opaque, screen->r.min); | 
|  | tr = draw_frame(); | 
|  | slant = get_clip_data(&univ, &clipr); | 
|  | for (fp=univ.p; fp!=0; fp=fp->link) | 
|  | if (fintersects(&clipr, &fp->bb, slant)) | 
|  | draw_1fpoly(fp, &tr, fp->clr, &clipr, slant); | 
|  | reselect(0); | 
|  | if (mv_bkgd!=0 && mv_bkgd->repl==0) { | 
|  | freeimage(mv_bkgd); | 
|  | mv_bkgd = display->white; | 
|  | } | 
|  | flushimage(display, 1); | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  |  | 
|  | /********************************* Recoloring *********************************/ | 
|  |  | 
|  | int draw_palette(int n)		/* n is number of colors; returns patch dy */ | 
|  | { | 
|  | int y0 = screen->r.min.y + top_border; | 
|  | int dy = (screen->r.max.y - bot_border - y0)/n; | 
|  | Rectangle r; | 
|  | int i; | 
|  | r.min.y = y0; | 
|  | r.min.x = screen->r.max.x - rt_border + framewd; | 
|  | r.max.y = y0 + dy; | 
|  | r.max.x = screen->r.max.x; | 
|  | for (i=0; i<n; i++) { | 
|  | draw(screen, r, clrtab[i].im, display->opaque, r.min); | 
|  | r.min.y = r.max.y; | 
|  | r.max.y += dy; | 
|  | } | 
|  | return dy; | 
|  | } | 
|  |  | 
|  |  | 
|  | Image* palette_color(Point pt, int dy, int n) | 
|  | {				/* mouse at pt, patch size dy, n colors */ | 
|  | int yy; | 
|  | if (screen->r.max.x - pt.x > rt_border - framewd) | 
|  | return 0; | 
|  | yy = pt.y - (screen->r.min.y + top_border); | 
|  | if (yy<0 || yy>=n*dy) | 
|  | return 0; | 
|  | return clrtab[yy/dy].im; | 
|  | } | 
|  |  | 
|  |  | 
|  | void all_set_clr(fpolygons* fps, Image* clr) | 
|  | { | 
|  | fpolygon* p; | 
|  | for (p=fps->p; p!=0; p=p->link) | 
|  | p->clr = clr; | 
|  | } | 
|  |  | 
|  |  | 
|  | void do_recolor(int but, Mouse* m, int alluniv) | 
|  | { | 
|  | int nclr = clr_id(DWhite); | 
|  | int dy = draw_palette(nclr); | 
|  | Image* clr; | 
|  | if (!get_1click(but, m, 0)) { | 
|  | eresized(0); | 
|  | return; | 
|  | } | 
|  | clr = palette_color(m->xy, dy, nclr); | 
|  | if (clr != 0) { | 
|  | if (alluniv) | 
|  | all_set_clr(&univ, clr); | 
|  | else	cur_sel.fp->clr = clr; | 
|  | } | 
|  | eresized(0); | 
|  | lift_button(but, m, Never); | 
|  | } | 
|  |  | 
|  |  | 
|  | /****************************** Move and rotate  ******************************/ | 
|  |  | 
|  | void prepare_mv(const fpolygon* fp) | 
|  | { | 
|  | Rectangle r = screen->r; | 
|  | Image* scr0; | 
|  | int dt = 1 + fp->thick; | 
|  | r.min.x+=lft_border-dt;  r.min.y+=top_border-dt; | 
|  | r.max.x-=rt_border-dt;   r.max.y-=bot_border-dt; | 
|  | if (mv_bkgd!=0 && mv_bkgd->repl==0) | 
|  | freeimage(mv_bkgd); | 
|  | mv_bkgd = allocimage(display, r, CMAP8, 0, DNofill); | 
|  | if (mv_bkgd==0) | 
|  | mv_bkgd = display->white; | 
|  | else {	transform tr = cur_trans(); | 
|  | draw(mv_bkgd, r, screen, display->opaque, r.min); | 
|  | draw(mv_bkgd, sel_dot_box(&tr), sel_bkg, display->opaque, ZP); | 
|  | scr0 = screen; | 
|  | screen = mv_bkgd; | 
|  | draw_fpoly(fp, &tr, display->white); | 
|  | screen = scr0; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | void move_fp(fpolygon* fp, double dx, double dy) | 
|  | { | 
|  | fpoint *p, *pn=fp->p+fp->n; | 
|  | for (p=fp->p; p<=pn; p++) { | 
|  | (p->x) += dx; | 
|  | (p->y) += dy; | 
|  | } | 
|  | (fp->bb.min.x)+=dx;  (fp->bb.min.y)+=dy; | 
|  | (fp->bb.max.x)+=dx;  (fp->bb.max.y)+=dy; | 
|  | } | 
|  |  | 
|  |  | 
|  | void rotate_fp(fpolygon* fp, fpoint o, double theta) | 
|  | { | 
|  | double s=sin(theta), c=cos(theta); | 
|  | fpoint *p, *pn=fp->p+fp->n; | 
|  | for (p=fp->p; p<=pn; p++) { | 
|  | double x=p->x-o.x, y=p->y-o.y; | 
|  | (p->x) = o.x + c*x - s*y; | 
|  | (p->y) = o.y + s*x + c*y; | 
|  | } | 
|  | set_fbb(fp); | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Move the selected fpolygon so the selected point tracks the mouse, and return | 
|  | the total amount of movement.  Button but has already been held down for at | 
|  | least Mv_delay milliseconds and the mouse might have moved some distance. | 
|  | */ | 
|  | fpoint do_move(int but, Mouse* m) | 
|  | { | 
|  | transform tr = cur_trans(); | 
|  | int bbit = Button_bit(but); | 
|  | fpolygon* fp = cur_sel.fp; | 
|  | fpoint loc, loc0=cur_sel.p; | 
|  | double tsav = cur_sel.t; | 
|  | unselect(&tr); | 
|  | do {	latest_mouse(but, m); | 
|  | (fp->thick)++;		/* line() DISAGREES WITH ITSELF */ | 
|  | draw_fpoly(fp, &tr, mv_bkgd); | 
|  | (fp->thick)--; | 
|  | do_untransform(&loc, &tr, &m->xy); | 
|  | move_fp(fp, loc.x-cur_sel.p.x, loc.y-cur_sel.p.y); | 
|  | cur_sel.p = loc; | 
|  | draw_fpoly(fp, &tr, fp->clr); | 
|  | } while (m->buttons & bbit); | 
|  | cur_sel.t = tsav; | 
|  | reselect(&tr); | 
|  | loc.x -= loc0.x; | 
|  | loc.y -= loc0.y; | 
|  | return loc; | 
|  | } | 
|  |  | 
|  |  | 
|  | double dir_angle(const Point* pt, const transform* tr) | 
|  | { | 
|  | fpoint p; | 
|  | double dy, dx; | 
|  | do_untransform(&p, tr, pt); | 
|  | dy=p.y-cur_sel.p.y;  dx=p.x-cur_sel.p.x; | 
|  | return (dx==0 && dy==0) ? 0.0 : atan2(dy, dx); | 
|  | } | 
|  |  | 
|  |  | 
|  | /* Rotate the selected fpolygon around the selection point so as to track the | 
|  | direction angle from the selected point to m->xy.  Stop when button but goes | 
|  | up and return the total amount of rotation in radians. | 
|  | */ | 
|  | double do_rotate(int but, Mouse* m) | 
|  | { | 
|  | transform tr = cur_trans(); | 
|  | int bbit = Button_bit(but); | 
|  | fpolygon* fp = cur_sel.fp; | 
|  | double theta0 = dir_angle(&m->xy, &tr); | 
|  | double th, theta = theta0; | 
|  | do {	latest_mouse(but, m); | 
|  | (fp->thick)++;		/* line() DISAGREES WITH ITSELF */ | 
|  | draw_fpoly(fp, &tr, mv_bkgd); | 
|  | (fp->thick)--; | 
|  | th = dir_angle(&m->xy, &tr); | 
|  | rotate_fp(fp, cur_sel.p, th-theta); | 
|  | theta = th; | 
|  | draw_fpoly(fp, &tr, fp->clr); | 
|  | } while (m->buttons & bbit); | 
|  | unselect(&tr); | 
|  | cur_sel = prev_sel; | 
|  | reselect(&tr); | 
|  | return theta - theta0; | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | /********************************* Edit menu  *********************************/ | 
|  |  | 
|  | typedef enum e_index { | 
|  | Erecolor, Ethick, Edelete, Eundo, Erotate, Eoptions, | 
|  | Emove | 
|  | } e_index; | 
|  |  | 
|  | char* e_items[Eoptions+1]; | 
|  |  | 
|  | Menu e_menu = {e_items, 0, 0}; | 
|  |  | 
|  |  | 
|  | typedef struct e_action { | 
|  | e_index typ;			/* What type of action */ | 
|  | fpolygon* fp;			/* fpolygon the action applies to */ | 
|  | Image* clr;			/* color to use if typ==Erecolor */ | 
|  | double amt;			/* rotation angle or line thickness */ | 
|  | fpoint pt;			/* movement vector or rotation center */ | 
|  | struct e_action* link;		/* next in a stack */ | 
|  | } e_action; | 
|  |  | 
|  | e_action* unact = 0;			/* heads a linked list of actions */ | 
|  | e_action* do_undo(e_action*);		/* pop off an e_action and (un)do it */ | 
|  | e_action* save_act(e_action*,e_index);	/* append new e_action for status quo */ | 
|  |  | 
|  |  | 
|  | void save_mv(fpoint movement) | 
|  | { | 
|  | unact = save_act(unact, Emove); | 
|  | unact->pt = movement; | 
|  | } | 
|  |  | 
|  |  | 
|  | void init_e_menu(void) | 
|  | { | 
|  | char* u = "can't undo"; | 
|  | e_items[Erecolor] = "recolor"; | 
|  | e_items[Edelete] = "delete"; | 
|  | e_items[Erotate] = "rotate"; | 
|  | e_items[Eoptions-cantmv] = 0; | 
|  | e_items[Ethick] = (cur_sel.fp->thick >0) ? "thin" : "thick"; | 
|  | if (unact!=0) | 
|  | switch (unact->typ) { | 
|  | case Erecolor: u="uncolor"; break; | 
|  | case Ethick: u=(unact->fp->thick==0) ? "unthin" : "unthicken"; | 
|  | break; | 
|  | case Edelete: u="undelete"; break; | 
|  | case Emove: u="unmove"; break; | 
|  | case Erotate: u="unrotate"; break; | 
|  | } | 
|  | e_items[Eundo] = u; | 
|  | } | 
|  |  | 
|  |  | 
|  | void do_emenu(int but, Mouse* m) | 
|  | { | 
|  | int h; | 
|  | if (cur_sel.t < 0) | 
|  | return; | 
|  | init_e_menu(); | 
|  | h = emenuhit(but, m, &e_menu); | 
|  | switch(h) { | 
|  | case Ethick: unact = save_act(unact, h); | 
|  | cur_sel.fp->thick ^= 1; | 
|  | eresized(0); | 
|  | break; | 
|  | case Edelete: unact = save_act(unact, h); | 
|  | fp_remove(&univ, cur_sel.fp); | 
|  | unselect(0); | 
|  | eresized(0); | 
|  | break; | 
|  | case Erecolor: unact = save_act(unact, h); | 
|  | do_recolor(but, m, 0); | 
|  | break; | 
|  | case Erotate: unact = save_act(unact, h); | 
|  | prepare_mv(cur_sel.fp); | 
|  | if (get_1click(but, m, 0)) { | 
|  | unact->pt = cur_sel.p; | 
|  | unact->amt = do_rotate(but, m); | 
|  | } | 
|  | break; | 
|  | case Eundo: unact = do_undo(unact); | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | /******************************* Undoing edits  *******************************/ | 
|  |  | 
|  | e_action* save_act(e_action* a0, e_index typ) | 
|  | {					/* append new e_action for status quo */ | 
|  | e_action* a = malloc(sizeof(e_action)); | 
|  | a->link = a0; | 
|  | a->pt.x = a->pt.y = 0.0; | 
|  | a->amt = cur_sel.fp->thick; | 
|  | a->clr = cur_sel.fp->clr; | 
|  | a->fp = cur_sel.fp; | 
|  | a->typ = typ; | 
|  | return a; | 
|  | } | 
|  |  | 
|  |  | 
|  | /* This would be trivial except it's nice to preserve the selection in order to make | 
|  | it easy to undo a series of moves.  (There's no do_unrotate() because it's harder | 
|  | and less important to preserve the selection in that case.) | 
|  | */ | 
|  | void do_unmove(e_action* a) | 
|  | { | 
|  | double tsav = cur_sel.t; | 
|  | unselect(0); | 
|  | move_fp(a->fp, -a->pt.x, -a->pt.y); | 
|  | if (a->fp == cur_sel.fp) { | 
|  | cur_sel.p.x -= a->pt.x; | 
|  | cur_sel.p.y -= a->pt.y; | 
|  | } | 
|  | cur_sel.t = tsav; | 
|  | reselect(0); | 
|  | } | 
|  |  | 
|  |  | 
|  | e_action* do_undo(e_action* a0)		/* pop off an e_action and (un)do it */ | 
|  | { | 
|  | e_action* a = a0; | 
|  | if (a==0) | 
|  | return 0; | 
|  | switch(a->typ) { | 
|  | case Ethick: a->fp->thick = a->amt; | 
|  | eresized(0); | 
|  | break; | 
|  | case Erecolor: a->fp->clr = a->clr; | 
|  | eresized(0); | 
|  | break; | 
|  | case Edelete: | 
|  | a->fp->link = univ.p; | 
|  | univ.p = a->fp; | 
|  | grow_bb(&univ.bb, &a->fp->bb); | 
|  | eresized(0); | 
|  | break; | 
|  | case Emove: | 
|  | do_unmove(a); | 
|  | eresized(0); | 
|  | break; | 
|  | case Erotate: | 
|  | unselect(0); | 
|  | rotate_fp(a->fp, a->pt, -a->amt); | 
|  | eresized(0); | 
|  | break; | 
|  | } | 
|  | a0 = a->link; | 
|  | free(a); | 
|  | return a0; | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | /********************************* Main menu  *********************************/ | 
|  |  | 
|  | enum m_index {     Mzoom_in,  Mzoom_out,  Munzoom,  Mslant,    Munslant, | 
|  | Msquare_up,  Mrecenter,  Mrecolor,  Mrestack,  Mread, | 
|  | Mwrite,      Mexit}; | 
|  | char* m_items[] = {"zoom in", "zoom out", "unzoom", "slant",   "unslant", | 
|  | "square up", "recenter", "recolor", "restack", "read", | 
|  | "write",     "exit", 0}; | 
|  |  | 
|  | Menu m_menu = {m_items, 0, 0}; | 
|  |  | 
|  |  | 
|  | void do_mmenu(int but, Mouse* m) | 
|  | { | 
|  | int e, h = emenuhit(but, m, &m_menu); | 
|  | switch (h) { | 
|  | case Mzoom_in: | 
|  | disp_zoomin(egetrect(but,m)); | 
|  | eresized(0); | 
|  | break; | 
|  | case Mzoom_out: | 
|  | disp_zoomout(egetrect(but,m)); | 
|  | eresized(0); | 
|  | break; | 
|  | case Msquare_up: | 
|  | disp_squareup(); | 
|  | eresized(0); | 
|  | break; | 
|  | case Munzoom: | 
|  | init_disp(); | 
|  | eresized(0); | 
|  | break; | 
|  | case Mrecenter: | 
|  | if (get_1click(but, m, &bullseye)) { | 
|  | recenter_disp(m->xy); | 
|  | eresized(0); | 
|  | lift_button(but, m, Never); | 
|  | } | 
|  | break; | 
|  | case Mslant: | 
|  | if (cur_sel.t>=0 && prev_sel.t>=0) { | 
|  | slant_disp(prev_sel.p, cur_sel.p); | 
|  | eresized(0); | 
|  | } | 
|  | break; | 
|  | case Munslant: | 
|  | univ.slant_ht = univ.disp.max.y - univ.disp.min.y; | 
|  | eresized(0); | 
|  | break; | 
|  | case Mrecolor: | 
|  | do_recolor(but, m, 1); | 
|  | break; | 
|  | case Mrestack: | 
|  | fps_invert(&univ); | 
|  | eresized(0); | 
|  | break; | 
|  | case Mread: | 
|  | e = doinput(prompt_text("File:")); | 
|  | if (e==0) | 
|  | eresized(0); | 
|  | else if (e<0) | 
|  | show_mytext(" - can't read"); | 
|  | else { | 
|  | char ebuf[80]; | 
|  | snprintf(ebuf, 80, " - error line %d", e); | 
|  | show_mytext(ebuf); | 
|  | } | 
|  | break; | 
|  | case Mwrite: | 
|  | if (!dooutput(prompt_text("File:"))) | 
|  | show_mytext(" - can't write"); | 
|  | break; | 
|  | case Mexit: | 
|  | exits(""); | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | /****************************** Handling events  ******************************/ | 
|  |  | 
|  | void doevent(void) | 
|  | { | 
|  | ulong etype; | 
|  | int mobile; | 
|  | ulong mvtime; | 
|  | Event	ev; | 
|  |  | 
|  | etype = eread(Emouse|Ekeyboard, &ev); | 
|  | if(etype & Emouse) { | 
|  | if (ev.mouse.buttons & But1) { | 
|  | do_select(ev.mouse.xy); | 
|  | mvtime = Never; | 
|  | mobile = !cantmv && cur_sel.t>=0; | 
|  | if (mobile) { | 
|  | mvtime = ev.mouse.msec + Mv_delay; | 
|  | prepare_mv(cur_sel.fp); | 
|  | if (!lift_button(1, &ev.mouse, mvtime)) | 
|  | save_mv(do_move(1, &ev.mouse)); | 
|  | } | 
|  | } else if (ev.mouse.buttons & But2) | 
|  | do_emenu(2, &ev.mouse); | 
|  | else if (ev.mouse.buttons & But3) | 
|  | do_mmenu(3, &ev.mouse); | 
|  | } | 
|  | /* no need to check (etype & Ekeyboard)--there are no keyboard commands */ | 
|  | } | 
|  |  | 
|  |  | 
|  |  | 
|  | /******************************** Main program ********************************/ | 
|  |  | 
|  | extern char* argv0; | 
|  |  | 
|  | void usage(void) | 
|  | { | 
|  | int i; | 
|  | fprintf(stderr,"Usage %s [options] [infile]\n", argv0); | 
|  | fprintf(stderr, | 
|  | "option ::= -W winsize | -l logfile | -m\n" | 
|  | "\n" | 
|  | "Read a polygonal line graph in an ASCII format (one x y pair per line, delimited\n" | 
|  | "by spaces with a label after each polyline), and view it interactively.  Use\n" | 
|  | "standard input if no infile is specified.\n" | 
|  | ); | 
|  | fprintf(stderr, | 
|  | "Option -l specifies a file in which to log the coordinates of each point selected.\n" | 
|  | "(Clicking a point with button one selects it and displays its coordinates and\n" | 
|  | "the label of its polylone.)  Option -m allows polylines to be moved and rotated.\n" | 
|  | "The polyline labels can use the following color names:" | 
|  | ); | 
|  | for (i=0; clrtab[i].c!=DNofill; i++) | 
|  | fprintf(stderr,"%s%8s", (i%8==0 ? "\n" : "  "), clrtab[i].nam); | 
|  | fputc('\n', stderr); | 
|  | exits("usage"); | 
|  | } | 
|  |  | 
|  | void main(int argc, char *argv[]) | 
|  | { | 
|  | int e; | 
|  |  | 
|  | ARGBEGIN { | 
|  | case 'm': cantmv=0; | 
|  | break; | 
|  | case 'l': logfil = fopen(ARGF(),"w"); | 
|  | break; | 
|  | case 'W': | 
|  | winsize = EARGF(usage()); | 
|  | break; | 
|  | default: usage(); | 
|  | } ARGEND | 
|  |  | 
|  | if(initdraw(0, 0, "gview") < 0) | 
|  | exits("initdraw"); | 
|  | einit(Emouse|Ekeyboard); | 
|  |  | 
|  | e = doinput(*argv ? *argv : "-"); | 
|  | if (e < 0) { | 
|  | fprintf(stderr,"Cannot read input file %s\n", *argv); | 
|  | exits("no valid input file"); | 
|  | } else if (e > 0) { | 
|  | fprintf(stderr,"Bad syntax at line %d in input file\n", e); | 
|  | exits("bad syntax in input"); | 
|  | } | 
|  | init_disp(); | 
|  | init_clrtab(); | 
|  | set_default_clrs(&univ, 0); | 
|  | adjust_border(display->defaultfont); | 
|  | cur_sel.t = prev_sel.t = -1; | 
|  | eresized(0); | 
|  | for(;;) | 
|  | doevent(); | 
|  | } |