Subversion Repositories planix.SVN

Rev

Rev 2 | Details | Compare with Previous | Last modification | View Log | RSS feed

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