ref: 88ccea37f65b87312292b2b1a107c39927c6a2c3
dir: /sys/src/cmd/tweak.c/
#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; } }