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