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 */
str_insert(char * buf,char * s,int n)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 */
remove_substr(char * smain,char * ssub)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
adjust_border(Font * f)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
is_off_screen(Point p)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) */
get_1click(int but,Mouse * m,Cursor * curs)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 */
get_click_or_kbd(int but,Mouse * m,const char * expected)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 */
lift_button(int but,Mouse * m,int tlimit)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 */
latest_mouse(int but,Mouse * m)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
init_clrtab(void)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
clrim_id(Image * clr)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
clr_id(int clr)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
tc_default(thick_color * buf)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 */
parse_color_chars(const char * c0,const char * fin,thick_color * buf)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 */
nam2thclr(const char * nam,thick_color * r1,int * idxdest)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 */
nam_with_thclr(char * nam,const thick_color * tc,char * buf,int bufn)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 */
fintersects(const frectangle * r1,const frectangle * r2,double slant)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
fcontains(const frectangle * r,fpoint p)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
grow_bb(frectangle * dest,const frectangle * r)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
slant_frect(frectangle * r,double sl)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
fcenter(const frectangle * r)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
free_fp_etc(fpolygon * fp)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
set_default_clrs(fpolygons * fps,fpolygon * fpstop)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
fps_invert(fpolygons * fps)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
fp_remove(fpolygons * fps,fpolygon * fp)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
cur_trans(void)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
u_slant_amt(fpolygons * u)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 */
set_unslanted_y(fpolygons * u,double * y0,double * y1)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
nontrivial_interval(double * lo,double * hi)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
init_disp(void)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
recenter_disp(Point c)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 */
untransform_corners(double rminx,double rminy,double rmaxx,double rmaxy,fpoint * ul,fpoint * lr)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
disp_dozoom(double rminx,double rminy,double rmaxx,double rmaxy)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
disp_zoomin(Rectangle r)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
disp_zoomout(Rectangle r)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
expand2(double * a,double * b,double f)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
disp_squareup(void)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 */
slant_disp(fpoint p,fpoint q)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
set_fbb(fpolygon * fp)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
mystrdup(char * s)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
is_valid_label(char * lab)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 */
rd_fpoly(FILE * fin,int * lineno)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 */
rd_fpolys(FILE * fin,fpolygons * fps)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 */
doinput(char * fnam)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
fp_reverse(fpolygon * fp)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
wr_fpoly(FILE * fout,const fpolygon * fp)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
wr_fpolys(FILE * fout,fpolygons * fps)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
dooutput(char * fnam)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 */
do_xory(double x0,double x1,double xlo,double xhi,double * t0,double * t1)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 */
frac_outside(const fpoint * p,const fpoint * q,const frectangle * r,double slope)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 */
in_length(const fpoint * p0,const fpoint * pn,frectangle r,double slope)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 */
out_length(const fpoint * p0,const fpoint * pn,frectangle r,double slope)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
abbrev_num(double x,const lab_interval * iv)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
lead_digits(double n,double r)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
next_larger(double s0,double xlo,double xhi)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
min_hsep(const transform * tr)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
mark_x_axis(const transform * tr)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
mark_y_axis(const transform * tr)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
lab_iv_info(const lab_interval * iv,double slant,char * buf,int * n)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
draw_xy_ranges(const lab_interval * xiv,const lab_interval * yiv)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
draw_frame(void)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 */
closest_time(const fpoint * p0,const fpoint * ctr,double slant,double xwt,double ywt)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 */
improve_pt(fpoint * p0,double len,const frectangle * r,double slant,pt_on_fpoly * psel)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 */
select_in_fpoly(fpolygon * fp,const frectangle * r,double slant,pt_on_fpoly * psel)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 */
select_in_univ(const frectangle * r,double slant)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
clear_txt(void)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
sel_dot_box(const transform * tr)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
unselect(const transform * tr)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 */
show_mytext(char * msg)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
rnd(double x,double tol)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
t_tol(double xtol,double ytol)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
say_where(const transform * tr)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
reselect(const transform * tr)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
do_select(Point pt)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
unshow_mytext(char * msg)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 */
prompt_text(char * prompt)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 */
draw_fpts(const fpoint * p0,double n1,const transform * tr,int thick,Image * clr)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
draw_1fpoly(const fpolygon * fp,const transform * tr,Image * clr,const frectangle * udisp,double slant)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
get_clip_data(const fpolygons * u,frectangle * r)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
draw_fpoly(const fpolygon * fp,const transform * tr,Image * clr)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
eresized(int new)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
draw_palette(int n)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
palette_color(Point pt,int dy,int n)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
all_set_clr(fpolygons * fps,Image * clr)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
all_set_scheme(fpolygons * fps,int scheme)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
do_recolor(int but,Mouse * m,int alluniv)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
prepare_mv(const fpolygon * fp)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
move_fp(fpolygon * fp,double dx,double dy)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
rotate_fp(fpolygon * fp,fpoint o,double theta)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 */
do_move(int but,Mouse * m)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
dir_angle(const Point * pt,const transform * tr)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 */
do_rotate(int but,Mouse * m)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
save_mv(fpoint movement)1833 void save_mv(fpoint movement)
1834 {
1835 unact = save_act(unact, Emove);
1836 unact->pt = movement;
1837 }
1838
1839
init_e_menu(void)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
do_emenu(int but,Mouse * m)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
save_act(e_action * a0,e_index typ)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 */
do_unmove(e_action * a)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
do_undo(e_action * a0)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
do_mmenu(int but,Mouse * m)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
doevent(void)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
usage(void)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
main(int argc,char * argv[])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 }
2148