xref: /plan9-contrib/sys/src/cmd/gview.c (revision 4d44ba9b9ee4246ddbd96c7fcaf0918ab92ab35a)
1 #include	<u.h>
2 #include	<libc.h>
3 #include	<ctype.h>
4 #include	<draw.h>
5 #include	<event.h>
6 #include	<cursor.h>
7 #include	<stdio.h>
8 
9 #define Never	0xffffffff	/* Maximum ulong */
10 #define LOG2  0.301029995664
11 #define Button_bit(b)	(1 << ((b)-1))
12 
13 enum {
14 	But1	= Button_bit(1),/* mouse buttons for events */
15 	But2	= Button_bit(2),
16 	But3	= Button_bit(3),
17 };
18 int cantmv = 1;			/* disallow rotate and move? 0..1 */
19 int top_border, bot_border, lft_border, rt_border;
20 int lft_border0;		/* lft_border for y-axis labels >0 */
21 int top_left, top_right;	/* edges of top line free space */
22 int Mv_delay = 400;		/* msec for button click vs. button hold down */
23 int Dotrad = 2;			/* dot radius in pixels */
24 int framewd=1;			/* line thickness for frame (pixels) */
25 int framesep=1;			/* distance between frame and surrounding text */
26 int outersep=1;			/* distance: surrounding text to screen edge */
27 Point sdigit;			/* size of a digit in the font */
28 Point smaxch;			/* assume any character in font fits in this */
29 double underscan = .05;		/* fraction of frame initially unused per side */
30 double fuzz = 6;		/* selection tolerance in pixels */
31 int tick_len = 15;		/* length of axis label tick mark in pixels */
32 FILE* logfil = 0;		/* dump selected points here if nonzero */
33 
34 #define labdigs  3		/* allow this many sig digits in axis labels */
35 #define digs10pow 1000		/* pow(10,labdigs) */
36 #define axis_color  clr_im(DLtblue)
37 
38 
39 
40 
41 /********************************* Utilities  *********************************/
42 
43 /* Prepend string s to null-terminated string in n-byte buffer buf[], truncating if
44    necessary and using a space to separate s from the rest of buf[].
45 */
46 char* str_insert(char* buf, char* s, int n)
47 {
48 	int blen, slen = strlen(s) + 1;
49 	if (slen >= n)
50 		{strncpy(buf,s,n); buf[n-1]='\0'; return buf;}
51 	blen = strlen(buf);
52 	if (blen >= n-slen)
53 		buf[blen=n-slen-1] = '\0';
54 	memmove(buf+slen, buf, slen+blen+1);
55 	memcpy(buf, s, slen-1);
56 	buf[slen-1] = ' ';
57 	return buf;
58 }
59 
60 /* Alter string smain (without lengthening it) so as to remove the first occurrence of
61    ssub, assuming ssub is ASCII.  Return nonzero (true) if string smain had to be changed.
62    In spite of the ASCII-centric appearance, I think this can handle UTF in smain.
63 */
64 int remove_substr(char* smain, char* ssub)
65 {
66 	char *ss, *s = strstr(smain, ssub);
67 	int n = strlen(ssub);
68 	if (s==0)
69 		return 0;
70 	if (islower(s[n]))
71 		s[0] ^= 32;			/* probably tolower(s[0]) or toupper(s[0]) */
72 	else {
73 		for (ss=s+n; *ss!=0; s++, ss++)
74 			*s = *ss;
75 		*s = '\0';
76 	}
77 	return 1;
78 }
79 
80 void adjust_border(Font* f)
81 {
82 	int sep = framesep + outersep;
83 	sdigit = stringsize(f, "8");
84 	smaxch = stringsize(f, "MMMg");
85 	smaxch.x = (smaxch.x + 3)/4;
86 	lft_border0 = (1+labdigs)*sdigit.x + framewd + sep;
87 	rt_border = (lft_border0 - sep)/2 + outersep;
88 	bot_border = sdigit.y + framewd + sep;
89 	top_border = smaxch.y + framewd + sep;
90 	lft_border = lft_border0;		/* this gets reset later */
91 }
92 
93 
94 int is_off_screen(Point p)
95 {
96 	const Rectangle* r = &(screen->r);
97 	return p.x-r->min.x<lft_border || r->max.x-p.x<rt_border
98 		|| p.y-r->min.y<=top_border || r->max.y-p.y<=bot_border;
99 }
100 
101 
102 Cursor	bullseye =
103 {
104 	{-7, -7},
105 	{
106 		0x1F, 0xF8, 0x3F, 0xFC, 0x7F, 0xFE, 0xFB, 0xDF,
107 	 	0xF3, 0xCF, 0xE3, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF,
108 	 	0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xC7, 0xF3, 0xCF,
109 	 	0x7B, 0xDF, 0x7F, 0xFE, 0x3F, 0xFC, 0x1F, 0xF8,
110 	},
111 	{
112 		0x00, 0x00, 0x0F, 0xF0, 0x31, 0x8C, 0x21, 0x84,
113 		0x41, 0x82, 0x41, 0x82, 0x41, 0x82, 0x7F, 0xFE,
114 		0x7F, 0xFE, 0x41, 0x82, 0x41, 0x82, 0x41, 0x82,
115 		0x21, 0x84, 0x31, 0x8C, 0x0F, 0xF0, 0x00, 0x00,
116 	}
117 };
118 
119 int get_1click(int but, Mouse* m, Cursor* curs)
120 {
121 	if (curs)
122 		esetcursor(curs);
123 	while (m->buttons==0)
124 		*m = emouse();
125 	if (curs)
126 		esetcursor(0);
127 	return (m->buttons==Button_bit(but));
128 }
129 
130 
131 /* Wait until but goes up or until a mouse event's msec passes tlimit.
132    Return a boolean result that tells whether the button went up.
133 */
134 int lift_button(int but, Mouse* m, int tlimit)
135 {
136 	do {	*m = emouse();
137 		if (m->msec >= tlimit)
138 			return 0;
139 	} while (m->buttons & Button_bit(but));
140 	return 1;
141 }
142 
143 
144 /* Set *m to the last pending mouse event, or the first one where but is up.
145    If no mouse events are pending, wait for the next one.
146 */
147 void latest_mouse(int but, Mouse* m)
148 {
149 	int bbit = Button_bit(but);
150 	do {	*m = emouse();
151 	} while ((m->buttons & bbit) && ecanmouse());
152 }
153 
154 
155 
156 /*********************************** Colors ***********************************/
157 
158 enum {	DOrange=0xffaa00FF, Dgray=0xbbbbbbFF, DDkgreen=0x009900FF,
159 	DDkred=0xcc0000FF, DViolet=0x990099FF, DDkyellow=0xaaaa00FF,
160 	DLtblue=0xaaaaffFF, DPink=0xffaaaaFF,
161 	/* ndraw.h sets DBlack, DBlue, DRed, DYellow, DGreen,
162 		DCyan, DMagenta, DWhite */
163 };
164 
165 typedef struct color_ref {
166 	ulong c;			/* RGBA pixel color */
167 	char* nam;			/* ASCII name (matched to input, used in output)*/
168 	Image* im;			/* replicated solid-color image */
169 } color_ref;
170 
171 color_ref clrtab[] = {
172 	DRed,		"Red",		0,
173 	DPink,		"Pink",		0,
174 	DDkred,		"Dkred",	0,
175 	DOrange,	"Orange",	0,
176 	DYellow,	"Yellow",	0,
177 	DDkyellow,	"Dkyellow",	0,
178 	DGreen,		"Green",	0,
179 	DDkgreen,	"Dkgreen",	0,
180 	DCyan,		"Cyan",		0,
181 	DBlue,		"Blue",		0,
182 	DLtblue,	"Ltblue",	0,
183 	DMagenta,	"Magenta",	0,
184 	DViolet,	"Violet",	0,
185 	Dgray,		"Gray",		0,
186 	DBlack,		"Black",	0,
187 	DWhite,		"White",	0,
188 	DNofill,	0,		0	/* DNofill means "end of data" */
189 };
190 
191 
192 void  init_clrtab(void)
193 {
194 	int i;
195 	Rectangle r = Rect(0,0,1,1);
196 	for (i=0; clrtab[i].c!=DNofill; i++)
197 		clrtab[i].im = allocimage(display, r, CMAP8, 1, clrtab[i].c);
198 		/* should check for 0 result? */
199 }
200 
201 
202 int clrim_id(Image* clr)
203 {
204 	int i;
205 	for (i=0; clrtab[i].im!=clr; i++)
206 		if (clrtab[i].c==DNofill)
207 			exits("bad image color");
208 	return i;
209 }
210 
211 int clr_id(int clr)
212 {
213 	int i;
214 	for (i=0; clrtab[i].c!=clr; i++)
215 		if (clrtab[i].c==DNofill)
216 			exits("bad color");
217 	return i;
218 }
219 
220 #define clr_im(clr)	clrtab[clr_id(clr)].im
221 
222 
223 /* This decides what color to use for a polyline based on the label it has in the
224    input file.  Whichever color name comes first is the winner, otherwise return black.
225 */
226 Image* nam2clr(const char* nam, int *idxdest)
227 {
228 	char *c, *cbest=nam;
229 	int i, ibest=-1;
230 	if (*nam!=0)
231 		for (i=0; clrtab[i].nam!=0; i++) {
232 			c = strstr(nam,clrtab[i].nam);
233 			if (c!=0 && (ibest<0 || c<cbest))
234 				{ibest=i; cbest=c;}
235 		}
236 	if (idxdest!=0)
237 		*idxdest = (ibest<0) ? clr_id(DBlack) : ibest;
238 	return (ibest<0) ? clr_im(DBlack) : clrtab[ibest].im;
239 }
240 
241 /* A polyline is initial drawn in thick mode iff its label in the file contains "Thick" */
242 int nam2thick(const char* nam)
243 {
244 	return strstr(nam,"Thick")==0 ? 0 : 1;
245 }
246 
247 
248 /* Alter string nam so that nam2thick() and nam2clr() agree with th and clr, using
249    buf[] (a buffer of length bufn) to store the result if it differs from nam.
250    We go to great pains to perform this alteration in a manner that will seem natural
251    to the user, i.e., we try removing a suitably isolated color name before inserting
252    a new one.
253 */
254 char* nam_with_thclr(char* nam, int th, Image* clr, char* buf, int bufn)
255 {
256 	int clr0i, th0=nam2thick(nam);
257 	Image* clr0 = nam2clr(nam, &clr0i);
258 	char *clr0s;
259 	if (th0==th && clr0==clr)
260 		return nam;
261 	clr0s = clrtab[clr0i].nam;
262 	if (strlen(nam)<bufn) strcpy(buf,nam);
263 	else {strncpy(buf,nam,bufn); buf[bufn-1]='\0';}
264 	if (clr0 != clr)
265 		remove_substr(buf, clr0s);
266 	if (th0 > th)
267 		while (remove_substr(buf, "Thick"))
268 			/* do nothing */;
269 	if (nam2clr(buf,0) != clr)
270 		str_insert(buf, clrtab[clrim_id(clr)].nam, bufn);
271 	if (th0 < th)
272 		str_insert(buf, "Thick", bufn);
273 	return buf;
274 }
275 
276 
277 
278 /****************************** Data structures  ******************************/
279 
280 Image* mv_bkgd;				/* Background image (usually 0) */
281 
282 typedef struct fpoint {
283 	double x, y;
284 } fpoint;
285 
286 typedef struct frectangle {
287 	fpoint min, max;
288 } frectangle;
289 
290 frectangle empty_frect = {1e30, 1e30, -1e30, -1e30};
291 
292 
293 /* When *r2 is transformed by y=y-x*slant, might it intersect *r1 ?
294 */
295 int fintersects(const frectangle* r1, const frectangle* r2, double slant)
296 {
297 	double x2min=r2->min.x, x2max=r2->max.x;
298 	if (r1->max.x <= x2min || x2max <= r1->min.x)
299 		return 0;
300 	if (slant >=0)
301 		{x2min*=slant; x2max*=slant;}
302 	else	{double t=x2min*slant; x2min=x2max*slant; x2max=t;}
303 	return r1->max.y > r2->min.y-x2max && r2->max.y-x2min > r1->min.y;
304 }
305 
306 int fcontains(const frectangle* r, fpoint p)
307 {
308 	return r->min.x <=p.x && p.x<= r->max.x && r->min.y <=p.y && p.y<= r->max.y;
309 }
310 
311 
312 void grow_bb(frectangle* dest, const frectangle* r)
313 {
314 	if (r->min.x < dest->min.x) dest->min.x=r->min.x;
315 	if (r->min.y < dest->min.y) dest->min.y=r->min.y;
316 	if (r->max.x > dest->max.x) dest->max.x=r->max.x;
317 	if (r->max.y > dest->max.y) dest->max.y=r->max.y;
318 }
319 
320 
321 void slant_frect(frectangle *r, double sl)
322 {
323 	r->min.y += sl*r->min.x;
324 	r->max.y += sl*r->max.x;
325 }
326 
327 
328 fpoint fcenter(const frectangle* r)
329 {
330 	fpoint c;
331 	c.x = .5*(r->max.x + r->min.x);
332 	c.y = .5*(r->max.y + r->min.y);
333 	return c;
334 }
335 
336 
337 typedef struct fpolygon {
338 	fpoint* p;			/* a malloc'ed array */
339 	int n;				/* p[] has n elements: p[0..n] */
340 	frectangle bb;			/* bounding box */
341 	char* nam;			/* name of this polygon (malloc'ed) */
342 	int thick;			/* use 1+2*thick pixel wide lines */
343 	Image* clr;			/* Color to use when drawing this */
344 	struct fpolygon* link;
345 } fpolygon;
346 
347 typedef struct fpolygons {
348 	fpolygon* p;			/* the head of a linked list */
349 	frectangle bb;			/* overall bounding box */
350 	frectangle disp;		/* part being mapped onto screen->r */
351 	double slant_ht;		/* controls how disp is slanted */
352 } fpolygons;
353 
354 
355 fpolygons univ = {			/* everything there is to display */
356 	0,
357 	1e30, 1e30, -1e30, -1e30,
358 	0, 0, 0, 0,
359 	2*1e30
360 };
361 
362 
363 void set_default_clrs(fpolygons* fps, fpolygon* fpstop)
364 {
365 	fpolygon* fp;
366 	for (fp=fps->p; fp!=0 && fp!=fpstop; fp=fp->link) {
367 		fp->clr = nam2clr(fp->nam,0);
368 		fp->thick = nam2thick(fp->nam);
369 	}
370 }
371 
372 
373 void fps_invert(fpolygons* fps)
374 {
375 	fpolygon *p, *r=0;
376 	for (p=fps->p; p!=0;) {
377 		fpolygon* q = p;
378 		p = p->link;
379 		q->link = r;
380 		r = q;
381 	}
382 	fps->p = r;
383 }
384 
385 
386 void fp_remove(fpolygons* fps, fpolygon* fp)
387 {
388 	fpolygon *q, **p = &fps->p;
389 	while (*p!=fp)
390 		if (*p==0)
391 			return;
392 		else	p = &(*p)->link;
393 	*p = fp->link;
394 	fps->bb = empty_frect;
395 	for (q=fps->p; q!=0; q=q->link)
396 		grow_bb(&fps->bb, &q->bb);
397 }
398 
399 
400 /* The transform maps abstract fpoint coordinates (the ones used in the input)
401    to the current screen coordinates.  The do_untransform() macros reverses this.
402    If univ.slant_ht is not the height of univ.disp, the actual region in the
403    abstract coordinates is a parallelogram inscribed in univ.disp with two
404    vertical edges and two slanted slanted edges: slant_ht>0 means that the
405    vertical edges have height slant_ht and the parallelogram touches the lower
406    left and upper right corners of univ.disp; slant_ht<0 refers to a parallelogram
407    of height -slant_ht that touches the other two corners of univ.disp.
408    NOTE: the ytransform macro assumes that tr->sl times the x coordinate has
409    already been subtracted from yy.
410 */
411 typedef struct transform {
412 	double sl;
413 	fpoint o, sc;		/* (x,y):->(o.x+sc.x*x, o.y+sc.y*y+sl*x) */
414 } transform;
415 
416 #define do_transform(d,tr,s)	((d)->x = (tr)->o.x + (tr)->sc.x*(s)->x,  \
417 				(d)->y = (tr)->o.y + (tr)->sc.y*(s)->y    \
418 					+ (tr)->sl*(s)->x)
419 #define do_untransform(d,tr,s)	((d)->x = (.5+(s)->x-(tr)->o.x)/(tr)->sc.x,    \
420 				(d)->y = (.5+(s)->y-(tr)->sl*(d)->x-(tr)->o.y) \
421 					/(tr)->sc.y)
422 #define xtransform(tr,xx)	((tr)->o.x + (tr)->sc.x*(xx))
423 #define ytransform(tr,yy)	((tr)->o.y + (tr)->sc.y*(yy))
424 #define dxuntransform(tr,xx)	((xx)/(tr)->sc.x)
425 #define dyuntransform(tr,yy)	((yy)/(tr)->sc.y)
426 
427 
428 transform cur_trans(void)
429 {
430 	transform t;
431 	Rectangle d = screen->r;
432 	const frectangle* s = &univ.disp;
433 	double sh = univ.slant_ht;
434 	d.min.x += lft_border;
435 	d.min.y += top_border;
436 	d.max.x -= rt_border;
437 	d.max.y -= bot_border;
438 	t.sc.x = (d.max.x - d.min.x)/(s->max.x - s->min.x);
439 	t.sc.y = -(d.max.y - d.min.y)/fabs(sh);
440 	if (sh > 0) {
441 		t.sl = -t.sc.y*(s->max.y-s->min.y-sh)/(s->max.x - s->min.x);
442 		t.o.y = d.min.y - t.sc.y*s->max.y - t.sl*s->max.x;
443 	} else {
444 		t.sl = t.sc.y*(s->max.y-s->min.y+sh)/(s->max.x - s->min.x);
445 		t.o.y = d.min.y - t.sc.y*s->max.y - t.sl*s->min.x;
446 	}
447 	t.o.x = d.min.x - t.sc.x*s->min.x;
448 	return t;
449 }
450 
451 
452 double u_slant_amt(fpolygons *u)
453 {
454 	double sh=u->slant_ht, dy=u->disp.max.y - u->disp.min.y;
455 	double dx = u->disp.max.x - u->disp.min.x;
456 	return (sh>0) ? (dy-sh)/dx : -(dy+sh)/dx;
457 }
458 
459 
460 /* Set *y0 and *y1 to the lower and upper bounds of the set of y-sl*x values that
461    *u says to display, where sl is the amount of slant.
462 */
463 double set_unslanted_y(fpolygons *u, double *y0, double *y1)
464 {
465 	double yy1, sl=u_slant_amt(u);
466 	if (u->slant_ht > 0) {
467 		*y0 = u->disp.min.y - sl*u->disp.min.x;
468 		yy1 = *y0 + u->slant_ht;
469 	} else {
470 		yy1 = u->disp.max.y - sl*u->disp.min.x;
471 		*y0 = yy1 + u->slant_ht;
472 	}
473 	if (y1 != 0)
474 		*y1 = yy1;
475 	return sl;
476 }
477 
478 
479 
480 
481 /*************************** The region to display ****************************/
482 
483 void nontrivial_interval(double *lo, double *hi)
484 {
485 	if (*lo >= *hi) {
486 		double mid = .5*(*lo + *hi);
487 		double tweak = 1e-6 + 1e-6*fabs(mid);
488 		*lo = mid - tweak;
489 		*hi = mid + tweak;
490 	}
491 }
492 
493 
494 void init_disp(void)
495 {
496 	double dw = (univ.bb.max.x - univ.bb.min.x)*underscan;
497 	double dh = (univ.bb.max.y - univ.bb.min.y)*underscan;
498 	univ.disp.min.x = univ.bb.min.x - dw;
499 	univ.disp.min.y = univ.bb.min.y - dh;
500 	univ.disp.max.x = univ.bb.max.x + dw;
501 	univ.disp.max.y = univ.bb.max.y + dh;
502 	nontrivial_interval(&univ.disp.min.x, &univ.disp.max.x);
503 	nontrivial_interval(&univ.disp.min.y, &univ.disp.max.y);
504 	univ.slant_ht = univ.disp.max.y - univ.disp.min.y;	/* means no slant */
505 }
506 
507 
508 void recenter_disp(Point c)
509 {
510 	transform tr = cur_trans();
511 	fpoint cc, off;
512 	do_untransform(&cc, &tr, &c);
513 	off.x = cc.x - .5*(univ.disp.min.x + univ.disp.max.x);
514 	off.y = cc.y - .5*(univ.disp.min.y + univ.disp.max.y);
515 	univ.disp.min.x += off.x;
516 	univ.disp.min.y += off.y;
517 	univ.disp.max.x += off.x;
518 	univ.disp.max.y += off.y;
519 }
520 
521 
522 /* Find the upper-left and lower-right corners of the bounding box of the
523    parallelogram formed by untransforming the rectangle rminx, rminy, ... (given
524    in screen coordinates), and return the height of the parallelogram (negated
525    if it slopes downward).
526 */
527 double untransform_corners(double rminx, double rminy, double rmaxx, double rmaxy,
528 		fpoint *ul, fpoint *lr)
529 {
530 	fpoint r_ur, r_ul, r_ll, r_lr;	/* corners of the given recangle */
531 	fpoint ur, ll;			/* untransformed versions of r_ur, r_ll */
532 	transform tr = cur_trans();
533 	double ht;
534 	r_ur.x=rmaxx;  r_ur.y=rminy;
535 	r_ul.x=rminx;  r_ul.y=rminy;
536 	r_ll.x=rminx;  r_ll.y=rmaxy;
537 	r_lr.x=rmaxx;  r_lr.y=rmaxy;
538 	do_untransform(ul, &tr, &r_ul);
539 	do_untransform(lr, &tr, &r_lr);
540 	do_untransform(&ur, &tr, &r_ur);
541 	do_untransform(&ll, &tr, &r_ll);
542 	ht = ur.y - lr->y;
543 	if (ll.x < ul->x)
544 		ul->x = ll.x;
545 	if (ur.y > ul->y)
546 		ul->y = ur.y;
547 	else	ht = -ht;
548 	if (ur.x > lr->x)
549 		lr->x = ur.x;
550 	if (ll.y < lr->y)
551 		lr->y = ll.y;
552 	return ht;
553 }
554 
555 
556 void disp_dozoom(double rminx, double rminy, double rmaxx, double rmaxy)
557 {
558 	fpoint ul, lr;
559 	double sh = untransform_corners(rminx, rminy, rmaxx, rmaxy, &ul, &lr);
560 	if (ul.x==lr.x || ul.y==lr.y)
561 		return;
562 	univ.slant_ht = sh;
563 	univ.disp.min.x = ul.x;
564 	univ.disp.max.y = ul.y;
565 	univ.disp.max.x = lr.x;
566 	univ.disp.min.y = lr.y;
567 	nontrivial_interval(&univ.disp.min.x, &univ.disp.max.x);
568 	nontrivial_interval(&univ.disp.min.y, &univ.disp.max.y);
569 }
570 
571 
572 void disp_zoomin(Rectangle r)
573 {
574 	disp_dozoom(r.min.x, r.min.y, r.max.x, r.max.y);
575 }
576 
577 
578 void disp_zoomout(Rectangle r)
579 {
580 	double qminx, qminy, qmaxx, qmaxy;
581 	double scx, scy;
582 	Rectangle s = screen->r;
583 	if (r.min.x==r.max.x || r.min.y==r.max.y)
584 		return;
585 	s.min.x += lft_border;
586 	s.min.y += top_border;
587 	s.max.x -= rt_border;
588 	s.max.y -= bot_border;
589 	scx = (s.max.x - s.min.x)/(r.max.x - r.min.x);
590 	scy = (s.max.y - s.min.y)/(r.max.y - r.min.y);
591 	qminx = s.min.x + scx*(s.min.x - r.min.x);
592 	qmaxx = s.max.x + scx*(s.max.x - r.max.x);
593 	qminy = s.min.y + scy*(s.min.y - r.min.y);
594 	qmaxy = s.max.y + scy*(s.max.y - r.max.y);
595 	disp_dozoom(qminx, qminy, qmaxx, qmaxy);
596 }
597 
598 
599 void expand2(double* a, double* b, double f)
600 {
601 	double mid = .5*(*a + *b);
602 	*a = mid + f*(*a - mid);
603 	*b = mid + f*(*b - mid);
604 }
605 
606 void disp_squareup(void)
607 {
608 	double dx = univ.disp.max.x - univ.disp.min.x;
609 	double dy = univ.disp.max.y - univ.disp.min.y;
610 	dx /= screen->r.max.x - lft_border - screen->r.min.x - rt_border;
611 	dy /= screen->r.max.y - bot_border - screen->r.min.y - top_border;
612 	if (dx > dy)
613 		expand2(&univ.disp.min.y, &univ.disp.max.y, dx/dy);
614 	else	expand2(&univ.disp.min.x, &univ.disp.max.x, dy/dx);
615 	univ.slant_ht = univ.disp.max.y - univ.disp.min.y;
616 }
617 
618 
619 /* Slant so that p and q appear at the same height on the screen and the
620    screen contains the smallest possible superset of what its previous contents.
621 */
622 void slant_disp(fpoint p, fpoint q)
623 {
624 	double yll, ylr, yul, yur;	/* corner y coords of displayed parallelogram */
625 	double sh, dy;
626 	if (p.x == q.x)
627 		return;
628 	sh = univ.slant_ht;
629 	if (sh > 0) {
630 		yll=yul=univ.disp.min.y;  yul+=sh;
631 		ylr=yur=univ.disp.max.y;  ylr-=sh;
632 	} else {
633 		yll=yul=univ.disp.max.y;  yll+=sh;
634 		ylr=yur=univ.disp.min.y;  yur-=sh;
635 	}
636 	dy = (univ.disp.max.x-univ.disp.min.x)*(q.y - p.y)/(q.x - p.x);
637 	dy -= ylr - yll;
638 	if (dy > 0)
639 		{yll-=dy; yur+=dy;}
640 	else	{yul-=dy; ylr+=dy;}
641 	if (ylr > yll) {
642 		univ.disp.min.y = yll;
643 		univ.disp.max.y = yur;
644 		univ.slant_ht = yur - ylr;
645 	} else {
646 		univ.disp.max.y = yul;
647 		univ.disp.min.y = ylr;
648 		univ.slant_ht = ylr - yur;
649 	}
650 }
651 
652 
653 
654 
655 /******************************** Ascii input  ********************************/
656 
657 void set_fbb(fpolygon* fp)
658 {
659 	fpoint lo=fp->p[0], hi=fp->p[0];
660 	const fpoint *q, *qtop;
661 	for (qtop=(q=fp->p)+fp->n; ++q<=qtop;) {
662 		if (q->x < lo.x) lo.x=q->x;
663 		if (q->y < lo.y) lo.y=q->y;
664 		if (q->x > hi.x) hi.x=q->x;
665 		if (q->y > hi.y) hi.y=q->y;
666 	}
667 	fp->bb.min = lo;
668 	fp->bb.max = hi;
669 }
670 
671 char* mystrdup(char* s)
672 {
673 	char *r, *t = strrchr(s,'"');
674 	if (t==0) {
675 		t = s + strlen(s);
676 		while (t>s && (t[-1]=='\n' || t[-1]=='\r'))
677 			t--;
678 	}
679 	r = malloc(1+(t-s));
680 	memcpy(r, s, t-s);
681 	r[t-s] = 0;
682 	return r;
683 }
684 
685 int is_valid_label(char* lab)
686 {
687 	char* t;
688 	if (lab[0]=='"')
689 		return (t=strrchr(lab,'"'))!=0 && t!=lab && strspn(t+1," \t\r\n")==strlen(t+1);
690 	return strcspn(lab," \t")==strlen(lab);
691 }
692 
693 /* Read a polyline and update the number of lines read.  A zero result indicates bad
694    syntax if *lineno increases; otherwise it indicates end of file.
695 */
696 fpolygon* rd_fpoly(FILE* fin, int *lineno)
697 {
698 	char buf[256], junk[2];
699 	fpoint q;
700 	fpolygon* fp;
701 	int allocn;
702 	if (!fgets(buf,256,fin))
703 		return 0;
704 	(*lineno)++;
705 	if (sscanf(buf,"%lg%lg%1s",&q.x,&q.y,junk) != 2)
706 		return 0;
707 	fp = malloc(sizeof(fpolygon));
708 	allocn = 16;
709 	fp->p = malloc(allocn*sizeof(fpoint));
710 	fp->p[0] = q;
711 	fp->n = 0;
712 	fp->nam = "";
713 	fp->thick = 0;
714 	fp->clr = clr_im(DBlack);
715 	while (fgets(buf,256,fin)) {
716 		(*lineno)++;
717 		if (sscanf(buf,"%lg%lg%1s",&q.x,&q.y,junk) != 2) {
718 			if (!is_valid_label(buf))
719 				{free(fp->p); free(fp); return 0;}
720 			fp->nam = (buf[0]=='"') ? buf+1 : buf;
721 			break;
722 		}
723 		if (++(fp->n) == allocn)
724 			fp->p = realloc(fp->p, (allocn<<=1)*sizeof(fpoint));
725 		fp->p[fp->n] = q;
726 	}
727 	fp->nam = mystrdup(fp->nam);
728 	set_fbb(fp);
729 	fp->link = 0;
730 	return fp;
731 }
732 
733 
734 /* Read input into *fps and return 0 or a line number where there's a syntax error */
735 int rd_fpolys(FILE* fin, fpolygons* fps)
736 {
737 	fpolygon *fp, *fp0=fps->p;
738 	int lineno=0, ok_upto=0;
739 	while ((fp=rd_fpoly(fin,&lineno)) != 0) {
740 		ok_upto = lineno;
741 		fp->link = fps->p;
742 		fps->p = fp;
743 		grow_bb(&fps->bb, &fp->bb);
744 	}
745 	set_default_clrs(fps, fp0);
746 	return (ok_upto==lineno) ? 0 : lineno;
747 }
748 
749 
750 /* Read input from file fnam and return an error line no., -1 for "can't open"
751    or 0 for success.
752 */
753 int doinput(char* fnam)
754 {
755 	FILE* fin = strcmp(fnam,"-")==0 ? stdin : fopen(fnam, "r");
756 	int errline_or0;
757 	if (fin==0)
758 		return -1;
759 	errline_or0 = rd_fpolys(fin, &univ);
760 	fclose(fin);
761 	return errline_or0;
762 }
763 
764 
765 
766 /******************************** Ascii output ********************************/
767 
768 fpolygon* fp_reverse(fpolygon* fp)
769 {
770 	fpolygon* r = 0;
771 	while (fp!=0) {
772 		fpolygon* q = fp->link;
773 		fp->link = r;
774 		r = fp;
775 		fp = q;
776 	}
777 	return r;
778 }
779 
780 void wr_fpoly(FILE* fout, const fpolygon* fp)
781 {
782 	char buf[256];
783 	int i;
784 	for (i=0; i<=fp->n; i++)
785 		fprintf(fout,"%.12g\t%.12g\n", fp->p[i].x, fp->p[i].y);
786 	fprintf(fout,"\"%s\"\n", nam_with_thclr(fp->nam, fp->thick, fp->clr, buf, 256));
787 }
788 
789 void wr_fpolys(FILE* fout, fpolygons* fps)
790 {
791 	fpolygon* fp;
792 	fps->p = fp_reverse(fps->p);
793 	for (fp=fps->p; fp!=0; fp=fp->link)
794 		wr_fpoly(fout, fp);
795 	fps->p = fp_reverse(fps->p);
796 }
797 
798 
799 int dooutput(char* fnam)
800 {
801 	FILE* fout = fopen(fnam, "w");
802 	if (fout==0)
803 		return 0;
804 	wr_fpolys(fout, &univ);
805 	fclose(fout);
806 	return 1;
807 }
808 
809 
810 
811 
812 /************************ Clipping to screen rectangle ************************/
813 
814 /* Find the t values, 0<=t<=1 for which x0+t*(x1-x0) is between xlo and xhi,
815    or return 0 to indicate no such t values exist.  If returning 1, set *t0 and
816    *t1 to delimit the t interval.
817 */
818 int do_xory(double x0, double x1, double xlo, double xhi, double* t0, double* t1)
819 {
820 	*t1 = 1.0;
821 	if (x0<xlo) {
822 		if (x1<xlo) return 0;
823 		*t0 = (xlo-x0)/(x1-x0);
824 		if (x1>xhi) *t1 = (xhi-x0)/(x1-x0);
825 	} else if (x0>xhi) {
826 		if (x1>xhi) return 0;
827 		*t0 = (xhi-x0)/(x1-x0);
828 		if (x1<xlo) *t1 = (xlo-x0)/(x1-x0);
829 	} else {
830 		*t0 = 0.0;
831 		if (x1>xhi) *t1 = (xhi-x0)/(x1-x0);
832 		else if (x1<xlo) *t1 = (xlo-x0)/(x1-x0);
833 		else *t1 = 1.0;
834 	}
835 	return 1;
836 }
837 
838 
839 /* After mapping y to y-slope*x, what initial fraction of the *p to *q edge is
840    outside of *r?  Note that the edge could start outside *r, pass through *r,
841    and wind up outside again.
842 */
843 double frac_outside(const fpoint* p, const fpoint* q, const frectangle* r,
844 		double slope)
845 {
846 	double t0, t1, tt0, tt1;
847 	double px=p->x, qx=q->x;
848 	if (!do_xory(px, qx, r->min.x, r->max.x, &t0, &t1))
849 		return 1;
850 	if (!do_xory(p->y-slope*px, q->y-slope*qx, r->min.y, r->max.y, &tt0, &tt1))
851 		return 1;
852 	if (tt0 > t0)
853 		t0 = tt0;
854 	if (t1<=t0 || tt1<=t0)
855 		return 1;
856 	return t0;
857 }
858 
859 
860 /* Think of p0..pn as piecewise-linear function F(t) for t=0..pn-p0, and find
861    the maximum tt such that F(0..tt) is all inside of r, assuming p0 is inside.
862    Coordinates are transformed by y=y-x*slope before testing against r.
863 */
864 double in_length(const fpoint* p0, const fpoint* pn, frectangle r, double slope)
865 {
866 	const fpoint* p = p0;
867 	double px, py;
868 	do if (++p > pn)
869 		return pn - p0;
870 	while (r.min.x<=(px=p->x) && px<=r.max.x
871 			&& r.min.y<=(py=p->y-slope*px) && py<=r.max.y);
872 	return (p - p0) - frac_outside(p, p-1, &r, slope);
873 }
874 
875 
876 /* Think of p0..pn as piecewise-linear function F(t) for t=0..pn-p0, and find
877    the maximum tt such that F(0..tt) is all outside of *r.  Coordinates are
878    transformed by y=y-x*slope before testing against r.
879 */
880 double out_length(const fpoint* p0, const fpoint* pn, frectangle r, double slope)
881 {
882 	const fpoint* p = p0;
883 	double fr;
884 	do {	if (p->x < r.min.x)
885 			do if (++p>pn) return pn-p0;
886 			while (p->x <= r.min.x);
887 		else if (p->x > r.max.x)
888 			do if (++p>pn) return pn-p0;
889 			while (p->x >= r.max.x);
890 		else if (p->y-slope*p->x < r.min.y)
891 			do if (++p>pn) return pn-p0;
892 			while (p->y-slope*p->x <= r.min.y);
893 		else if (p->y-slope*p->x > r.max.y)
894 			do if (++p>pn) return pn-p0;
895 			while (p->y-slope*p->x >= r.max.y);
896 		else return p - p0;
897 	} while ((fr=frac_outside(p-1,p,&r,slope)) == 1);
898 	return (p - p0) + fr-1;
899 }
900 
901 
902 
903 /*********************** Drawing frame and axis labels  ***********************/
904 
905 #define Nthous  7
906 #define Len_thous  30			/* bound on strlen(thous_nam[i]) */
907 char* thous_nam[Nthous] = {
908 	"one", "thousand", "million", "billion",
909 	"trillion", "quadrillion", "quintillion",
910 };
911 
912 
913 typedef struct lab_interval {
914 	double sep;			/* separation between tick marks */
915 	double unit;		/* power of 1000 divisor */
916 	int logunit;		/* log base 1000 of of this divisor */
917 	double off;			/* offset to subtract before dividing */
918 } lab_interval;
919 
920 
921 char* abbrev_num(double x, const lab_interval* iv)
922 {
923 	static char buf[16];
924 	double dx = x - iv->off;
925 	dx = iv->sep * floor(dx/iv->sep + .5);
926 	sprintf(buf,"%g", dx/iv->unit);
927 	return buf;
928 }
929 
930 
931 double lead_digits(double n, double r)	/* n truncated to power of 10 above r */
932 {
933 	double rr = pow(10, ceil(log10(r)));
934 	double nn = (n<rr) ? 0.0 : rr*floor(n/rr);
935 	if (n+r-nn >= digs10pow) {
936 		rr /= 10;
937 		nn = (n<rr) ? 0.0 : rr*floor(n/rr);
938 	}
939 	return nn;
940 }
941 
942 
943 lab_interval next_larger(double s0, double xlo, double xhi)
944 {
945 	double nlo, nhi;
946 	lab_interval r;
947 	r.logunit = (int) floor(log10(s0) + LOG2);
948 	r.unit = pow(10, r.logunit);
949 	nlo = xlo/r.unit;
950 	nhi = xhi/r.unit;
951 	if (nhi >= digs10pow)
952 		r.off = r.unit*lead_digits(nlo, nhi-nlo);
953 	else if (nlo <= -digs10pow)
954 		r.off = -r.unit*lead_digits(-nhi, nhi-nlo);
955 	else	r.off = 0;
956 	r.sep = (s0<=r.unit) ? r.unit : (s0<2*r.unit ? 2*r.unit : 5*r.unit);
957 	switch (r.logunit%3) {
958 	case 1:	r.unit*=.1; r.logunit--;
959 		break;
960 	case -1: case 2:
961 		r.unit*=10; r.logunit++;
962 		break;
963 	case -2: r.unit*=100; r.logunit+=2;
964 	}
965 	r.logunit /= 3;
966 	return r;
967 }
968 
969 
970 double min_hsep(const transform* tr)
971 {
972 	double s = (2+labdigs)*sdigit.x;
973 	double ss = (univ.disp.min.x<0) ? s+sdigit.x : s;
974 	return dxuntransform(tr, ss);
975 }
976 
977 
978 lab_interval mark_x_axis(const transform* tr)
979 {
980 	fpoint p = univ.disp.min;
981 	Point q, qtop, qbot, tmp;
982 	double x0=univ.disp.min.x, x1=univ.disp.max.x;
983 	double seps0, nseps, seps;
984 	lab_interval iv = next_larger(min_hsep(tr), x0, x1);
985 	set_unslanted_y(&univ, &p.y, 0);
986 	q.y = ytransform(tr, p.y) + .5;
987 	qtop.y = q.y - tick_len;
988 	qbot.y = q.y + framewd + framesep;
989 	seps0 = ceil(x0/iv.sep);
990 	for (seps=0, nseps=floor(x1/iv.sep)-seps0; seps<=nseps; seps+=1) {
991 		char* num = abbrev_num((p.x=iv.sep*(seps0+seps)), &iv);
992 		Font* f = display->defaultfont;
993 		q.x = qtop.x = qbot.x = xtransform(tr, p.x);
994 		line(screen, qtop, q, Enddisc, Enddisc, 0, axis_color, q);
995 		tmp = stringsize(f, num);
996 		qbot.x -= tmp.x/2;
997 		string(screen, qbot, display->black, qbot, f, num);
998 	}
999 	return iv;
1000 }
1001 
1002 
1003 lab_interval mark_y_axis(const transform* tr)
1004 {
1005 	Font* f = display->defaultfont;
1006 	fpoint p = univ.disp.min;
1007 	Point q, qrt, qlft;
1008 	double y0, y1, seps0, nseps, seps;
1009 	lab_interval iv;
1010 	set_unslanted_y(&univ, &y0, &y1);
1011 	iv = next_larger(dyuntransform(tr,-f->height), y0, y1);
1012 	q.x = xtransform(tr, p.x) - .5;
1013 	qrt.x = q.x + tick_len;
1014 	qlft.x = q.x - (framewd + framesep);
1015 	seps0 = ceil(y0/iv.sep);
1016 	for (seps=0, nseps=floor(y1/iv.sep)-seps0; seps<=nseps; seps+=1) {
1017 		char* num = abbrev_num((p.y=iv.sep*(seps0+seps)), &iv);
1018 		Point qq = stringsize(f, num);
1019 		q.y = qrt.y = qlft.y = ytransform(tr, p.y);
1020 		line(screen, qrt, q, Enddisc, Enddisc, 0, axis_color, q);
1021 		qq.x = qlft.x - qq.x;
1022 		qq.y = qlft.y - qq.y/2;
1023 		string(screen, qq, display->black, qq, f, num);
1024 	}
1025 	return iv;
1026 }
1027 
1028 
1029 void lab_iv_info(const lab_interval *iv, double slant, char* buf, int *n)
1030 {
1031 	if (iv->off > 0)
1032 		(*n) += sprintf(buf+*n,"-%.12g",iv->off);
1033 	else if (iv->off < 0)
1034 		(*n) += sprintf(buf+*n,"+%.12g",-iv->off);
1035 	if (slant>0)
1036 		(*n) += sprintf(buf+*n,"-%.6gx", slant);
1037 	else if (slant<0)
1038 		(*n) += sprintf(buf+*n,"+%.6gx", -slant);
1039 	if (abs(iv->logunit) >= Nthous)
1040 		(*n) += sprintf(buf+*n," in 1e%d units", 3*iv->logunit);
1041 	else if (iv->logunit > 0)
1042 		(*n) += sprintf(buf+*n," in %ss", thous_nam[iv->logunit]);
1043 	else if (iv->logunit < 0)
1044 		(*n) += sprintf(buf+*n," in %sths", thous_nam[-iv->logunit]);
1045 }
1046 
1047 
1048 void draw_xy_ranges(const lab_interval *xiv, const lab_interval *yiv)
1049 {
1050 	Point p;
1051 	char buf[2*(19+Len_thous+8)+50];
1052 	int bufn = 0;
1053 	buf[bufn++] = 'x';
1054 	lab_iv_info(xiv, 0, buf, &bufn);
1055 	bufn += sprintf(buf+bufn, "; y");
1056 	lab_iv_info(yiv, u_slant_amt(&univ), buf, &bufn);
1057 	buf[bufn] = '\0';
1058 	p = stringsize(display->defaultfont, buf);
1059 	top_left = screen->r.min.x + lft_border;
1060 	p.x = top_right = screen->r.max.x - rt_border - p.x;
1061 	p.y = screen->r.min.y + outersep;
1062 	string(screen, p, display->black, p, display->defaultfont, buf);
1063 }
1064 
1065 
1066 transform draw_frame(void)
1067 {
1068 	lab_interval x_iv, y_iv;
1069 	transform tr;
1070 	Rectangle r = screen->r;
1071 	lft_border = (univ.disp.min.y<0) ? lft_border0+sdigit.x : lft_border0;
1072 	tr = cur_trans();
1073 	r.min.x += lft_border;
1074 	r.min.y += top_border;
1075 	r.max.x -= rt_border;
1076 	r.max.y -= bot_border;
1077 	border(screen, r, -framewd, axis_color, r.min);
1078 	x_iv = mark_x_axis(&tr);
1079 	y_iv = mark_y_axis(&tr);
1080 	draw_xy_ranges(&x_iv, &y_iv);
1081 	return tr;
1082 }
1083 
1084 
1085 
1086 /*************************** Finding the selection  ***************************/
1087 
1088 typedef struct pt_on_fpoly {
1089 	fpoint p;			/* the point */
1090 	fpolygon* fp;			/* the fpolygon it lies on */
1091 	double t;			/* how many knots from the beginning */
1092 } pt_on_fpoly;
1093 
1094 
1095 static double myx, myy;
1096 #define mydist(p,o,sl,xwt,ywt)	(myx=(p).x-(o).x, myy=(p).y-sl*(p).x-(o).y,	\
1097 					xwt*myx*myx + ywt*myy*myy)
1098 
1099 /* At what fraction of the way from p0[0] to p0[1] is mydist(p,ctr,slant,xwt,ywt)
1100    minimized?
1101 */
1102 double closest_time(const fpoint* p0, const fpoint* ctr, double slant,
1103 		double xwt, double ywt)
1104 {
1105 	double p00y=p0[0].y-slant*p0[0].x, p01y=p0[1].y-slant*p0[1].x;
1106 	double dx=p0[1].x-p0[0].x, dy=p01y-p00y;
1107 	double x0=p0[0].x-ctr->x, y0=p00y-ctr->y;
1108 	double bot = xwt*dx*dx + ywt*dy*dy;
1109 	if (bot==0)
1110 		return 0;
1111 	return -(xwt*x0*dx + ywt*y0*dy)/bot;
1112 }
1113 
1114 
1115 /* Scan the polygonal path of length len knots starting at p0, and find the
1116    point that the transformation y=y-x*slant makes closest to the center of *r,
1117    where *r itself defines the distance metric.  Knots get higher priority than
1118    points between knots.  If psel->t is negative, always update *psel; otherwise
1119    update *psel only if the scan can improve it.  Return a boolean that says
1120    whether *psel was updated.
1121      Note that *r is a very tiny rectangle (tiny when converted screen pixels)
1122    such that anything in *r is considered close enough to match the mouse click.
1123    The purpose of this routine is to be careful in case there is a lot of hidden
1124    detail in the tiny rectangle *r.
1125 */
1126 int improve_pt(fpoint* p0, double len, const frectangle* r, double slant,
1127 		pt_on_fpoly* psel)
1128 {
1129 	fpoint ctr = fcenter(r);
1130 	double x_wt=2/(r->max.x-r->min.x), y_wt=2/(r->max.y-r->min.y);
1131 	double xwt=x_wt*x_wt, ywt=y_wt*y_wt;
1132 	double d, dbest = (psel->t <0) ? 1e30 : mydist(psel->p,ctr,slant,xwt,ywt);
1133 	double tt, dbest0 = dbest;
1134 	fpoint pp;
1135 	int ilen = (int) len;
1136 	if (len==0 || ilen>0) {
1137 		int i;
1138 		for (i=(len==0 ? 0 : 1); i<=ilen; i++) {
1139 			d = mydist(p0[i], ctr, slant, xwt, ywt);
1140 			if (d < dbest)
1141 				{psel->p=p0[i]; psel->t=i; dbest=d;}
1142 		}
1143 		return (dbest < dbest0);
1144 	}
1145 	tt = closest_time(p0, &ctr, slant, xwt, ywt);
1146 	if (tt > len)
1147 		tt = len;
1148 	pp.x = p0[0].x + tt*(p0[1].x - p0[0].x);
1149 	pp.y = p0[0].y + tt*(p0[1].y - p0[0].y);
1150 	if (mydist(pp, ctr, slant, xwt, ywt) < dbest) {
1151 		psel->p = pp;
1152 		psel->t = tt;
1153 		return 1;
1154 	}
1155 	return 0;
1156 }
1157 
1158 
1159 /* Test *fp against *r after transforming by y=y-x*slope, and set *psel accordingly.
1160 */
1161 void select_in_fpoly(fpolygon* fp, const frectangle* r, double slant,
1162 		pt_on_fpoly* psel)
1163 {
1164 	fpoint *p0=fp->p, *pn=fp->p+fp->n;
1165 	double l1, l2;
1166 	if (p0==pn)
1167 		{improve_pt(p0, 0, r, slant, psel); psel->fp=fp; return;}
1168 	while ((l1=out_length(p0,pn,*r,slant)) < pn-p0) {
1169 		fpoint p0sav;
1170 		int i1 = (int) l1;
1171 		p0+=i1; l1-=i1;
1172 		p0sav = *p0;
1173 		p0[0].x += l1*(p0[1].x - p0[0].x);
1174 		p0[0].y += l1*(p0[1].y - p0[0].y);
1175 		l2 = in_length(p0, pn, *r, slant);
1176 		if (improve_pt(p0, l2, r, slant, psel)) {
1177 			if (l1==0 && psel->t!=((int) psel->t)) {
1178 				psel->t = 0;
1179 				psel->p = *p0;
1180 			} else if (psel->t < 1)
1181 				psel->t += l1*(1 - psel->t);
1182 			psel->t += p0 - fp->p;
1183 			psel->fp = fp;
1184 		}
1185 		*p0 = p0sav;
1186 		p0 += (l2>0) ? ((int) ceil(l2)) : 1;
1187 	}
1188 }
1189 
1190 
1191 /* Test all the fpolygons against *r after transforming by y=y-x*slope, and return
1192    the resulting selection, if any.
1193 */
1194 pt_on_fpoly* select_in_univ(const frectangle* r, double slant)
1195 {
1196 	static pt_on_fpoly answ;
1197 	fpolygon* fp;
1198 	answ.t = -1;
1199 	for (fp=univ.p; fp!=0; fp=fp->link)
1200 		if (fintersects(r, &fp->bb, slant))
1201 			select_in_fpoly(fp, r, slant, &answ);
1202 	if (answ.t < 0)
1203 		return 0;
1204 	return &answ;
1205 }
1206 
1207 
1208 
1209 /**************************** Using the selection  ****************************/
1210 
1211 pt_on_fpoly cur_sel;			/* current selection if cur_sel.t>=0 */
1212 pt_on_fpoly prev_sel;			/* previous selection if prev_sel.t>=0 (for slant) */
1213 Image* sel_bkg = 0;			/* what's behind the red dot */
1214 
1215 
1216 void clear_txt(void)
1217 {
1218 	Rectangle r;
1219 	r.min = screen->r.min;
1220 	r.min.x += lft_border;
1221 	r.min.y += outersep;
1222 	r.max.x = top_left;
1223 	r.max.y = r.min.y + smaxch.y;
1224 	draw(screen, r, display->white, display->opaque, r.min);
1225 	top_left = r.min.x;
1226 }
1227 
1228 
1229 Rectangle sel_dot_box(const transform* tr)
1230 {
1231 	Point ctr;
1232 	Rectangle r;
1233 	if (tr==0)
1234 		ctr.x = ctr.y = Dotrad;
1235 	else	do_transform(&ctr, tr, &cur_sel.p);
1236 	r.min.x=ctr.x-Dotrad;  r.max.x=ctr.x+Dotrad+1;
1237 	r.min.y=ctr.y-Dotrad;  r.max.y=ctr.y+Dotrad+1;
1238 	return r;
1239 }
1240 
1241 
1242 void unselect(const transform* tr)
1243 {
1244 	transform tra;
1245 	if (sel_bkg==0)
1246 		sel_bkg = allocimage(display, sel_dot_box(0), CMAP8, 0, DWhite);
1247 	clear_txt();
1248 	if (cur_sel.t < 0)
1249 		return;
1250 	prev_sel = cur_sel;
1251 	if (tr==0)
1252 		{tra=cur_trans(); tr=&tra;}
1253 	draw(screen, sel_dot_box(tr), sel_bkg, display->opaque, ZP);
1254 	cur_sel.t = -1;
1255 }
1256 
1257 
1258 /* Text at top right is written first and this low-level routine clobbers it if
1259    the new top-left text would overwrite it.  However, users of this routine should
1260    try to keep the new text short enough to avoid this.
1261 */
1262 void show_mytext(char* msg)
1263 {
1264 	Point tmp, pt = screen->r.min;
1265 	int siz;
1266 	tmp = stringsize(display->defaultfont, msg);
1267 	siz = tmp.x;
1268 	pt.x=top_left;  pt.y+=outersep;
1269 	if (top_left+siz > top_right) {
1270 		Rectangle r;
1271 		r.min.y = pt.y;
1272 		r.min.x = top_right;
1273 		r.max.y = r.min.y + smaxch.y;
1274 		r.max.x = top_left+siz;
1275 		draw(screen, r, display->white, display->opaque, r.min);
1276 		top_right = top_left+siz;
1277 	}
1278 	string(screen, pt, display->black, ZP, display->defaultfont, msg);
1279 	top_left += siz;
1280 }
1281 
1282 
1283 double rnd(double x, double tol)	/* round to enough digits for accuracy tol */
1284 {
1285 	double t = pow(10, floor(log10(tol)));
1286 	return t * floor(x/t + .5);
1287 }
1288 
1289 double t_tol(double xtol, double ytol)
1290 {
1291 	int t = (int) floor(cur_sel.t);
1292 	fpoint* p = cur_sel.fp->p;
1293 	double dx, dy;
1294 	if (t==cur_sel.t)
1295 		return 1;
1296 	dx = fabs(p[t+1].x - p[t].x);
1297 	dy = fabs(p[t+1].y - p[t].y);
1298 	xtol /= (xtol>dx) ? xtol : dx;
1299 	ytol /= (ytol>dy) ? ytol : dy;
1300 	return (xtol<ytol) ? xtol : ytol;
1301 }
1302 
1303 void say_where(const transform* tr)
1304 {
1305 	double xtol=dxuntransform(tr,1), ytol=dyuntransform(tr,-1);
1306 	char buf[100];
1307 	int n, nmax = (top_right - top_left)/smaxch.x;
1308 	if (nmax >= 100)
1309 		nmax = 100-1;
1310 	n = sprintf(buf,"(%.14g,%.14g) at t=%.14g",
1311 			rnd(cur_sel.p.x,xtol), rnd(cur_sel.p.y,ytol),
1312 			rnd(cur_sel.t, t_tol(xtol,ytol)));
1313 	if (cur_sel.fp->nam[0] != 0)
1314 		sprintf(buf+n," %.*s", nmax-n-1, cur_sel.fp->nam);
1315 	show_mytext(buf);
1316 }
1317 
1318 
1319 void reselect(const transform* tr)	/* uselect(); set cur_sel; call this */
1320 {
1321 	Point pt2, pt3;
1322 	fpoint p2;
1323 	transform tra;
1324 	if (cur_sel.t < 0)
1325 		return;
1326 	if (tr==0)
1327 		{tra=cur_trans(); tr=&tra;}
1328 	do_transform(&p2, tr, &cur_sel.p);
1329 	if (fabs(p2.x)+fabs(p2.y)>1e8 || (pt2.x=p2.x, pt2.y=p2.y, is_off_screen(pt2)))
1330 		{cur_sel.t= -1; return;}
1331 	pt3.x=pt2.x-Dotrad;  pt3.y=pt2.y-Dotrad;
1332 	draw(sel_bkg, sel_dot_box(0), screen, display->opaque, pt3);
1333 	fillellipse(screen, pt2, Dotrad, Dotrad, clr_im(DRed), pt2);
1334 	say_where(tr);
1335 }
1336 
1337 
1338 void do_select(Point pt)
1339 {
1340 	transform tr = cur_trans();
1341 	fpoint pt1, pt2, ctr;
1342 	frectangle r;
1343 	double slant;
1344 	pt_on_fpoly* psel;
1345 	unselect(&tr);
1346 	do_untransform(&ctr, &tr, &pt);
1347 	pt1.x=pt.x-fuzz;  pt1.y=pt.y+fuzz;
1348 	pt2.x=pt.x+fuzz;  pt2.y=pt.y-fuzz;
1349 	do_untransform(&r.min, &tr, &pt1);
1350 	do_untransform(&r.max, &tr, &pt2);
1351 	slant = u_slant_amt(&univ);
1352 	slant_frect(&r, -slant);
1353 	psel = select_in_univ(&r, slant);
1354 	if (psel==0)
1355 		return;
1356 	if (logfil!=0) {
1357 		fprintf(logfil,"%.14g\t%.14g\n", psel->p.x, psel->p.y);
1358 		fflush(logfil);
1359 	}
1360 	cur_sel = *psel;
1361 	reselect(&tr);
1362 }
1363 
1364 
1365 /***************************** Prompting for text *****************************/
1366 
1367 void unshow_mytext(char* msg)
1368 {
1369 	Rectangle r;
1370 	Point siz = stringsize(display->defaultfont, msg);
1371 	top_left -= siz.x;
1372 	r.min.y = screen->r.min.y + outersep;
1373 	r.min.x = top_left;
1374 	r.max.y = r.min.y + siz.y;
1375 	r.max.x = r.min.x + siz.x;
1376 	draw(screen, r, display->white, display->opaque, r.min);
1377 }
1378 
1379 
1380 /* Show the given prompt and read a line of user input.  The text appears at the
1381    top left.  If it runs into the top right text, we stop echoing but let the user
1382    continue typing blind if he wants to.
1383 */
1384 char* prompt_text(char* prompt)
1385 {
1386 	static char buf[200];
1387 	int n0, n=0, nshown=0;
1388 	Rune c;
1389 	unselect(0);
1390 	show_mytext(prompt);
1391 	while (n<200-1-UTFmax && (c=ekbd())!='\n') {
1392 		if (c=='\b') {
1393 			buf[n] = 0;
1394 			if (n > 0)
1395 				do n--;
1396 				while (n>0 && (buf[n-1]&0xc0)==0x80);
1397 			if (n < nshown)
1398 				{unshow_mytext(buf+n); nshown=n;}
1399 		} else {
1400 			n0 = n;
1401 			n += runetochar(buf+n, &c);
1402 			buf[n] = 0;
1403 			if (nshown==n0 && top_right-top_left >= smaxch.x)
1404 				{show_mytext(buf+n0); nshown=n;}
1405 		}
1406 	}
1407 	buf[n] = 0;
1408 	while (ecanmouse())
1409 		emouse();
1410 	return buf;
1411 }
1412 
1413 
1414 /**************************** Redrawing the screen ****************************/
1415 
1416 /* Let p0 and its successors define a piecewise-linear function of a paramter t,
1417    and draw the 0<=t<=n1 portion using transform *tr.
1418 */
1419 void draw_fpts(const fpoint* p0, double n1, const transform* tr, int thick,
1420 		Image* clr)
1421 {
1422 	int n = (int) n1;
1423 	const fpoint* p = p0 + n;
1424 	fpoint pp;
1425 	Point qq, q;
1426 	if (n1 > n) {
1427 		pp.x = p[0].x + (n1-n)*(p[1].x - p[0].x);
1428 		pp.y = p[0].y + (n1-n)*(p[1].y - p[0].y);
1429 	} else	pp = *p--;
1430 	do_transform(&qq, tr, &pp);
1431 	if (n1==0)
1432 		fillellipse(screen, qq, 1+thick, 1+thick, clr, qq);
1433 	for (; p>=p0; p--) {
1434 		do_transform(&q, tr, p);
1435 		line(screen, qq, q, Enddisc, Enddisc, thick, clr, qq);
1436 		qq = q;
1437 	}
1438 }
1439 
1440 void draw_1fpoly(const fpolygon* fp, const transform* tr, Image* clr,
1441 		const frectangle *udisp, double slant)
1442 {
1443 	fpoint *p0=fp->p, *pn=fp->p+fp->n;
1444 	double l1, l2;
1445 	if (p0==pn && fcontains(udisp,*p0))
1446 		{draw_fpts(p0, 0, tr, fp->thick, clr); return;}
1447 	while ((l1=out_length(p0,pn,*udisp,slant)) < pn-p0) {
1448 		fpoint p0sav;
1449 		int i1 = (int) l1;
1450 		p0+=i1; l1-=i1;
1451 		p0sav = *p0;
1452 		p0[0].x += l1*(p0[1].x - p0[0].x);
1453 		p0[0].y += l1*(p0[1].y - p0[0].y);
1454 		l2 = in_length(p0, pn, *udisp, slant);
1455 		draw_fpts(p0, l2, tr, fp->thick, clr);
1456 		*p0 = p0sav;
1457 		p0 += (l2>0) ? ((int) ceil(l2)) : 1;
1458 	}
1459 }
1460 
1461 
1462 double get_clip_data(const fpolygons *u, frectangle *r)
1463 {
1464 	double slant = set_unslanted_y(u, &r->min.y, &r->max.y);
1465 	r->min.x = u->disp.min.x;
1466 	r->max.x = u->disp.max.x;
1467 	return slant;
1468 }
1469 
1470 
1471 void draw_fpoly(const fpolygon* fp, const transform* tr, Image* clr)
1472 {
1473 	frectangle r;
1474 	double slant = get_clip_data(&univ, &r);
1475 	draw_1fpoly(fp, tr, clr, &r, slant);
1476 }
1477 
1478 
1479 void eresized(int new)
1480 {
1481 	transform tr;
1482 	fpolygon* fp;
1483 	frectangle clipr;
1484 	double slant;
1485 	if(new && getwindow(display, Refmesg) < 0) {
1486 		fprintf(stderr,"can't reattach to window\n");
1487 		exits("reshap");
1488 	}
1489 	draw(screen, screen->r, display->white, display->opaque, screen->r.min);
1490 	tr = draw_frame();
1491 	slant = get_clip_data(&univ, &clipr);
1492 	for (fp=univ.p; fp!=0; fp=fp->link)
1493 		if (fintersects(&clipr, &fp->bb, slant))
1494 			draw_1fpoly(fp, &tr, fp->clr, &clipr, slant);
1495 	reselect(0);
1496 	if (mv_bkgd!=0 && mv_bkgd->repl==0) {
1497 		freeimage(mv_bkgd);
1498 		mv_bkgd = display->white;
1499 	}
1500 	flushimage(display, 1);
1501 }
1502 
1503 
1504 
1505 
1506 /********************************* Recoloring *********************************/
1507 
1508 int draw_palette(int n)		/* n is number of colors; returns patch dy */
1509 {
1510 	int y0 = screen->r.min.y + top_border;
1511 	int dy = (screen->r.max.y - bot_border - y0)/n;
1512 	Rectangle r;
1513 	int i;
1514 	r.min.y = y0;
1515 	r.min.x = screen->r.max.x - rt_border + framewd;
1516 	r.max.y = y0 + dy;
1517 	r.max.x = screen->r.max.x;
1518 	for (i=0; i<n; i++) {
1519 		draw(screen, r, clrtab[i].im, display->opaque, r.min);
1520 		r.min.y = r.max.y;
1521 		r.max.y += dy;
1522 	}
1523 	return dy;
1524 }
1525 
1526 
1527 Image* palette_color(Point pt, int dy, int n)
1528 {				/* mouse at pt, patch size dy, n colors */
1529 	int yy;
1530 	if (screen->r.max.x - pt.x > rt_border - framewd)
1531 		return 0;
1532 	yy = pt.y - (screen->r.min.y + top_border);
1533 	if (yy<0 || yy>=n*dy)
1534 		return 0;
1535 	return clrtab[yy/dy].im;
1536 }
1537 
1538 
1539 void all_set_clr(fpolygons* fps, Image* clr)
1540 {
1541 	fpolygon* p;
1542 	for (p=fps->p; p!=0; p=p->link)
1543 		p->clr = clr;
1544 }
1545 
1546 
1547 void do_recolor(int but, Mouse* m, int alluniv)
1548 {
1549 	int nclr = clr_id(DWhite);
1550 	int dy = draw_palette(nclr);
1551 	Image* clr;
1552 	if (!get_1click(but, m, 0)) {
1553 		eresized(0);
1554 		return;
1555 	}
1556 	clr = palette_color(m->xy, dy, nclr);
1557 	if (clr != 0) {
1558 		if (alluniv)
1559 			all_set_clr(&univ, clr);
1560 		else	cur_sel.fp->clr = clr;
1561 	}
1562 	eresized(0);
1563 	lift_button(but, m, Never);
1564 }
1565 
1566 
1567 /****************************** Move and rotate  ******************************/
1568 
1569 void prepare_mv(const fpolygon* fp)
1570 {
1571 	Rectangle r = screen->r;
1572 	Image* scr0;
1573 	int dt = 1 + fp->thick;
1574 	r.min.x+=lft_border-dt;  r.min.y+=top_border-dt;
1575 	r.max.x-=rt_border-dt;   r.max.y-=bot_border-dt;
1576 	if (mv_bkgd!=0 && mv_bkgd->repl==0)
1577 		freeimage(mv_bkgd);
1578 	mv_bkgd = allocimage(display, r, CMAP8, 0, DNofill);
1579 	if (mv_bkgd==0)
1580 		mv_bkgd = display->white;
1581 	else {	transform tr = cur_trans();
1582 		draw(mv_bkgd, r, screen, display->opaque, r.min);
1583 		draw(mv_bkgd, sel_dot_box(&tr), sel_bkg, display->opaque, ZP);
1584 		scr0 = screen;
1585 		screen = mv_bkgd;
1586 		draw_fpoly(fp, &tr, display->white);
1587 		screen = scr0;
1588 	}
1589 }
1590 
1591 
1592 void move_fp(fpolygon* fp, double dx, double dy)
1593 {
1594 	fpoint *p, *pn=fp->p+fp->n;
1595 	for (p=fp->p; p<=pn; p++) {
1596 		(p->x) += dx;
1597 		(p->y) += dy;
1598 	}
1599 	(fp->bb.min.x)+=dx;  (fp->bb.min.y)+=dy;
1600 	(fp->bb.max.x)+=dx;  (fp->bb.max.y)+=dy;
1601 }
1602 
1603 
1604 void rotate_fp(fpolygon* fp, fpoint o, double theta)
1605 {
1606 	double s=sin(theta), c=cos(theta);
1607 	fpoint *p, *pn=fp->p+fp->n;
1608 	for (p=fp->p; p<=pn; p++) {
1609 		double x=p->x-o.x, y=p->y-o.y;
1610 		(p->x) = o.x + c*x - s*y;
1611 		(p->y) = o.y + s*x + c*y;
1612 	}
1613 	set_fbb(fp);
1614 }
1615 
1616 
1617 /* Move the selected fpolygon so the selected point tracks the mouse, and return
1618    the total amount of movement.  Button but has already been held down for at
1619    least Mv_delay milliseconds and the mouse might have moved some distance.
1620 */
1621 fpoint do_move(int but, Mouse* m)
1622 {
1623 	transform tr = cur_trans();
1624 	int bbit = Button_bit(but);
1625 	fpolygon* fp = cur_sel.fp;
1626 	fpoint loc, loc0=cur_sel.p;
1627 	double tsav = cur_sel.t;
1628 	unselect(&tr);
1629 	do {	latest_mouse(but, m);
1630 		(fp->thick)++;		/* line() DISAGREES WITH ITSELF */
1631 		draw_fpoly(fp, &tr, mv_bkgd);
1632 		(fp->thick)--;
1633 		do_untransform(&loc, &tr, &m->xy);
1634 		move_fp(fp, loc.x-cur_sel.p.x, loc.y-cur_sel.p.y);
1635 		cur_sel.p = loc;
1636 		draw_fpoly(fp, &tr, fp->clr);
1637 	} while (m->buttons & bbit);
1638 	cur_sel.t = tsav;
1639 	reselect(&tr);
1640 	loc.x -= loc0.x;
1641 	loc.y -= loc0.y;
1642 	return loc;
1643 }
1644 
1645 
1646 double dir_angle(const Point* pt, const transform* tr)
1647 {
1648 	fpoint p;
1649 	double dy, dx;
1650 	do_untransform(&p, tr, pt);
1651 	dy=p.y-cur_sel.p.y;  dx=p.x-cur_sel.p.x;
1652 	return (dx==0 && dy==0) ? 0.0 : atan2(dy, dx);
1653 }
1654 
1655 
1656 /* Rotate the selected fpolygon around the selection point so as to track the
1657    direction angle from the selected point to m->xy.  Stop when button but goes
1658    up and return the total amount of rotation in radians.
1659 */
1660 double do_rotate(int but, Mouse* m)
1661 {
1662 	transform tr = cur_trans();
1663 	int bbit = Button_bit(but);
1664 	fpolygon* fp = cur_sel.fp;
1665 	double theta0 = dir_angle(&m->xy, &tr);
1666 	double th, theta = theta0;
1667 	do {	latest_mouse(but, m);
1668 		(fp->thick)++;		/* line() DISAGREES WITH ITSELF */
1669 		draw_fpoly(fp, &tr, mv_bkgd);
1670 		(fp->thick)--;
1671 		th = dir_angle(&m->xy, &tr);
1672 		rotate_fp(fp, cur_sel.p, th-theta);
1673 		theta = th;
1674 		draw_fpoly(fp, &tr, fp->clr);
1675 	} while (m->buttons & bbit);
1676 	unselect(&tr);
1677 	cur_sel = prev_sel;
1678 	reselect(&tr);
1679 	return theta - theta0;
1680 }
1681 
1682 
1683 
1684 /********************************* Edit menu  *********************************/
1685 
1686 typedef enum e_index {
1687 		Erecolor, Ethick, Edelete, Eundo, Erotate, Eoptions,
1688 		Emove
1689 } e_index;
1690 
1691 char* e_items[Eoptions+1];
1692 
1693 Menu e_menu = {e_items, 0, 0};
1694 
1695 
1696 typedef struct e_action {
1697 	e_index typ;			/* What type of action */
1698 	fpolygon* fp;			/* fpolygon the action applies to */
1699 	Image* clr;			/* color to use if typ==Erecolor */
1700 	double amt;			/* rotation angle or line thickness */
1701 	fpoint pt;			/* movement vector or rotation center */
1702 	struct e_action* link;		/* next in a stack */
1703 } e_action;
1704 
1705 e_action* unact = 0;			/* heads a linked list of actions */
1706 e_action* do_undo(e_action*);		/* pop off an e_action and (un)do it */
1707 e_action* save_act(e_action*,e_index);	/* append new e_action for status quo */
1708 
1709 
1710 void save_mv(fpoint movement)
1711 {
1712 	unact = save_act(unact, Emove);
1713 	unact->pt = movement;
1714 }
1715 
1716 
1717 void init_e_menu(void)
1718 {
1719 	char* u = "can't undo";
1720 	e_items[Erecolor] = "recolor";
1721 	e_items[Edelete] = "delete";
1722 	e_items[Erotate] = "rotate";
1723 	e_items[Eoptions-cantmv] = 0;
1724 	e_items[Ethick] = (cur_sel.fp->thick >0) ? "thin" : "thick";
1725 	if (unact!=0)
1726 		switch (unact->typ) {
1727 		case Erecolor: u="uncolor"; break;
1728 		case Ethick: u=(unact->fp->thick==0) ? "unthin" : "unthicken";
1729 			break;
1730 		case Edelete: u="undelete"; break;
1731 		case Emove: u="unmove"; break;
1732 		case Erotate: u="unrotate"; break;
1733 		}
1734 	e_items[Eundo] = u;
1735 }
1736 
1737 
1738 void do_emenu(int but, Mouse* m)
1739 {
1740 	int h;
1741 	if (cur_sel.t < 0)
1742 		return;
1743 	init_e_menu();
1744 	h = emenuhit(but, m, &e_menu);
1745 	switch(h) {
1746 	case Ethick: unact = save_act(unact, h);
1747 		cur_sel.fp->thick ^= 1;
1748 		eresized(0);
1749 		break;
1750 	case Edelete: unact = save_act(unact, h);
1751 		fp_remove(&univ, cur_sel.fp);
1752 		unselect(0);
1753 		eresized(0);
1754 		break;
1755 	case Erecolor: unact = save_act(unact, h);
1756 		do_recolor(but, m, 0);
1757 		break;
1758 	case Erotate: unact = save_act(unact, h);
1759 		prepare_mv(cur_sel.fp);
1760 		if (get_1click(but, m, 0)) {
1761 			unact->pt = cur_sel.p;
1762 			unact->amt = do_rotate(but, m);
1763 		}
1764 		break;
1765 	case Eundo: unact = do_undo(unact);
1766 		break;
1767 	}
1768 }
1769 
1770 
1771 
1772 /******************************* Undoing edits  *******************************/
1773 
1774 e_action* save_act(e_action* a0, e_index typ)
1775 {					/* append new e_action for status quo */
1776 	e_action* a = malloc(sizeof(e_action));
1777 	a->link = a0;
1778 	a->pt.x = a->pt.y = 0.0;
1779 	a->amt = cur_sel.fp->thick;
1780 	a->clr = cur_sel.fp->clr;
1781 	a->fp = cur_sel.fp;
1782 	a->typ = typ;
1783 	return a;
1784 }
1785 
1786 
1787 /* This would be trivial except it's nice to preserve the selection in order to make
1788    it easy to undo a series of moves.  (There's no do_unrotate() because it's harder
1789    and less important to preserve the selection in that case.)
1790 */
1791 void do_unmove(e_action* a)
1792 {
1793 	double tsav = cur_sel.t;
1794 	unselect(0);
1795 	move_fp(a->fp, -a->pt.x, -a->pt.y);
1796 	if (a->fp == cur_sel.fp) {
1797 		cur_sel.p.x -= a->pt.x;
1798 		cur_sel.p.y -= a->pt.y;
1799 	}
1800 	cur_sel.t = tsav;
1801 	reselect(0);
1802 }
1803 
1804 
1805 e_action* do_undo(e_action* a0)		/* pop off an e_action and (un)do it */
1806 {
1807 	e_action* a = a0;
1808 	if (a==0)
1809 		return 0;
1810 	switch(a->typ) {
1811 	case Ethick: a->fp->thick = a->amt;
1812 		eresized(0);
1813 		break;
1814 	case Erecolor: a->fp->clr = a->clr;
1815 		eresized(0);
1816 		break;
1817 	case Edelete:
1818 		a->fp->link = univ.p;
1819 		univ.p = a->fp;
1820 		grow_bb(&univ.bb, &a->fp->bb);
1821 		eresized(0);
1822 		break;
1823 	case Emove:
1824 		do_unmove(a);
1825 		eresized(0);
1826 		break;
1827 	case Erotate:
1828 		unselect(0);
1829 		rotate_fp(a->fp, a->pt, -a->amt);
1830 		eresized(0);
1831 		break;
1832 	}
1833 	a0 = a->link;
1834 	free(a);
1835 	return a0;
1836 }
1837 
1838 
1839 
1840 /********************************* Main menu  *********************************/
1841 
1842 enum m_index {     Mzoom_in,  Mzoom_out,  Munzoom,  Mslant,    Munslant,
1843 		Msquare_up,  Mrecenter,  Mrecolor,  Mrestack,  Mread,
1844 		Mwrite,      Mexit};
1845 char* m_items[] = {"zoom in", "zoom out", "unzoom", "slant",   "unslant",
1846 		"square up", "recenter", "recolor", "restack", "read",
1847 		"write",     "exit", 0};
1848 
1849 Menu m_menu = {m_items, 0, 0};
1850 
1851 
1852 void do_mmenu(int but, Mouse* m)
1853 {
1854 	int e, h = emenuhit(but, m, &m_menu);
1855 	switch (h) {
1856 	case Mzoom_in:
1857 		disp_zoomin(egetrect(but,m));
1858 		eresized(0);
1859 		break;
1860 	case Mzoom_out:
1861 		disp_zoomout(egetrect(but,m));
1862 		eresized(0);
1863 		break;
1864 	case Msquare_up:
1865 		disp_squareup();
1866 		eresized(0);
1867 		break;
1868 	case Munzoom:
1869 		init_disp();
1870 		eresized(0);
1871 		break;
1872 	case Mrecenter:
1873 		if (get_1click(but, m, &bullseye)) {
1874 			recenter_disp(m->xy);
1875 			eresized(0);
1876 			lift_button(but, m, Never);
1877 		}
1878 		break;
1879 	case Mslant:
1880 		if (cur_sel.t>=0 && prev_sel.t>=0) {
1881 			slant_disp(prev_sel.p, cur_sel.p);
1882 			eresized(0);
1883 		}
1884 		break;
1885 	case Munslant:
1886 		univ.slant_ht = univ.disp.max.y - univ.disp.min.y;
1887 		eresized(0);
1888 		break;
1889 	case Mrecolor:
1890 		do_recolor(but, m, 1);
1891 		break;
1892 	case Mrestack:
1893 		fps_invert(&univ);
1894 		eresized(0);
1895 		break;
1896 	case Mread:
1897 		e = doinput(prompt_text("File:"));
1898 		if (e==0)
1899 			eresized(0);
1900 		else if (e<0)
1901 			show_mytext(" - can't read");
1902 		else {
1903 			char ebuf[80];
1904 			snprintf(ebuf, 80, " - error line %d", e);
1905 			show_mytext(ebuf);
1906 		}
1907 		break;
1908 	case Mwrite:
1909 		if (!dooutput(prompt_text("File:")))
1910 			show_mytext(" - can't write");
1911 		break;
1912 	case Mexit:
1913 		exits("");
1914 	}
1915 }
1916 
1917 
1918 
1919 /****************************** Handling events  ******************************/
1920 
1921 void doevent(void)
1922 {
1923 	ulong etype;
1924 	int mobile;
1925 	ulong mvtime;
1926 	Event	ev;
1927 
1928 	etype = eread(Emouse|Ekeyboard, &ev);
1929 	if(etype & Emouse) {
1930 		if (ev.mouse.buttons & But1) {
1931 			do_select(ev.mouse.xy);
1932 			mvtime = Never;
1933 			mobile = !cantmv && cur_sel.t>=0;
1934 			if (mobile) {
1935 				mvtime = ev.mouse.msec + Mv_delay;
1936 				prepare_mv(cur_sel.fp);
1937 			}
1938 			if (!lift_button(1, &ev.mouse, mvtime) && mobile)
1939 				save_mv(do_move(1, &ev.mouse));
1940 		} else if (ev.mouse.buttons & But2)
1941 			do_emenu(2, &ev.mouse);
1942 		else if (ev.mouse.buttons & But3)
1943 			do_mmenu(3, &ev.mouse);
1944 	}
1945 	/* no need to check (etype & Ekeyboard)--there are no keyboard commands */
1946 }
1947 
1948 
1949 
1950 /******************************** Main program ********************************/
1951 
1952 extern char* argv0;
1953 
1954 void usage(void)
1955 {
1956 	int i;
1957 	fprintf(stderr,"Usage %s [options] [infile]\n", argv0);
1958 	fprintf(stderr,
1959 "option ::= -l logfile | -m\n"
1960 "\n"
1961 "Read a polygonal line graph in an ASCII format (one x y pair per line, delimited\n"
1962 "by spaces with a label after each polyline), and view it interactively.  Use\n"
1963 "standard input if no infile is specified.\n"
1964 "Option -l specifies a file in which to log the coordinates of each point selected.\n"
1965 "(Clicking a point with button one selects it and displays its coordinates and\n"
1966 "the label of its polylone.)  Option -m allows polylines to be moved and rotated.\n"
1967 "The polyline labels can use the following color names:"
1968 	);
1969 	for (i=0; clrtab[i].c!=DNofill; i++)
1970 		fprintf(stderr,"%s%8s", (i%8==0 ? "\n" : "  "), clrtab[i].nam);
1971 	fputc('\n', stderr);
1972 	exits("usage");
1973 }
1974 
1975 void main(int argc, char *argv[])
1976 {
1977 	int e;
1978 
1979 	ARGBEGIN {
1980 	case 'm': cantmv=0;
1981 		break;
1982 	case 'l': logfil = fopen(ARGF(),"w");
1983 		break;
1984 	default: usage();
1985 	} ARGEND
1986 
1987 	if(initdraw(0, 0, "gview") < 0)
1988 		exits("initdraw");
1989 	einit(Emouse|Ekeyboard);
1990 
1991 	do {
1992 		e = doinput(*argv ? *argv : "-");
1993 		if (e < 0) {
1994 			fprintf(stderr,"Cannot read input file %s\n", *argv);
1995 			exits("no valid input file");
1996 		} else if (e > 0) {
1997 			fprintf(stderr,"Bad syntax at line %d of file %s\n", e, *argv ? *argv : "-");
1998 			exits("bad syntax in input");
1999 		}
2000 	} while (*argv && *++argv);
2001 	init_disp();
2002 	init_clrtab();
2003 	set_default_clrs(&univ, 0);
2004 	adjust_border(display->defaultfont);
2005 	cur_sel.t = prev_sel.t = -1;
2006 	eresized(0);
2007 	for(;;)
2008 		doevent();
2009 }
2010