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