#include <u.h>
#include <libc.h>
#include <draw.h>
#include <cursor.h>
#include <event.h>
#include <bio.h>

typedef struct  Thing   Thing;

struct Thing
{
       Image   *b;
       Subfont         *s;
       char            *name;  /* file name */
       int             face;           /* is 48x48 face file or cursor file*/
       Rectangle r;            /* drawing region */
       Rectangle tr;           /* text region */
       Rectangle er;           /* entire region */
       long            c;              /* character number in subfont */
       int             mod;    /* modified */
       int             mag;            /* magnification */
       Rune            off;            /* offset for subfont indices */
       Thing   *parent;        /* thing of which i'm an edit */
       Thing   *next;
};

enum
{
       Border  = 1,
       Up              = 1,
       Down    = 0,
       Mag             = 4,
       Maxmag  = 10,
};

enum
{
       NORMAL  =0,
       FACE    =1,
       CURSOR  =2
};

enum
{
       Mopen,
       Mread,
       Mwrite,
       Mcopy,
       Mchar,
       Mpixels,
       Mclose,
       Mexit,
};

enum
{
       Blue    = 54,
};

char    *menu3str[] = {
       [Mopen] "open",
       [Mread] "read",
       [Mwrite]        "write",
       [Mcopy] "copy",
       [Mchar] "char",
       [Mpixels]       "pixels",
       [Mclose]        "close",
       [Mexit] "exit",
       0,
};

Menu    menu3 = {
       menu3str
};

Cursor sweep0 = {
       {-7, -7},
       {0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0,
        0x03, 0xC0, 0x03, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF,
        0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xC0, 0x03, 0xC0,
        0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0},
       {0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80,
        0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x7F, 0xFE,
        0x7F, 0xFE, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80,
        0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x00, 0x00}
};

Cursor box = {
       {-7, -7},
       {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
        0xFF, 0xFF, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F,
        0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xFF, 0xFF,
        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF},
       {0x00, 0x00, 0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE,
        0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
        0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E, 0x70, 0x0E,
        0x7F, 0xFE, 0x7F, 0xFE, 0x7F, 0xFE, 0x00, 0x00}
};

Cursor sight = {
       {-7, -7},
       {0x1F, 0xF8, 0x3F, 0xFC, 0x7F, 0xFE, 0xFB, 0xDF,
        0xF3, 0xCF, 0xE3, 0xC7, 0xFF, 0xFF, 0xFF, 0xFF,
        0xFF, 0xFF, 0xFF, 0xFF, 0xE3, 0xC7, 0xF3, 0xCF,
        0x7B, 0xDF, 0x7F, 0xFE, 0x3F, 0xFC, 0x1F, 0xF8,},
       {0x00, 0x00, 0x0F, 0xF0, 0x31, 0x8C, 0x21, 0x84,
        0x41, 0x82, 0x41, 0x82, 0x41, 0x82, 0x7F, 0xFE,
        0x7F, 0xFE, 0x41, 0x82, 0x41, 0x82, 0x41, 0x82,
        0x21, 0x84, 0x31, 0x8C, 0x0F, 0xF0, 0x00, 0x00,}
};

Cursor pixel = {
       {-7, -7},
       {0x1f, 0xf8, 0x3f, 0xfc,  0x7f, 0xfe,  0xf8, 0x1f,
       0xf0, 0x0f,  0xe0, 0x07, 0xe0, 0x07, 0xfe, 0x7f,
       0xfe, 0x7f, 0xe0, 0x07, 0xe0, 0x07, 0xf0, 0x0f,
       0x78, 0x1f, 0x7f, 0xfe, 0x3f, 0xfc, 0x1f, 0xf8, },
       {0x00, 0x00, 0x0f, 0xf0, 0x31, 0x8c, 0x21, 0x84,
       0x41, 0x82, 0x41, 0x82, 0x41, 0x82, 0x40, 0x02,
       0x40, 0x02, 0x41, 0x82, 0x41, 0x82, 0x41, 0x82,
       0x21, 0x84, 0x31, 0x8c, 0x0f, 0xf0, 0x00, 0x00, }
};

Cursor busy = {
       {-7, -7},
       {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x0c, 0x00, 0x8e, 0x1d, 0xc7,
        0xff, 0xe3, 0xff, 0xf3, 0xff, 0xff, 0x7f, 0xfe,
        0x3f, 0xf8, 0x17, 0xf0, 0x03, 0xe0, 0x00, 0x00,},
       {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x00, 0x82,
        0x04, 0x41, 0xff, 0xe1, 0x5f, 0xf1, 0x3f, 0xfe,
        0x17, 0xf0, 0x03, 0xe0, 0x00, 0x00, 0x00, 0x00,}
};

Cursor skull = {
       {-7,-7},
       {0x00, 0x00, 0x00, 0x00, 0xc0, 0x03, 0xe7, 0xe7,
        0xff, 0xff, 0xff, 0xff, 0x3f, 0xfc, 0x1f, 0xf8,
        0x0f, 0xf0, 0x3f, 0xfc, 0xff, 0xff, 0xff, 0xff,
        0xef, 0xf7, 0xc7, 0xe3, 0x00, 0x00, 0x00, 0x00,},
       {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x03,
        0xE7, 0xE7, 0x3F, 0xFC, 0x0F, 0xF0, 0x0D, 0xB0,
        0x07, 0xE0, 0x06, 0x60, 0x37, 0xEC, 0xE4, 0x27,
        0xC3, 0xC3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,}
};

Rectangle       cntlr;          /* control region */
Rectangle       editr;          /* editing region */
Rectangle       textr;          /* text region */
Thing           *thing;
Mouse           mouse;
char            hex[] = "0123456789abcdefABCDEF";
jmp_buf         err;
char            *file;
int             mag;
int             but1val = 0;
int             but2val = 255;
int             invert = 0;
Image           *values[256];
Image           *greyvalues[256];
uchar           data[8192];

Thing*  tget(char*);
void    mesg(char*, ...);
void    drawthing(Thing*, int);
void    select(void);
void    menu(void);
void    error(Display*, char*);
void    buttons(int);
void    drawall(void);
void    tclose1(Thing*);

void
main(int argc, char *argv[])
{
       int i;
       Event e;
       Thing *t;

       mag = Mag;
       if(initdraw(error, 0, "tweak") < 0){
               fprint(2, "tweak: initdraw failed: %r\n");
               exits("initdraw");
       }
       for(i=0; i<256; i++){
               values[i] = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, cmap2rgba(i));
               greyvalues[i] = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, (i<<24)|(i<<16)|(i<<8)|0xFF);
               if(values[i] == 0 || greyvalues[i] == 0)
                       drawerror(display, "can't allocate image");
       }
       einit(Emouse|Ekeyboard);
       eresized(0);
       i = 1;
       setjmp(err);
       for(; i<argc; i++){
               file = argv[i];
               t = tget(argv[i]);
               if(t)
                       drawthing(t, 1);
               flushimage(display, 1);
       }
       file = 0;
       setjmp(err);
       for(;;)
               switch(event(&e)){
               case Ekeyboard:
                       break;
               case Emouse:
                       mouse = e.mouse;
                       if(mouse.buttons & 3){
                               select();
                               break;
                       }
                       if(mouse.buttons & 4)
                               menu();
               }
}

void
error(Display*, char *s)
{
       if(file)
               mesg("can't read %s: %s: %r", file, s);
       else
               mesg("/dev/bitblt error: %s", s);
       if(err[0])
               longjmp(err, 1);
       exits(s);
}

void
redraw(Thing *t)
{
       Thing *nt;
       Point p;

       if(thing==0 || thing==t)
               draw(screen, editr, display->white, nil, ZP);
       if(thing == 0)
               return;
       if(thing != t){
               for(nt=thing; nt->next!=t; nt=nt->next)
                       ;
               draw(screen, Rect(screen->r.min.x, nt->er.max.y, editr.max.x, editr.max.y),
                       display->white, nil, ZP);
       }
       for(nt=t; nt; nt=nt->next){
               drawthing(nt, 0);
               if(nt->next == 0){
                       p = Pt(editr.min.x, nt->er.max.y);
                       draw(screen, Rpt(p, editr.max), display->white, nil, ZP);
               }
       }
       mesg("");
}

void
eresized(int new)
{
       if(new && getwindow(display, Refnone) < 0)
               error(display, "can't reattach to window");
       cntlr = insetrect(screen->clipr, 1);
       editr = cntlr;
       textr = editr;
       textr.min.y = textr.max.y - font->height;
       cntlr.max.y = cntlr.min.y + font->height;
       editr.min.y = cntlr.max.y+1;
       editr.max.y = textr.min.y-1;
       draw(screen, screen->clipr, display->white, nil, ZP);
       draw(screen, Rect(editr.min.x, editr.max.y, editr.max.x+1, editr.max.y+1), display->black, nil, ZP);
       replclipr(screen, 0, editr);
       drawall();
}

void
mesgstr(Point p, int line, char *s)
{
       Rectangle c, r;

       r.min = p;
       r.min.y += line*font->height;
       r.max.y = r.min.y+font->height;
       r.max.x = editr.max.x;
       c = screen->clipr;
       replclipr(screen, 0, r);
       draw(screen, r, values[0xDD], nil, ZP);
       r.min.x++;
       string(screen, r.min, display->black, ZP, font, s);
       replclipr(screen, 0, c);
       flushimage(display, 1);
}

void
mesg(char *fmt, ...)
{
       char buf[1024];
       va_list arg;

       va_start(arg, fmt);
       vseprint(buf, buf+sizeof(buf), fmt, arg);
       va_end(arg);
       mesgstr(textr.min, 0, buf);
}

void
tmesg(Thing *t, int line, char *fmt, ...)
{
       char buf[1024];
       va_list arg;

       va_start(arg, fmt);
       vseprint(buf, buf+sizeof(buf), fmt, arg);
       va_end(arg);
       mesgstr(t->tr.min, line, buf);
}


void
scntl(char *l)
{
       sprint(l, "mag: %d  but1: %d  but2: %d  invert-on-copy: %c", mag, but1val, but2val, "ny"[invert]);
}

void
cntl(void)
{
       char buf[256];

       scntl(buf);
       mesgstr(cntlr.min, 0, buf);
}

void
stext(Thing *t, char *l0, char *l1)
{
       Fontchar *fc;
       char buf[256];

       l1[0] = 0;
       sprint(buf, "depth:%d r:%d %d  %d %d ",
               t->b->depth, t->b->r.min.x, t->b->r.min.y,
               t->b->r.max.x, t->b->r.max.y);
       if(t->parent)
               sprint(buf+strlen(buf), "mag: %d ", t->mag);
       sprint(l0, "%s file: %s", buf, t->name);
       if(t->c >= 0){
               fc = &t->parent->s->info[t->c];
               sprint(l1, "c(hex): %x c(char): %C x: %d "
                          "top: %d bottom: %d left: %d width: %d iwidth: %d",
                       (int)(t->c+t->parent->off), (int)(t->c+t->parent->off),
                       fc->x, fc->top, fc->bottom, fc->left,
                       fc->width, Dx(t->b->r));
       }else if(t->s)
               sprint(l1, "offset(hex): %ux n:%d  height:%d  ascent:%d",
                       t->off, t->s->n, t->s->height, t->s->ascent);
}

void
text(Thing *t)
{
       char l0[256], l1[256];

       stext(t, l0, l1);
       tmesg(t, 0, l0);
       if(l1[0])
               tmesg(t, 1, l1);
}

void
drawall(void)
{
       Thing *t;

       cntl();
       for(t=thing; t; t=t->next)
               drawthing(t, 0);
}

/* imported from libdraw/arith.c to permit an extern log2 function */
static int log2[] = {
       -1, 0, 1, -1, 2, -1, -1, -1, 3, -1, -1, -1, -1, -1, -1, -1, 4,
       -1, -1, -1, -1, -1, -1, -1, 4 /* BUG */, -1, -1, -1, -1, -1, -1, -1, 5
};

int
value(Image *b, int x)
{
       int v, l, w;
       uchar mask;

       w = b->depth;
       if(w > 8){
               mesg("ldepth too large");
               return 0;
       }
       l = log2[w];
       mask = (1<<w)-1;                /* ones at right end of word */
       x -= b->r.min.x&~(7>>l);        /* adjust x relative to first pixel */
       v = data[x>>(3-l)];
       v >>= ((7>>l)<<l) - ((x&(7>>l))<<l);    /* pixel at right end of word */
       v &= mask;                      /* pixel at right end of word */
       return v;
}

int
bvalue(int v, int d)
{
       v &= (1<<d)-1;
       if(d > screen->depth)
               v >>= d - screen->depth;
       else
               while(d < screen->depth && d < 8){
                       v |= v << d;
                       d <<= 1;
               }
       if(v<0 || v>255){
               mesg("internal error: bad color");
               return Blue;
       }
       return v;
}

void
drawthing(Thing *nt, int link)
{
       int nl, nf, i, x, y, sx, sy, fdx, dx, dy, v;
       Thing *t;
       Subfont *s;
       Image *b, *col;
       Point p, p1, p2;

       if(link){
               nt->next = 0;
               if(thing == 0){
                       thing = nt;
                       y = editr.min.y;
               }else{
                       for(t=thing; t->next; t=t->next)
                               ;
                       t->next = nt;
                       y = t->er.max.y;
               }
       }else{
               if(thing == nt)
                       y = editr.min.y;
               else{
                       for(t=thing; t->next!=nt; t=t->next)
                               ;
                       y = t->er.max.y;
               }
       }
       s = nt->s;
       b = nt->b;
       nl = font->height;
       if(s || nt->c>=0)
               nl += font->height;
       fdx = Dx(editr) - 2*Border;
       dx = Dx(b->r);
       dy = Dy(b->r);
       if(nt->mag > 1){
               dx *= nt->mag;
               dy *= nt->mag;
               fdx -= fdx%nt->mag;
       }
       nf = 1 + dx/fdx;
       nt->er.min.y = y;
       nt->er.min.x = editr.min.x;
       nt->er.max.x = nt->er.min.x + Border + dx + Border;
       if(nt->er.max.x > editr.max.x)
               nt->er.max.x = editr.max.x;
       nt->er.max.y = nt->er.min.y + Border + nf*(dy+Border);
       nt->r = insetrect(nt->er, Border);
       nt->er.max.x = editr.max.x;
       draw(screen, nt->er, display->white, nil, ZP);
       for(i=0; i<nf; i++){
               p1 = Pt(nt->r.min.x-1, nt->r.min.y+i*(Border+dy));
               /* draw portion of bitmap */
               p = Pt(p1.x+1, p1.y);
               if(nt->mag == 1)
                       draw(screen, Rect(p.x, p.y, p.x+fdx+Dx(b->r), p.y+Dy(b->r)),
                               b, nil, Pt(b->r.min.x+i*fdx, b->r.min.y));
               else{
                       for(y=b->r.min.y; y<b->r.max.y; y++){
                               sy = p.y+(y-b->r.min.y)*nt->mag;
                               unloadimage(b, Rect(b->r.min.x, y, b->r.max.x, y+1), data, sizeof data);
                               for(x=b->r.min.x+i*(fdx/nt->mag); x<b->r.max.x; x++){
                                       sx = p.x+(x-i*(fdx/nt->mag)-b->r.min.x)*nt->mag;
                                       if(sx >= nt->r.max.x)
                                               break;
                                       v = bvalue(value(b, x), b->depth);
                                       if(v == 255)
                                               continue;
                                       if(b->chan == GREY8)
                                               draw(screen, Rect(sx, sy, sx+nt->mag, sy+nt->mag),
                                                       greyvalues[v], nil, ZP);
                                       else
                                               draw(screen, Rect(sx, sy, sx+nt->mag, sy+nt->mag),
                                                       values[v], nil, ZP);
                               }

                       }
               }
               /* line down left */
               if(i == 0)
                       col = display->black;
               else
                       col = display->white;
               draw(screen, Rect(p1.x, p1.y, p1.x+1, p1.y+dy+Border), col, nil, ZP);
               /* line across top */
               draw(screen, Rect(p1.x, p1.y-1, nt->r.max.x+Border, p1.y), display->black, nil, ZP);
               p2 = p1;
               if(i == nf-1){
                       p2.x += 1 + dx%fdx;
                       col = display->black;
               }else{
                       p2.x = nt->r.max.x;
                       col = display->white;
               }
               /* line down right */
               draw(screen, Rect(p2.x, p2.y, p2.x+1, p2.y+dy+Border), col, nil, ZP);
               /* line across bottom */
               if(i == nf-1){
                       p1.y += Border+dy;
                       draw(screen, Rect(p1.x, p1.y-1, p2.x,p1.y), display->black, nil, ZP);
               }
       }
       nt->tr.min.x = editr.min.x;
       nt->tr.max.x = editr.max.x;
       nt->tr.min.y = nt->er.max.y + Border;
       nt->tr.max.y = nt->tr.min.y + nl;
       nt->er.max.y = nt->tr.max.y + Border;
       text(nt);
}

int
tohex(int c)
{
       if('0'<=c && c<='9')
               return c - '0';
       if('a'<=c && c<='f')
               return 10 + (c - 'a');
       if('A'<=c && c<='F')
               return 10 + (c - 'A');
       return 0;
}

Thing*
tget(char *file)
{
       int i, j, fd, face, x, y, c, chan;
       Image *b;
       Subfont *s;
       Thing *t;
       Dir *d;
       jmp_buf oerr;
       uchar buf[256];
       char *data;

       buf[0] = '\0';
       errstr((char*)buf, sizeof buf); /* flush pending error message */
       memmove(oerr, err, sizeof err);
       d = nil;
       if(setjmp(err)){
  Err:
               free(d);
               memmove(err, oerr, sizeof err);
               return 0;
       }
       fd = open(file, OREAD);
       if(fd < 0){
               mesg("can't open %s: %r", file);
               goto Err;
       }
       d = dirfstat(fd);
       if(d == nil){
               mesg("can't stat bitmap file %s: %r", file);
               close(fd);
               goto Err;
       }
       if(read(fd, buf, 11) != 11){
               mesg("can't read %s: %r", file);
               close(fd);
               goto Err;
       }
       seek(fd, 0, 0);
       data = (char*)buf;
       if(*data == '{')
               data++;
       if(memcmp(data, "0x", 2)==0 && data[4]==','){
               /*
                * cursor file
                */
               face = CURSOR;
               s = 0;
               data = malloc(d->length+1);
               if(data == 0){
                       mesg("can't malloc buffer: %r");
                       close(fd);
                       goto Err;
               }
               data[d->length] = 0;
               if(read(fd, data, d->length) != d->length){
                       mesg("can't read cursor file %s: %r", file);
                       close(fd);
                       goto Err;
               }
               b = allocimage(display, Rect(0, 0, 16, 32), GREY1, 0, DNofill);
               if(b == 0){
                       mesg("image alloc failed file %s: %r", file);
                       free(data);
                       close(fd);
                       goto Err;
               }
               i = 0;
               for(x=0;x<64; ){
                       if((c=data[i]) == '\0')
                               goto ill;
                       if(c=='0' && data[i+1] == 'x'){
                               i += 2;
                               continue;
                       }
                       if(strchr(hex, c)){
                               buf[x++] = (tohex(c)<<4) | tohex(data[i+1]);
                               i += 2;
                               continue;
                       }
                       i++;
               }
               loadimage(b, Rect(0, 0, 16, 32), buf, sizeof buf);
               free(data);
       }else if(memcmp(buf, "0x", 2)==0){
               /*
                * face file
                */
               face = FACE;
               s = 0;
               data = malloc(d->length+1);
               if(data == 0){
                       mesg("can't malloc buffer: %r");
                       close(fd);
                       goto Err;
               }
               data[d->length] = 0;
               if(read(fd, data, d->length) != d->length){
                       mesg("can't read bitmap file %s: %r", file);
                       close(fd);
                       goto Err;
               }
               for(y=0,i=0; i<d->length; i++)
                       if(data[i] == '\n')
                               y++;
               if(y == 0){
       ill:
                       mesg("ill-formed face file %s", file);
                       close(fd);
                       free(data);
                       goto Err;
               }
               for(x=0,i=0; (c=data[i])!='\n'; ){
                       if(c==',' || c==' ' || c=='\t'){
                               i++;
                               continue;
                       }
                       if(c=='0' && data[i+1] == 'x'){
                               i += 2;
                               continue;
                       }
                       if(strchr(hex, c)){
                               x += 4;
                               i++;
                               continue;
                       }
                       goto ill;
               }
               if(x % y)
                       goto ill;
               switch(x / y){
               default:
                       goto ill;
               case 1:
                       chan = GREY1;
                       break;
               case 2:
                       chan = GREY2;
                       break;
               case 4:
                       chan = GREY4;
                       break;
               case 8:
                       chan = CMAP8;
                       break;
               }
               b = allocimage(display, Rect(0, 0, y, y), chan, 0, -1);
               if(b == 0){
                       mesg("image alloc failed file %s: %r", file);
                       free(data);
                       close(fd);
                       goto Err;
               }
               i = 0;
               for(j=0; j<y; j++){
                       for(x=0; (c=data[i])!='\n'; ){
                               if(c=='0' && data[i+1] == 'x'){
                                       i += 2;
                                       continue;
                               }
                               if(strchr(hex, c)){
                                       buf[x++] = ~((tohex(c)<<4) | tohex(data[i+1]));
                                       i += 2;
                                       continue;
                               }
                               i++;
                       }
                       i++;
                       loadimage(b, Rect(0, j, y, j+1), buf, sizeof buf);
               }
               free(data);
       }else{
               face = NORMAL;
               s = 0;
               b = readimage(display, fd, 0);
               if(b == 0){
                       mesg("can't read bitmap file %s: %r", file);
                       close(fd);
                       goto Err;
               }
               if(seek(fd, 0, 1) < d->length)
                       s = readsubfonti(display, file, fd, b, 0);
       }
       close(fd);
       t = malloc(sizeof(Thing));
       if(t == 0){
  nomem:
               mesg("malloc failed: %r");
               if(s)
                       freesubfont(s);
               else
                       freeimage(b);
               goto Err;
       }
       t->name = strdup(file);
       if(t->name == 0){
               free(t);
               goto nomem;
       }
       t->b = b;
       t->s = s;
       t->face = face;
       t->mod = 0;
       t->parent = 0;
       t->c = -1;
       t->mag = 1;
       t->off = 0;
       memmove(err, oerr, sizeof err);
       return t;
}

int
atline(int x, Point p, char *line, char *buf)
{
       char *s, *c, *word, *hit;
       int w, wasblank;
       Rune r;

       wasblank = 1;
       hit = 0;
       word = 0;
       for(s=line; *s; s+=w){
               w = chartorune(&r, s);
               x += runestringnwidth(font, &r, 1);
               if(wasblank && r!=' ')
                       word = s;
               wasblank = 0;
               if(r == ' '){
                       if(x >= p.x)
                               break;
                       wasblank = 1;
               }
               if(r == ':')
                       hit = word;
       }
       if(x < p.x)
               return 0;
       c = utfrune(hit, ':');
       strncpy(buf, hit, c-hit);
       buf[c-hit] = 0;
       return 1;
}

int
attext(Thing *t, Point p, char *buf)
{
       char l0[256], l1[256];

       if(!ptinrect(p, t->tr))
               return 0;
       stext(t, l0, l1);
       if(p.y < t->tr.min.y+font->height)
               return atline(t->r.min.x, p, l0, buf);
       else
               return atline(t->r.min.x, p, l1, buf);
}

int
type(char *buf, int nbuf, char *tag)
{
       Rune r;
       char *p, *e;

       esetcursor(&busy);
       p = buf;
       e = buf + nbuf-UTFmax-1;
       for(;;){
               *p = 0;
               mesg("%s: %s", tag, buf);
               r = ekbd();
               switch(r){
               case '\n':
                       mesg("");
                       esetcursor(0);
                       return p-buf;
               case 0x15:      /* control-U */
                       p = buf;
                       break;
               case '\b':
                       if(p > buf)
                               --p;
                       break;
               default:
                       if(p < e)
                               p += runetochar(p, &r);
               }
       }
}

void
textedit(Thing *t, char *tag)
{
       char buf[256];
       char *s;
       Image *b;
       Subfont *f;
       Fontchar *fc, *nfc;
       Rectangle r;
       ulong chan;
       int i, ld, d, w, c, doredraw, fdx, x;
       Thing *nt;

       buttons(Up);
       if(type(buf, sizeof(buf), tag) == 0)
               return;
       if(strcmp(tag, "file") == 0){
               for(s=buf; *s; s++)
                       if(*s <= ' '){
                               mesg("illegal file name");
                               return;
                       }
               if(strcmp(t->name, buf) != 0){
                       if(t->parent)
                               t->parent->mod = 1;
                       else
                               t->mod = 1;
               }
               for(nt=thing; nt; nt=nt->next)
                       if(t==nt || t->parent==nt || nt->parent==t){
                               free(nt->name);
                               nt->name = strdup(buf);
                               if(nt->name == 0){
                                       mesg("malloc failed: %r");
                                       return;
                               }
                               text(nt);
                       }
               return;
       }
       if(strcmp(tag, "depth") == 0){
               if(buf[0]<'0' || '9'<buf[0] || (d=atoi(buf))<0 || d>8 || log2[d]<0){
                       mesg("illegal ldepth");
                       return;
               }
               if(d == t->b->depth)
                       return;
               if(t->parent)
                       t->parent->mod = 1;
               else
                       t->mod = 1;
               if(d == 8)
                       chan = CMAP8;
               else
                       chan = CHAN1(CGrey, d);
               for(nt=thing; nt; nt=nt->next){
                       if(nt!=t && nt!=t->parent && nt->parent!=t)
                               continue;
                       b = allocimage(display, nt->b->r, chan, 0, 0);
                       if(b == 0){
       nobmem:
                               mesg("image alloc failed: %r");
                               return;
                       }
                       draw(b, b->r, nt->b, nil, nt->b->r.min);
                       freeimage(nt->b);
                       nt->b = b;
                       if(nt->s){
                               b = allocimage(display, nt->b->r, chan, 0, -1);
                               if(b == 0)
                                       goto nobmem;
                               draw(b, b->r, nt->b, nil, nt->b->r.min);
                               f = allocsubfont(t->name, nt->s->n, nt->s->height, nt->s->ascent, nt->s->info, b);
                               if(f == 0){
       nofmem:
                                       freeimage(b);
                                       mesg("can't make subfont: %r");
                                       return;
                               }
                               nt->s->info = 0;        /* prevent it being freed */
                               nt->s->bits = 0;
                               freesubfont(nt->s);
                               nt->s = f;
                       }
                       drawthing(nt, 0);
               }
               return;
       }
       if(strcmp(tag, "mag") == 0){
               if(buf[0]<'0' || '9'<buf[0] || (ld=atoi(buf))<=0 || ld>Maxmag){
                       mesg("illegal magnification");
                       return;
               }
               if(t->mag == ld)
                       return;
               t->mag = ld;
               redraw(t);
               return;
       }
       if(strcmp(tag, "r") == 0){
               if(t->s){
                       mesg("can't change rectangle of subfont\n");
                       return;
               }
               s = buf;
               r.min.x = strtoul(s, &s, 0);
               r.min.y = strtoul(s, &s, 0);
               r.max.x = strtoul(s, &s, 0);
               r.max.y = strtoul(s, &s, 0);
               if(Dx(r)<=0 || Dy(r)<=0){
                       mesg("illegal rectangle");
                       return;
               }
               if(t->parent)
                       t = t->parent;
               for(nt=thing; nt; nt=nt->next){
                       if(nt->parent==t && !rectinrect(nt->b->r, r))
                               tclose1(nt);
               }
               b = allocimage(display, r, t->b->chan, 0, 0);
               if(b == 0)
                       goto nobmem;
               draw(b, r, t->b, nil, r.min);
               freeimage(t->b);
               t->b = b;
               b = allocimage(display, r, t->b->chan, 0, 0);
               if(b == 0)
                       goto nobmem;
               redraw(t);
               t->mod = 1;
               return;
       }
       if(strcmp(tag, "ascent") == 0){
               if(buf[0]<'0' || '9'<buf[0] || (ld=atoi(buf))<0 || ld>t->s->height){
                       mesg("illegal ascent");
                       return;
               }
               if(t->s->ascent == ld)
                       return;
               t->s->ascent = ld;
               text(t);
               t->mod = 1;
               return;
       }
       if(strcmp(tag, "height") == 0){
               if(buf[0]<'0' || '9'<buf[0] || (ld=atoi(buf))<0){
                       mesg("illegal height");
                       return;
               }
               if(t->s->height == ld)
                       return;
               t->s->height = ld;
               text(t);
               t->mod = 1;
               return;
       }
       if(strcmp(tag, "left")==0 || strcmp(tag, "width") == 0){
               if(buf[0]<'0' || '9'<buf[0] || (ld=atoi(buf))<0){
                       mesg("illegal value");
                       return;
               }
               fc = &t->parent->s->info[t->c];
               if(strcmp(tag, "left")==0){
                       if(fc->left == ld)
                               return;
                       fc->left = ld;
               }else{
                       if(fc->width == ld)
                               return;
                       fc->width = ld;
               }
               text(t);
               t->parent->mod = 1;
               return;
       }
       if(strcmp(tag, "offset(hex)") == 0){
               if(!strchr(hex, buf[0])){
       illoff:
                       mesg("illegal offset");
                       return;
               }
               s = 0;
               ld = strtoul(buf, &s, 16);
               if(*s)
                       goto illoff;
               t->off = ld;
               text(t);
               for(nt=thing; nt; nt=nt->next)
                       if(nt->parent == t)
                               text(nt);
               return;
       }
       if(strcmp(tag, "n") == 0){
               if(buf[0]<'0' || '9'<buf[0] || (w=atoi(buf))<=0){
                       mesg("illegal n");
                       return;
               }
               f = t->s;
               if(w == f->n)
                       return;
               doredraw = 0;
       again:
               for(nt=thing; nt; nt=nt->next)
                       if(nt->parent == t){
                               doredraw = 1;
                               tclose1(nt);
                               goto again;
                       }
               r = t->b->r;
               if(w < f->n)
                       r.max.x = f->info[w].x;
               b = allocimage(display, r, t->b->chan, 0, 0);
               if(b == 0)
                       goto nobmem;
               draw(b, b->r, t->b, nil, r.min);
               fdx = Dx(editr) - 2*Border;
               if(Dx(t->b->r)/fdx != Dx(b->r)/fdx)
                       doredraw = 1;
               freeimage(t->b);
               t->b = b;
               b = allocimage(display, r, t->b->chan, 0, 0);
               if(b == 0)
                       goto nobmem;
               draw(b, b->r, t->b, nil, r.min);
               nfc = malloc((w+1)*sizeof(Fontchar));
               if(nfc == 0){
                       mesg("malloc failed");
                       freeimage(b);
                       return;
               }
               fc = f->info;
               for(i=0; i<=w && i<=f->n; i++)
                       nfc[i] = fc[i];
               if(w+1 < i)
                       memset(nfc+i, 0, ((w+1)-i)*sizeof(Fontchar));
               x = fc[f->n].x;
               for(; i<=w; i++)
                       nfc[i].x = x;
               f = allocsubfont(t->name, w, f->height, f->ascent, nfc, b);
               if(f == 0)
                       goto nofmem;
               t->s->bits = nil;       /* don't free it */
               freesubfont(t->s);
               f->info = nfc;
               t->s = f;
               if(doredraw)
                       redraw(thing);
               else
                       drawthing(t, 0);
               t->mod = 1;
               return;
       }
       if(strcmp(tag, "iwidth") == 0){
               if(buf[0]<'0' || '9'<buf[0] || (w=atoi(buf))<0){
                       mesg("illegal iwidth");
                       return;
               }
               w -= Dx(t->b->r);
               if(w == 0)
                       return;
               r = t->parent->b->r;
               r.max.x += w;
               c = t->c;
               t = t->parent;
               f = t->s;
               b = allocimage(display, r, t->b->chan, 0, 0);
               if(b == 0)
                       goto nobmem;
               fc = &f->info[c];
               draw(b, Rect(b->r.min.x, b->r.min.y,
                               b->r.min.x+(fc[1].x-t->b->r.min.x), b->r.min.y+Dy(t->b->r)),
                               t->b, nil, t->b->r.min);
               draw(b, Rect(fc[1].x+w, b->r.min.y, w+t->b->r.max.x, b->r.min.y+Dy(t->b->r)),
                       t->b, nil, Pt(fc[1].x, t->b->r.min.y));
               fdx = Dx(editr) - 2*Border;
               doredraw = 0;
               if(Dx(t->b->r)/fdx != Dx(b->r)/fdx)
                       doredraw = 1;
               freeimage(t->b);
               t->b = b;
               b = allocimage(display, r, t->b->chan, 0, 0);
               if(b == 0)
                       goto nobmem;
               draw(b, b->r, t->b, nil, t->b->r.min);
               fc = &f->info[c+1];
               for(i=c+1; i<=f->n; i++, fc++)
                       fc->x += w;
               f = allocsubfont(t->name, f->n, f->height, f->ascent,
                       f->info, b);
               if(f == 0)
                       goto nofmem;
               /* t->s and f share info; free carefully */
               fc = f->info;
               t->s->bits = nil;
               t->s->info = 0;
               freesubfont(t->s);
               f->info = fc;
               t->s = f;
               if(doredraw)
                       redraw(t);
               else
                       drawthing(t, 0);
               /* redraw all affected chars */
               for(nt=thing; nt; nt=nt->next){
                       if(nt->parent!=t || nt->c<c)
                               continue;
                       fc = &f->info[nt->c];
                       r.min.x = fc[0].x;
                       r.min.y = nt->b->r.min.y;
                       r.max.x = fc[1].x;
                       r.max.y = nt->b->r.max.y;
                       b = allocimage(display, r, nt->b->chan, 0, 0);
                       if(b == 0)
                               goto nobmem;
                       draw(b, r, t->b, nil, r.min);
                       doredraw = 0;
                       if(Dx(nt->b->r)/fdx != Dx(b->r)/fdx)
                               doredraw = 1;
                       freeimage(nt->b);
                       nt->b = b;
                       if(c != nt->c)
                               text(nt);
                       else{
                               if(doredraw)
                                       redraw(nt);
                               else
                                       drawthing(nt, 0);
                       }
               }
               t->mod = 1;
               return;
       }
       mesg("cannot edit %s in file %s", tag, t->name);
}

void
cntledit(char *tag)
{
       char buf[256];
       long l;

       buttons(Up);
       if(type(buf, sizeof(buf), tag) == 0)
               return;
       if(strcmp(tag, "mag") == 0){
               if(buf[0]<'0' || '9'<buf[0] || (l=atoi(buf))<=0 || l>Maxmag){
                       mesg("illegal magnification");
                       return;
               }
               mag = l;
               cntl();
               return;
       }
       if(strcmp(tag, "but1")==0
       || strcmp(tag, "but2")==0){
               if(buf[0]<'0' || '9'<buf[0] || (l=atoi(buf))<0 || l>255){
                       mesg("illegal value");
                       return;
               }
               if(strcmp(tag, "but1") == 0)
                       but1val = l;
               else if(strcmp(tag, "but2") == 0)
                       but2val = l;
               cntl();
               return;
       }
       if(strcmp(tag, "invert-on-copy")==0){
               if(buf[0]=='y' || buf[0]=='1')
                       invert = 1;
               else if(buf[0]=='n' || buf[0]=='0')
                       invert = 0;
               else{
                       mesg("illegal value");
                       return;
               }
               cntl();
               return;
       }
       mesg("cannot edit %s", tag);
}

void
buttons(int ud)
{
       while((mouse.buttons==0) != ud)
               mouse = emouse();
}

Point
screenpt(Thing *t, Point realp)
{
       int fdx, n;
       Point p;

       fdx = Dx(editr)-2*Border;
       if(t->mag > 1)
               fdx -= fdx%t->mag;
       p = mulpt(subpt(realp, t->b->r.min), t->mag);
       if(fdx < Dx(t->b->r)*t->mag){
               n = p.x/fdx;
               p.y += n * (Dy(t->b->r)*t->mag+Border);
               p.x -= n * fdx;
       }
       p = addpt(p, t->r.min);
       return p;
}

Point
realpt(Thing *t, Point screenp)
{
       int fdx, n, dy;
       Point p;

       fdx = (Dx(editr)-2*Border);
       if(t->mag > 1)
               fdx -= fdx%t->mag;
       p.y = screenp.y-t->r.min.y;
       p.x = 0;
       if(fdx < Dx(t->b->r)*t->mag){
               dy = Dy(t->b->r)*t->mag+Border;
               n = (p.y/dy);
               p.x = n * fdx;
               p.y -= n * dy;
       }
       p.x += screenp.x-t->r.min.x;
       p = addpt(divpt(p, t->mag), t->b->r.min);
       return p;
}

int
sweep(int but, Rectangle *r)
{
       Thing *t;
       Point p, q, lastq;

       esetcursor(&sweep0);
       buttons(Down);
       if(mouse.buttons != (1<<(but-1))){
               buttons(Up);
               esetcursor(0);
               return 0;
       }
       p = mouse.xy;
       for(t=thing; t; t=t->next)
               if(ptinrect(p, t->r))
                       break;
       if(t)
               p = screenpt(t, realpt(t, p));
       r->min = p;
       r->max = p;
       esetcursor(&box);
       lastq = ZP;
       while(mouse.buttons == (1<<(but-1))){
               edrawgetrect(insetrect(*r, -Borderwidth), 1);
               mouse = emouse();
               edrawgetrect(insetrect(*r, -Borderwidth), 0);
               q = mouse.xy;
               if(t)
                       q = screenpt(t, realpt(t, q));
               if(eqpt(q, lastq))
                       continue;
               *r = canonrect(Rpt(p, q));
               lastq = q;
       }
       esetcursor(0);
       if(mouse.buttons){
               buttons(Up);
               return 0;
       }
       return 1;
}

void
openedit(Thing *t, Point pt, int c)
{
       int x, y;
       Point p;
       Rectangle r;
       Rectangle br;
       Fontchar *fc;
       Thing *nt;

       if(t->b->depth > 8){
               mesg("image has depth %d; can't handle >8", t->b->depth);
               return;
       }
       br = t->b->r;
       if(t->s == 0){
               c = -1;
               /* if big enough to bother, sweep box */
               if(Dx(br)<=16 && Dy(br)<=16)
                       r = br;
               else{
                       if(!sweep(1, &r))
                               return;
                       r = rectaddpt(r, subpt(br.min, t->r.min));
                       if(!rectclip(&r, br))
                               return;
                       if(Dx(br) <= 8){
                               r.min.x = br.min.x;
                               r.max.x = br.max.x;
                       }else if(Dx(r) < 4){
           toosmall:
                               mesg("rectangle too small");
                               return;
                       }
                       if(Dy(br) <= 8){
                               r.min.y = br.min.y;
                               r.max.y = br.max.y;
                       }else if(Dy(r) < 4)
                               goto toosmall;
               }
       }else if(c >= 0){
               fc = &t->s->info[c];
               r.min.x = fc[0].x;
               r.min.y = br.min.y;
               r.max.x = fc[1].x;
               r.max.y = br.min.y + Dy(br);
       }else{
               /* just point at character */
               fc = t->s->info;
               p = addpt(pt, subpt(br.min, t->r.min));
               x = br.min.x;
               y = br.min.y;
               for(c=0; c<t->s->n; c++,fc++){
           again:
                       r.min.x = x;
                       r.min.y = y;
                       r.max.x = x + fc[1].x - fc[0].x;
                       r.max.y = y + Dy(br);
                       if(ptinrect(p, r))
                               goto found;
                       if(r.max.x >= br.min.x+Dx(t->r)){
                               x -= Dx(t->r);
                               y += t->s->height;
                               if(fc[1].x > fc[0].x)
                                       goto again;
                       }
                       x += fc[1].x - fc[0].x;
               }
               return;
          found:
               r = br;
               r.min.x = fc[0].x;
               r.max.x = fc[1].x;
       }
       nt = malloc(sizeof(Thing));
       if(nt == 0){
  nomem:
               mesg("can't allocate: %r");
               return;
       }
       memset(nt, 0, sizeof(Thing));
       nt->c = c;
       nt->b = allocimage(display, r, t->b->chan, 0, DNofill);
       if(nt->b == 0){
               free(nt);
               goto nomem;
       }
       draw(nt->b, r, t->b, nil, r.min);
       nt->name = strdup(t->name);
       if(nt->name == 0){
               freeimage(nt->b);
               free(nt);
               goto nomem;
       }
       nt->parent = t;
       nt->mag = mag;
       drawthing(nt, 1);
}

void
ckinfo(Thing *t, Rectangle mod)
{
       int i, j, k, top, bot, n, zero;
       Fontchar *fc;
       Rectangle r;
       Image *b;
       Thing *nt;

       if(t->parent)
               t = t->parent;
       if(t->s==0 || Dy(t->b->r)==0)
               return;
       b = 0;
       /* check bounding boxes */
       fc = &t->s->info[0];
       r.min.y = t->b->r.min.y;
       r.max.y = t->b->r.max.y;
       for(i=0; i<t->s->n; i++, fc++){
               r.min.x = fc[0].x;
               r.max.x = fc[1].x;
               if(!rectXrect(mod, r))
                       continue;
               if(b==0 || Dx(b->r)<Dx(r)){
                       if(b)
                               freeimage(b);
                       b = allocimage(display, rectsubpt(r, r.min), t->b->chan, 0, 0);
                       if(b == 0){
                               mesg("can't alloc image");
                               break;
                       }
               }
               draw(b, b->r, display->white, nil, ZP);
               draw(b, b->r, t->b, nil, r.min);
               top = 100000;
               bot = 0;
               n = 2+((Dx(r)/8)*t->b->depth);
               for(j=0; j<b->r.max.y; j++){
                       memset(data, 0, n);
                       unloadimage(b, Rect(b->r.min.x, j, b->r.max.x, j+1), data, sizeof data);
                       zero = 1;
                       for(k=0; k<n; k++)
                               if(data[k]){
                                       zero = 0;
                                       break;
                               }
                       if(!zero){
                               if(top > j)
                                       top = j;
                               bot = j+1;
                       }
               }
               if(top > j)
                       top = 0;
               if(top!=fc->top || bot!=fc->bottom){
                       fc->top = top;
                       fc->bottom = bot;
                       for(nt=thing; nt; nt=nt->next)
                               if(nt->parent==t && nt->c==i)
                                       text(nt);
               }
       }
       if(b)
               freeimage(b);
}

void
twidpix(Thing *t, Point p, int set)
{
       Image *b, *v;
       int c;

       b = t->b;
       if(!ptinrect(p, b->r))
               return;
       if(set)
               c = but1val;
       else
               c = but2val;
       if(b->chan == GREY8)
               v = greyvalues[c];
       else
               v = values[c];
       draw(b, Rect(p.x, p.y, p.x+1, p.y+1), v, nil, ZP);
       p = screenpt(t, p);
       draw(screen, Rect(p.x, p.y, p.x+t->mag, p.y+t->mag), v, nil, ZP);
}

void
twiddle(Thing *t)
{
       int set;
       Point p, lastp;
       Image *b;
       Thing *nt;
       Rectangle mod;

       if(mouse.buttons!=1 && mouse.buttons!=2){
               buttons(Up);
               return;
       }
       set = mouse.buttons==1;
       b = t->b;
       lastp = addpt(b->r.min, Pt(-1, -1));
       mod = Rpt(addpt(b->r.max, Pt(1, 1)), lastp);
       while(mouse.buttons){
               p = realpt(t, mouse.xy);
               if(!eqpt(p, lastp)){
                       lastp = p;
                       if(ptinrect(p, b->r)){
                               for(nt=thing; nt; nt=nt->next)
                                       if(nt->parent==t->parent || nt==t->parent)
                                               twidpix(nt, p, set);
                               if(t->parent)
                                       t->parent->mod = 1;
                               else
                                       t->mod = 1;
                               if(p.x < mod.min.x)
                                       mod.min.x = p.x;
                               if(p.y < mod.min.y)
                                       mod.min.y = p.y;
                               if(p.x >= mod.max.x)
                                       mod.max.x = p.x+1;
                               if(p.y >= mod.max.y)
                                       mod.max.y = p.y+1;
                       }
               }
               mouse = emouse();
       }
       ckinfo(t, mod);
}

void
select(void)
{
       Thing *t;
       char line[128], buf[128];
       Point p;

       if(ptinrect(mouse.xy, cntlr)){
               scntl(line);
               if(atline(cntlr.min.x, mouse.xy, line, buf)){
                       if(mouse.buttons == 1)
                               cntledit(buf);
                       else
                               buttons(Up);
                       return;
               }
               return;
       }
       for(t=thing; t; t=t->next){
               if(attext(t, mouse.xy, buf)){
                       if(mouse.buttons == 1)
                               textedit(t, buf);
                       else
                               buttons(Up);
                       return;
               }
               if(ptinrect(mouse.xy, t->r)){
                       if(t->parent == 0){
                               if(mouse.buttons == 1){
                                       p = mouse.xy;
                                       buttons(Up);
                                       openedit(t, p, -1);
                               }else
                                       buttons(Up);
                               return;
                       }
                       twiddle(t);
                       return;
               }
       }
}

void
twrite(Thing *t)
{
       int i, j, x, y, fd, ws, ld;
       Biobuf buf;
       Rectangle r;

       if(t->parent)
               t = t->parent;
       esetcursor(&busy);
       fd = create(t->name, OWRITE, 0666);
       if(fd < 0){
               mesg("can't write %s: %r", t->name);
               return;
       }
       if(t->face && t->b->depth <= 4){
               r = t->b->r;
               ld = log2[t->b->depth];
               /* This heuristic reflects peculiarly different formats */
               ws = 4;
               if(t->face == 2)        /* cursor file */
                       ws = 1;
               else if(Dx(r)<32 || ld==0)
                       ws = 2;
               Binit(&buf, fd, OWRITE);
               if(t->face == CURSOR)
                       Bprint(&buf, "{");
               for(y=r.min.y; y<r.max.y; y++){
                       unloadimage(t->b, Rect(r.min.x, y, r.max.x, y+1), data, sizeof data);
                       j = 0;
                       for(x=r.min.x; x<r.max.x; j+=ws,x+=ws*8>>ld){
                               Bprint(&buf, "0x");
                               for(i=0; i<ws; i++)
                                       Bprint(&buf, "%.2x", data[i+j]);
                               Bprint(&buf, ", ");
                       }
                       if(t->face == CURSOR){
                               switch(y){
                               case 3: case 7: case 11: case 19: case 23: case 27:
                                       Bprint(&buf, "\n ");
                                       break;
                               case 15:
                                       Bprint(&buf, "},\n{");
                                       break;
                               case 31:
                                       Bprint(&buf, "}\n");
                                       break;
                               }
                       }else
                               Bprint(&buf, "\n");
               }
               Bterm(&buf);
       }else
               if(writeimage(fd, t->b, 0)<0 || (t->s && writesubfont(fd, t->s)<0)){
                       close(fd);
                       mesg("can't write %s: %r", t->name);
               }
       t->mod = 0;
       close(fd);
       mesg("wrote %s", t->name);
}

void
tpixels(void)
{
       Thing *t;
       Point p, lastp;

       esetcursor(&pixel);
       for(;;){
               buttons(Down);
               if(mouse.buttons != 4)
                       break;
               for(t=thing; t; t=t->next){
                       lastp = Pt(-1, -1);
                       if(ptinrect(mouse.xy, t->r)){
                               while(ptinrect(mouse.xy, t->r) && mouse.buttons==4){
                                       p = realpt(t, mouse.xy);
                                       if(!eqpt(p, lastp)){
                                               if(p.y != lastp.y)
                                                       unloadimage(t->b, Rect(t->b->r.min.x, p.y, t->b->r.max.x, p.y+1), data, sizeof data);
                                               mesg("[%d,%d] = %d=0x%ux", p.x, p.y, value(t->b, p.x), value(t->b, p.x));
                                               lastp = p;
                                       }
                                       mouse = emouse();
                               }
                               goto Continue;
                       }
               }
               mouse = emouse();
   Continue:;
       }
       buttons(Up);
       esetcursor(0);
}

void
tclose1(Thing *t)
{
       Thing *nt;

       if(t == thing)
               thing = t->next;
       else{
               for(nt=thing; nt->next!=t; nt=nt->next)
                       ;
               nt->next = t->next;
       }
       do
               for(nt=thing; nt; nt=nt->next)
                       if(nt->parent == t){
                               tclose1(nt);
                               break;
                       }
       while(nt);
       if(t->s)
               freesubfont(t->s);
       else
               freeimage(t->b);
       free(t->name);
       free(t);
}

void
tclose(Thing *t)
{
       Thing *ct;

       if(t->mod){
               mesg("%s modified", t->name);
               t->mod = 0;
               return;
       }
       /* fiddle to save redrawing unmoved things */
       if(t == thing)
               ct = 0;
       else
               for(ct=thing; ct; ct=ct->next)
                       if(ct->next==t || ct->next->parent==t)
                               break;
       tclose1(t);
       if(ct)
               ct = ct->next;
       else
               ct = thing;
       redraw(ct);
}

void
tread(Thing *t)
{
       Thing *nt, *new;
       Fontchar *i;
       Rectangle r;
       int nclosed;

       if(t->parent)
               t = t->parent;
       new = tget(t->name);
       if(new == 0)
               return;
       nclosed = 0;
   again:
       for(nt=thing; nt; nt=nt->next)
               if(nt->parent == t){
                       if(!rectinrect(nt->b->r, new->b->r)
                       || new->b->depth!=nt->b->depth){
   closeit:
                               nclosed++;
                               nt->parent = 0;
                               tclose1(nt);
                               goto again;
                       }
                       if((t->s==0) != (new->s==0))
                               goto closeit;
                       if((t->face==0) != (new->face==0))
                               goto closeit;
                       if(t->s){       /* check same char */
                               if(nt->c >= new->s->n)
                                       goto closeit;
                               i = &new->s->info[nt->c];
                               r.min.x = i[0].x;
                               r.max.x = i[1].x;
                               r.min.y = new->b->r.min.y;
                               r.max.y = new->b->r.max.y;
                               if(!eqrect(r, nt->b->r))
                                       goto closeit;
                       }
                       nt->parent = new;
                       draw(nt->b, nt->b->r, new->b, nil, nt->b->r.min);
               }
       new->next = t->next;
       if(t == thing)
               thing = new;
       else{
               for(nt=thing; nt->next!=t; nt=nt->next)
                       ;
               nt->next = new;
       }
       if(t->s)
               freesubfont(t->s);
       else
               freeimage(t->b);
       free(t->name);
       free(t);
       for(nt=thing; nt; nt=nt->next)
               if(nt==new || nt->parent==new)
                       if(nclosed == 0)
                               drawthing(nt, 0);       /* can draw in place */
                       else{
                               redraw(nt);     /* must redraw all below */
                               break;
                       }
}

void
tchar(Thing *t)
{
       char buf[256], *p;
       Rune r;
       ulong c, d;

       if(t->s == 0){
               t = t->parent;
               if(t==0 || t->s==0){
                       mesg("not a subfont");
                       return;
               }
       }
       if(type(buf, sizeof(buf), "char (hex or character or hex-hex)") == 0)
               return;
       if(utflen(buf) == 1){
               chartorune(&r, buf);
               c = r;
               d = r;
       }else{
               if(!strchr(hex, buf[0])){
                       mesg("illegal hex character");
                       return;
               }
               c = strtoul(buf, 0, 16);
               d = c;
               p = utfrune(buf, '-');
               if(p){
                       d = strtoul(p+1, 0, 16);
                       if(d < c){
                               mesg("invalid range");
                               return;
                       }
               }
       }
       c -= t->off;
       d -= t->off;
       while(c <= d){
               if(c>=t->s->n){
                       mesg("0x%lux not in font %s", c+t->off, t->name);
                       return;
               }
               openedit(t, Pt(0, 0), c);
               c++;
       }
}

void
apply(void (*f)(Thing*))
{
       Thing *t;

       esetcursor(&sight);
       buttons(Down);
       if(mouse.buttons == 4)
               for(t=thing; t; t=t->next)
                       if(ptinrect(mouse.xy, t->er)){
                               buttons(Up);
                               f(t);
                               break;
                       }
       buttons(Up);
       esetcursor(0);
}

int
complement(Image *t)
{
       int i, n;
       uchar *buf;

       n = Dy(t->r)*bytesperline(t->r, t->depth);
       buf = malloc(n);
       if(buf == 0)
               return 0;
       unloadimage(t, t->r, buf, n);
       for(i=0; i<n; i++)
               buf[i] = ~buf[i];
       loadimage(t, t->r, buf, n);
       free(buf);
       return 1;
}

void
copy(void)
{
       Thing *st, *dt, *nt;
       Rectangle sr, dr, fr;
       Image *tmp;
       Point p1, p2;
       int but, up;

       if(!sweep(3, &sr))
               return;
       for(st=thing; st; st=st->next)
               if(rectXrect(sr, st->r))
                       break;
       if(st == 0)
               return;
       /* click gives full rectangle */
       if(Dx(sr)<4 && Dy(sr)<4)
               sr = st->r;
       rectclip(&sr, st->r);
       p1 = realpt(st, sr.min);
       p2 = realpt(st, Pt(sr.min.x, sr.max.y));
       up = 0;
       if(p1.x != p2.x){       /* swept across a fold */
  onafold:
               mesg("sweep spans a fold");
               goto Return;
       }
       p2 = realpt(st, sr.max);
       sr.min = p1;
       sr.max = p2;
       fr.min = screenpt(st, sr.min);
       fr.max = screenpt(st, sr.max);
       p1 = subpt(p2, p1);     /* diagonal */
       if(p1.x==0 || p1.y==0)
               return;
       border(screen, fr, -1, values[Blue], ZP);
       esetcursor(&box);
       for(; mouse.buttons==0; mouse=emouse()){
               for(dt=thing; dt; dt=dt->next)
                       if(ptinrect(mouse.xy, dt->er))
                               break;
               if(up)
                       edrawgetrect(insetrect(dr, -Borderwidth), 0);
               up = 0;
               if(dt == 0)
                       continue;
               dr.max = screenpt(dt, realpt(dt, mouse.xy));
               dr.min = subpt(dr.max, mulpt(p1, dt->mag));
               if(!rectXrect(dr, dt->r))
                       continue;
               edrawgetrect(insetrect(dr, -Borderwidth), 1);
               up = 1;
       }
       /* if up==1, we had a hit */
       esetcursor(0);
       if(up)
               edrawgetrect(insetrect(dr, -Borderwidth), 0);
       but = mouse.buttons;
       buttons(Up);
       if(!up || but!=4)
               goto Return;
       dt = 0;
       for(nt=thing; nt; nt=nt->next)
               if(rectXrect(dr, nt->r)){
                       if(dt){
                               mesg("ambiguous sweep");
                               return;
                       }
                       dt = nt;
               }
       if(dt == 0)
               goto Return;
       p1 = realpt(dt, dr.min);
       p2 = realpt(dt, Pt(dr.min.x, dr.max.y));
       if(p1.x != p2.x)
               goto onafold;
       p2 = realpt(dt, dr.max);
       dr.min = p1;
       dr.max = p2;

       if(invert){
               tmp = allocimage(display, dr, dt->b->chan, 0, 255);
               if(tmp == 0){
   nomem:
                       mesg("can't allocate temporary");
                       goto Return;
               }
               draw(tmp, dr, st->b, nil, sr.min);
               if(!complement(tmp))
                       goto nomem;
               draw(dt->b, dr, tmp, nil, dr.min);
               freeimage(tmp);
       }else
               draw(dt->b, dr, st->b, nil, sr.min);
       if(dt->parent){
               draw(dt->parent->b, dr, dt->b, nil, dr.min);
               dt = dt->parent;
       }
       drawthing(dt, 0);
       for(nt=thing; nt; nt=nt->next)
               if(nt->parent==dt && rectXrect(dr, nt->b->r)){
                       draw(nt->b, dr, dt->b, nil, dr.min);
                       drawthing(nt, 0);
               }
       ckinfo(dt, dr);
       dt->mod = 1;

Return:
       /* clear blue box */
       drawthing(st, 0);
}

void
menu(void)
{
       Thing *t;
       char *mod;
       int sel;
       char buf[256];

       sel = emenuhit(3, &mouse, &menu3);
       switch(sel){
       case Mopen:
               if(type(buf, sizeof(buf), "file")){
                       t = tget(buf);
                       if(t)
                               drawthing(t, 1);
               }
               break;
       case Mwrite:
               apply(twrite);
               break;
       case Mread:
               apply(tread);
               break;
       case Mchar:
               apply(tchar);
               break;
       case Mcopy:
               copy();
               break;
       case Mpixels:
               tpixels();
               break;
       case Mclose:
               apply(tclose);
               break;
       case Mexit:
               mod = 0;
               for(t=thing; t; t=t->next)
                       if(t->mod){
                               mod = t->name;
                               t->mod = 0;
                       }
               if(mod){
                       mesg("%s modified", mod);
                       break;
               }
               esetcursor(&skull);
               buttons(Down);
               if(mouse.buttons == 4){
                       buttons(Up);
                       exits(0);
               }
               buttons(Up);
               esetcursor(0);
               break;
       }
}