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