| ---
tgview.c (50773B)
---
1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8
9 #define Never 0xffffffff /* Maximum ulong */
10 #define LOG2 0.301029995664
11 #define Button_bit(b) (1 << ((b)-1))
12
13 enum {
14 But1 = Button_bit(1),/* mouse buttons for events */
15 But2 = Button_bit(2),
16 But3 = Button_bit(3)
17 };
18 int cantmv = 1; /* disallow rotate and move? 0..1 */
19 int top_border, bot_border, lft_border, rt_border;
20 int lft_border0; /* lft_border for y-axis labels >0 */
21 int top_left, top_right; /* edges of top line free space */
22 int Mv_delay = 400; /* msec for button click vs. button hold down */
23 int Dotrad = 2; /* dot radius in pixels */
24 int framewd=1; /* line thickness for frame (pixels) */
25 int framesep=1; /* distance between frame and surrounding text */
26 int outersep=1; /* distance: surrounding text to screen edge */
27 Point sdigit; /* size of a digit in the font */
28 Point smaxch; /* assume any character in font fits in this */
29 double underscan = .05; /* fraction of frame initially unused per side */
30 double fuzz = 6; /* selection tolerance in pixels */
31 int tick_len = 15; /* length of axis label tick mark in pixels */
32 FILE* logfil = 0; /* dump selected points here if nonzero */
33
34 #define labdigs 3 /* allow this many sig digits in axis labels */
35 #define digs10pow 1000 /* pow(10,labdigs) */
36 #define axis_color clr_im(DLtblue)
37
38
39
40
41 /********************************* Utilities *********************************/
42
43 /* Prepend string s to null-terminated string in n-byte buffer buf[], truncating if
44 necessary and using a space to separate s from the rest of buf[].
45 */
46 char* str_insert(char* buf, char* s, int n)
47 {
48 int blen, slen = strlen(s) + 1;
49 if (slen >= n)
50 {strncpy(buf,s,n); buf[n-1]='\0'; return buf;}
51 blen = strlen(buf);
52 if (blen >= n-slen)
53 buf[blen=n-slen-1] = '\0';
54 memmove(buf+slen, buf, slen+blen+1);
55 memcpy(buf, s, slen-1);
56 buf[slen-1] = ' ';
57 return buf;
58 }
59
60 /* Alter string smain (without lengthening it) so as to remove the first occurrence of
61 ssub, assuming ssub is ASCII. Return nonzero (true) if string smain had to be changed.
62 In spite of the ASCII-centric appearance, I think this can handle UTF in smain.
63 */
64 int remove_substr(char* smain, char* ssub)
65 {
66 char *ss, *s = strstr(smain, ssub);
67 int n = strlen(ssub);
68 if (s==0)
69 return 0;
70 if (islower((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
80 void adjust_border(Font* f)
81 {
82 int sep = framesep + outersep;
83 sdigit = stringsize(f, "8");
84 smaxch = stringsize(f, "MMMg");
85 smaxch.x = (smaxch.x + 3)/4;
86 lft_border0 = (1+labdigs)*sdigit.x + framewd + sep;
87 rt_border = (lft_border0 - sep)/2 + outersep;
88 bot_border = sdigit.y + framewd + sep;
89 top_border = smaxch.y + framewd + sep;
90 lft_border = lft_border0; /* this gets reset later */
91 }
92
93
94 int is_off_screen(Point p)
95 {
96 const Rectangle* r = &(screen->r);
97 return p.x-r->min.xmax.x-p.xmin.y<=top_border || r->max.y-p.y<=bot_border;
99 }
100
101
102 Cursor bullseye =
103 {
104 {-7, -7},
105 {
106 0x1F, 0xF8, 0x3F, 0xFC, 0x7F, 0xFE, 0xFB, 0xDF,
107 0xF3, 0xCF, 0xE3, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF,
108 0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xC7, 0xF3, 0xCF,
109 0x7B, 0xDF, 0x7F, 0xFE, 0x3F, 0xFC, 0x1F, 0xF8,
110 },
111 {
112 0x00, 0x00, 0x0F, 0xF0, 0x31, 0x8C, 0x21, 0x84,
113 0x41, 0x82, 0x41, 0x82, 0x41, 0x82, 0x7F, 0xFE,
114 0x7F, 0xFE, 0x41, 0x82, 0x41, 0x82, 0x41, 0x82,
115 0x21, 0x84, 0x31, 0x8C, 0x0F, 0xF0, 0x00, 0x00,
116 }
117 };
118
119 int get_1click(int but, Mouse* m, Cursor* curs)
120 {
121 if (curs)
122 esetcursor(curs);
123 while (m->buttons==0)
124 *m = emouse();
125 if (curs)
126 esetcursor(0);
127 return (m->buttons==Button_bit(but));
128 }
129
130
131 /* Wait until but goes up or until a mouse event's msec passes tlimit.
132 Return a boolean result that tells whether the button went up.
133 */
134 int lift_button(int but, Mouse* m, int tlimit)
135 {
136 do { *m = emouse();
137 if (m->msec >= tlimit)
138 return 0;
139 } while (m->buttons & Button_bit(but));
140 return 1;
141 }
142
143
144 /* Set *m to the last pending mouse event, or the first one where but is up.
145 If no mouse events are pending, wait for the next one.
146 */
147 void latest_mouse(int but, Mouse* m)
148 {
149 int bbit = Button_bit(but);
150 do { *m = emouse();
151 } while ((m->buttons & bbit) && ecanmouse());
152 }
153
154
155
156 /*********************************** Colors ***********************************/
157
158 #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
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
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
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 */
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 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 */
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
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
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
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
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
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
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
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
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
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 */
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
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
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
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 */
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
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
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
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
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
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 */
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
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
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
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 */
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 */
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 */
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
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
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
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
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 */
823 int do_xory(double x0, double x1, double xlo, double xhi, double* t0, double* t1)
824 {
825 *t1 = 1.0;
826 if (x0xhi) *t1 = (xhi-x0)/(x1-x0);
830 } else if (x0>xhi) {
831 if (x1>xhi) return 0;
832 *t0 = (xhi-x0)/(x1-x0);
833 if (x1xhi) *t1 = (xhi-x0)/(x1-x0);
837 else if (x1x, 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 */
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 */
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
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
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= digs10pow) {
941 rr /= 10;
942 nn = (n= 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
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
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
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
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
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
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 */
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 */
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 */
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 */
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
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
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
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 */
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
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
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= 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
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
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
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 */
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 */
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
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
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
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
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
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; iopaque, r.min);
1525 r.min.y = r.max.y;
1526 r.max.y += dy;
1527 }
1528 return dy;
1529 }
1530
1531
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
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
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
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
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
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 */
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
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 */
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
1715 void save_mv(fpoint movement)
1716 {
1717 unact = save_act(unact, Emove);
1718 unact->pt = movement;
1719 }
1720
1721
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
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
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 */
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
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
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
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
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
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 } |