]> git.lizzy.rs Git - plan9front.git/blob - sys/src/cmd/gview.c
amd64, vmx: support avx/avx2 for host/guest; use *noavx= in plan9.ini to disable
[plan9front.git] / sys / src / cmd / gview.c
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 }