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