ref: 2be9c4121f468b7fbe44d9553e1ce39a27ddedf3
dir: /vexed.c/
#include <u.h> #include <libc.h> #include <thread.h> #include <draw.h> #include <mouse.h> #include <keyboard.h> #include <ctype.h> #include "a.h" void redraw(void); void drawselchange(int); enum { Emouse, Eresize, Ekeyboard, }; enum { Padding = 4, Spacing = 8, Scrollwidth = 12, }; enum { Mundo, Mredo, Minsert, Mappend, Mdelete, Mgoto, Mlook, Mnext }; char *menu2str[] = { "undo", "redo", "insert", "append", "delete", "goto", "look", "next", 0 }; Menu menu2 = { menu2str }; enum { Msave, Mquit, }; char *menu3str[] = { "save", "quit", 0 }; Menu menu3 = { menu3str }; const char *filename; int modified; Buffer buf; int blines; Mousectl *mctl; Keyboardctl *kctl; int sw; int scrollsize; Rectangle scrollr; Rectangle viewr; Rectangle statusr; int nlines; int offset; int sel = 0; uchar sbuf[255] = {0}; int nsbuf; char sstr[256] = {0}; int sindex = -1; int hexval(Rune k) { int v; if(isdigit(k)) v = k - '0'; else v = 10 + tolower(k) - 'a'; return v; } int search(int from) { char *s, *p; int len, oldsel; s = (char*)buf.data + from; for(;;){ len = s - (char*)buf.data; if(len >= buf.count) break; p = memchr(s, sbuf[0], buf.count - len); if(p == nil || (nsbuf > 1 && memcmp(p, sbuf, nsbuf) != 0)){ s = p + 1; continue; } oldsel = sel; sel = p - (char*)buf.data; drawselchange(oldsel); sindex = sel; return 1; } return 0; } void xundo(void) { Undo u; if(!canundo()) return; undo(&u); switch(u.action){ case Udelete: if(insert(&buf, u.index) < 0) sysfatal("insert: %r"); buf.data[u.index] = u.value; break; case Uinsert: if(delete(&buf, u.index) < 0) sysfatal("delete: %r"); break; case Uappend: if(delete(&buf, u.index + 1) < 0) sysfatal("delete: %r"); break; case Uset: buf.data[u.index] = u.value; break; } sel = u.index; modified = u.modified; blines = buf.count / 16; redraw(); } void xredo(void) { Undo u; if(!canredo()) return; redo(&u); switch(u.action){ case Udelete: if(delete(&buf, u.index) < 0) sysfatal("insert: %r"); sel = u.index; break; case Uinsert: if(insert(&buf, u.index) < 0) sysfatal("insert: %r"); sel = u.index; break; case Uappend: if(insert(&buf, u.index + 1) < 0) sysfatal("insert: %r"); sel = u.index + 1; break; case Uset: buf.data[u.index] = u.newvalue; break; } if(sel == buf.count) --sel; modified = 1; blines = buf.count / 16; redraw(); } void xgoto(void) { char b[16] = {0}, *endp; int n; if(enter("Address:", b, sizeof b, mctl, kctl, nil) <= 0) return; n = strtol(b, &endp, 0); if(endp == nil || endp == b) return; if(n < 0 || n >= buf.count) return; sel = n; redraw(); } void xdelete(void) { pushundo(Udelete, sel, buf.data[sel], 0, modified); if(delete(&buf, sel) < 0) sysfatal("delete: %r"); if(sel == buf.count) --sel; modified = 1; blines = buf.count/16; redraw(); } void xinsert(void) { pushundo(Uinsert, sel, 0, 0, modified); if(insert(&buf, sel) < 0) sysfatal("insert: %r"); modified = 1; blines = buf.count/16; redraw(); } void xappend(void) { pushundo(Uappend, sel, 0, 0, modified); if(append(&buf, sel) < 0) sysfatal("append: %r"); sel += 1; modified = 1; blines = buf.count/16; redraw(); } void xlook(void) { char tmp[255] = {0}; int n, i; n = enter("Look:", tmp, sizeof tmp, mctl, kctl, nil); if(n <= 0) return; if(n%2 != 0){ showerr("invalid byte sequence", mctl, kctl); return; } nsbuf = 0; sindex = -1; for(i = 0; i < n; i += 2){ if(!(isxdigit(tmp[i]) && isxdigit(tmp[i+1]))){ showerr("invalid character in byte sequence", mctl, kctl); return; } sbuf[nsbuf++] = 16*hexval(tmp[i]) + hexval(tmp[i+1]); } sbuf[nsbuf] = 0; snprint(sstr, sizeof sstr, "/%s", tmp); if(!search(sel)){ sindex = -1; } } void xnext(void) { if(sindex == -1) return; if(!search(sindex + 1)) search(0); } int selvisible(void) { int l; l = sel/16; return offset <= l && l < offset+nlines; } int ensureselvisible(void) { if(selvisible()) return 0; offset = sel/16; return 1; } void drawline(int line) { int y, index, i, n; char b[8] = {0}, *s; Point p; Point p2; y = viewr.min.y + line * font->height; index = (line + offset)*16; if(index >= buf.count) return; draw(screen, Rect(viewr.min.x, y, viewr.max.x, y + font->height), cols[BACK], nil, ZP); p = Pt(viewr.min.x, y); if(index/16 == sel/16){ n = snprint(b, sizeof b, "%06X", sel); p = stringnbg(screen, p, cols[BACK], ZP, font, b, n, cols[ADDR], ZP); }else{ n = snprint(b, sizeof b, "%06X", index); p = stringn(screen, p, cols[ADDR], ZP, font, b, n); } p.x += 2*Spacing; p2 = addpt(p, Pt(16*3*sw - sw + 2*Spacing, 0)); for(i = 0; i < 16; i++) { if(index + i >= buf.count) break; n = snprint(b, sizeof b, "%02X", buf.data[index + i]); s = isprint(buf.data[index + i]) ? (char*)&buf.data[index + i] : "."; if(index + i == sel){ p = stringnbg(screen, p, cols[BACK], ZP, font, b, n, cols[HEX], ZP); p2 = stringnbg(screen, p2, cols[BACK], ZP, font, s, 1, cols[ASCII], ZP); }else{ p = stringn(screen, p, cols[HEX], ZP, font, b, n); p2 = stringn(screen, p2, cols[ASCII], ZP, font, s, 1); } p = stringn(screen, p, cols[BACK], ZP, font, " ", 1); } } void drawselchange(int oldsel) { int ol, nl; if(ensureselvisible()) redraw(); else{ ol = oldsel/16 - offset; nl = sel/16 - offset; if(ol != nl) drawline(ol); drawline(nl); flushimage(display, 1); } } void drawstatus(void) { char b[16] = {0}; Point p; int x; draw(screen, statusr, cols[BACK], nil, ZP); p = string(screen, Pt(statusr.min.x + Padding, statusr.min.y), cols[HEX], ZP, font, filename); if(modified) string(screen, p, cols[SCROLL], ZP, font, " (modified)"); snprint(b, sizeof b, "%d%%", (int)((100.0 * sel) / buf.count + 0.5)); x = statusr.max.x - stringwidth(font, b) - Padding; string(screen, Pt(x, statusr.min.y), cols[HEX], ZP, font, b); } void redraw(void) { int i, h, y, ye; Rectangle scrposr; draw(screen, screen->r, cols[BACK], nil, ZP); draw(screen, scrollr, cols[SCROLL], nil, ZP); border(screen, scrollr, 0, cols[HEX], ZP); if(blines > 0){ h = ((double)nlines / blines) * Dy(scrollr); y = ((double)offset / blines) * Dy(scrollr); ye = scrollr.min.y + y + h - 1; if(ye >= scrollr.max.y) ye = scrollr.max.y - 1; scrposr = Rect(scrollr.min.x + 1, scrollr.min.y + y + 1, scrollr.max.x - 1, ye); }else scrposr = insetrect(scrollr, -1); draw(screen, scrposr, cols[BACK], nil, ZP); for(i = 0; i < nlines; i++) drawline(i); drawstatus(); flushimage(display, 1); } int scroll(int lines) { if(blines <= nlines) return 0; if(lines < 0 && offset == 0) return 0; if(lines > 0 && offset + nlines >= blines){ return 0; } offset += lines; if(offset < 0) offset = 0; if(offset + blines%nlines >= blines) offset = blines - blines%nlines; sel += lines * 16; if(sel < 0) sel = 0; else if(sel >= buf.count) sel = buf.count - 1; redraw(); return 1; } int indexat(Point p) { int row, col, index; row = (p.y - viewr.min.y) / font->height; col = ((p.x - viewr.min.x) / sw - 8); if(col < 0 || col > 16*3*sw) return -1; index = col/3 + row*16 + offset*16; if(index >= buf.count) return -1; return index; } void menu2hit(void) { int n; menu2str[Mnext] = sindex == -1 ? nil : sstr; n = menuhit(2, mctl, &menu2, nil); switch(n){ case Mundo: xundo(); break; case Mredo: xredo(); break; case Mgoto: xgoto(); break; case Mdelete: xdelete(); break; case Minsert: xinsert(); break; case Mappend: xappend(); break; case Mlook: xlook(); break; case Mnext: xnext(); break; } } void menu3hit(void) { int n; n = menuhit(3, mctl, &menu3, nil); switch(n){ case Msave: if(!modified) return; if(writefile(&buf, filename) < 0) sysfatal("writefile: %r"); modified = 0; redraw(); break; case Mquit: threadexitsall(nil); break; } } void emouse(Mouse *m) { int n; if(ptinrect(m->xy, scrollr)){ if(m->buttons == 1){ n = (m->xy.y - scrollr.min.y) / font->height; scroll(-n); }else if(m->buttons == 2){ n = (m->xy.y - scrollr.min.y) * blines / Dy(scrollr); offset = n; sel = offset*16; redraw(); }else if(m->buttons == 4){ n = (m->xy.y - scrollr.min.y) / font->height; scroll(n); } } else if(ptinrect(m->xy, viewr)){ if(m->buttons == 1){ n = indexat(m->xy); if(n >= 0){ sel = n; redraw(); } }else if(m->buttons == 2){ menu2hit(); }else if(m->buttons == 4){ menu3hit(); }else if(m->buttons == 8){ scroll(-scrollsize); }else if(m->buttons == 16){ scroll(scrollsize); } } } void eresize(void) { int w, x; statusr = Rect(screen->r.min.x + Padding, screen->r.max.y - Padding - font->height, screen->r.max.x - Padding, screen->r.max.y - Padding); scrollr = Rect(screen->r.min.x + Padding, screen->r.min.y + Padding, screen->r.min.x + Padding + Scrollwidth, statusr.min.y - Padding); sw = stringwidth(font, " "); w = Padding + 6*sw + 2*Spacing + 16*3*sw-sw + 2*Spacing + 16*sw + Padding; x = scrollr.max.x + Padding; viewr = Rect(x, screen->r.min.y + Padding, x + w, statusr.min.y - Padding); nlines = Dy(viewr) / font->height; scrollsize = mousescrollsize(nlines); redraw(); flushimage(display, 1); } void ekeyboard(Rune k) { static long lastk = 0; static int lastv; long e; int oldsel; if(!isxdigit(k)) lastv = -1; e = time(nil); oldsel = sel; switch(k){ case 'q': case Kdel: threadexitsall(nil); break; case Kpgup: scroll(-nlines); break; case Kpgdown: scroll(nlines); break; case Kleft: if(sel > 0){ oldsel = sel; sel--; drawselchange(oldsel); } break; case Kright: if(sel < (buf.count - 1)){ sel++; drawselchange(oldsel); } break; case Kup: if(sel >= 16){ sel -= 16; drawselchange(oldsel); } break; case Kdown: if(sel < (buf.count - 16)){ sel += 16; drawselchange(oldsel); } break; case Khome: if(sel != 0){ sel = 0; drawselchange(oldsel); } break; case Kend: if(sel != buf.count - 1){ sel = buf.count - 1; if(!selvisible()) offset = blines - blines%nlines; redraw(); } break; case 'u': case 'U': xundo(); break; case 'r': case 'R': xredo(); break; case 'g': case 'G': xgoto(); break; case 'x': case 'X': xdelete(); break; case 'i': case 'I': xinsert(); break; case 'p': case 'P': xappend(); break; case 'l': case 'L': xlook(); break; case 'n': case 'N': xnext(); break; default: if(isxdigit(k)){ if(e - lastk < 2 && lastv > 0) { buf.data[sel] = lastv*16 + hexval(k); patchundo(buf.data[sel]); lastv = -1; }else{ lastv = hexval(k); pushundo(Uset, sel, buf.data[sel], lastv, modified); buf.data[sel] = lastv; } modified = 1; drawselchange(oldsel); drawstatus(); }else{ lastv = -1; } } lastk = e; } void usage(void) { fprint(2, "usage: %s [-b] <filename>\n", argv0); threadexitsall("usage"); } void threadmain(int argc, char *argv[]) { Mouse m; Rune k; Alt alts[] = { { nil, &m, CHANRCV }, { nil, nil, CHANRCV }, { nil, &k, CHANRCV }, { nil, nil, CHANEND }, }; int reverse; reverse = 0; ARGBEGIN{ case 'b': reverse = 1; break; default: usage(); }ARGEND; if(*argv == nil) usage(); filename = *argv; modified = 0; if(readfile(&buf, filename) < 0) sysfatal("readfile: %r"); blines = buf.count/16; if(initdraw(nil, nil, argv0) < 0) sysfatal("initdraw: %r"); display->locking = 0; if((mctl = initmouse(nil, screen)) == nil) sysfatal("initmouse: %r"); if((kctl = initkeyboard(nil)) == nil) sysfatal("initkeyboard: %r"); initcols(reverse); alts[Emouse].c = mctl->c; alts[Eresize].c = mctl->resizec; alts[Ekeyboard].c = kctl->c; eresize(); for(;;){ switch(alt(alts)){ case Emouse: emouse(&m); break; case Eresize: if(getwindow(display, Refnone) < 0) sysfatal("getwindow: %r"); eresize(); break; case Ekeyboard: ekeyboard(k); break; } } }