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