#include #include #include #include #include #include #include #include #include #include <9p.h> #include #include #include "gui.h" #include "cook.h" /* BUG: WIP */ static void xinit(Panel*); static void xterm(Panel*); static int xctl(Panel* p, char* ctl); static long xattrs(Panel* p, char* buf, long sz); static long xread(Panel* p, void* buf, long cnt, vlong off); static long xwrite(Panel* p, void* buf, long cnt, vlong off); static void xdraw(Panel* p, int resize); static void xmouse(Panel* p, Cmouse* m, Channel* mc); static void xkeyboard(Panel*, Rune); static void xtruncate(Panel* p); Pops textops = { .pref = "text:", .init = xinit, .term = xterm, .ctl = xctl, .attrs= xattrs, .read = xread, .write= xwrite, .draw = xdraw, .mouse = xmouse, .keyboard = xkeyboard, .truncate = xtruncate, }; Pops tagops = { .pref = "tag:", .init = xinit, .term = xterm, .ctl = xctl, .attrs= xattrs, .read = xread, .write= xwrite, .draw = xdraw, .mouse = xmouse, .keyboard = xkeyboard, .truncate = xtruncate, }; Pops labelops = { .pref = "label:", .init = xinit, .term = xterm, .ctl = xctl, .attrs= xattrs, .read = xread, .write= xwrite, .draw = xdraw, .mouse = xmouse, .keyboard = xkeyboard, .truncate = xtruncate, }; Pops buttonops = { .pref = "button:", .init = xinit, .term = xterm, .ctl = xctl, .attrs= xattrs, .read = xread, .write= xwrite, .draw = xdraw, .mouse = xmouse, .keyboard = xkeyboard, .truncate = xtruncate, }; static void xinit(Panel* p) { char* s; char* q; Rectangle r; if (!strncmp(p->name, "text:", 5)){ p->flags |= Pedit; p->wants = Pt(1,1); } else if (!strncmp(p->name, "tag:", 4)){ p->flags |= Pline|Pedit; p->wants = Pt(1,0); } else p->flags |= Pline; if (p->flags&Pline) p->font = fonts[FB]; else p->font = fonts[FR]; if (p->flags&Pline){ s = strchr(p->name, ':'); q = strchr(s, '.'); if (q) *q = 0; p->blks = str2block(s ? s+1 : p->name); if (q) *q = '.'; } else p->blks = str2block(""); assert(p->blks); settextsize(p); r = Rpt(ZP, p->minsz); p->f.nchars = 0; } static void xterm(Panel* p) { if (p->blks == nil) sysfatal("xterm on terminated panel"); blockfree(p->blks); p->blks = nil; cleanedits(p); if (p->loaded) frclear(&p->f, 1); p->loaded = 0; } static int dirty(Panel* p, int set, int sendev) { int old; if (p->flags&Pline) // lines do not get dirty return 0; old = p->flags; if (set) p->flags |= Pdirty; else p->flags &= ~Pdirty; if (old != p->flags){ borders(p->file->parent); if (sendev) event(p, set ? "dirty" : "clean" ); return 1; } return 0; } static int gotopos(Panel* p, int npos) { Tblock* b; int n; if (npos == p->froff) return 0; b = p->blks; packblock(b); if (npos > b->nr) npos = b->nr; if (npos > 1){ n = npos - 1; // skip this \n if (findrln(b, &n)){ // and search for previous one if (n > 0) n++; npos = n; } } if (npos < 0) npos = 0; if (p->froff != npos){ p->froff = npos; return 1; } else return 0; } static void mustshow(Panel* p, int npos) { int h; if (npos < p->froff || npos > p->froff + p->f.nchars){ if (gotopos(p, npos) && p->loaded){ h = gettextht(p)/2; scrollframe(p, -h); xdraw(p, 0); } } } static void xdraw(Panel* p, int ) { if (!p->file || (p->flags&Pdead)) return; /* Problem. For buttons, we create the data from the * file name. But, xinit does not have dfile to set its * length, and we must do that later, i.e. here. */ if (p->dfile && p->dfile->length == 0LL) p->dfile->length = blocksize(p->blks); if (hidden(p->file) || Dx(p->rect) < 10 || Dy(p->rect) < p->font->height){ if (p->loaded) frclear(&p->f, 0); p->loaded = 0; return; } hidesel(p); if (p->loaded) frclear(&p->f, 0); frinit(&p->f, p->rect, p->font, screen, cols); p->loaded = 1; filltext(p); drawsel(p); settextsize(p); if (p->flags&Ptag) drawtag(p, 0); } static void drawscrollbar(Panel* p, Image* i) { static Image* setcol; Rectangle r; Rectangle bar; int y0, dy; int ysz; int l; if (setcol == nil) setcol = allocimage(display, Rect(0,0,1,1), RGB24, 1, 0x5555AAFF); bar.max.x = r.max.x = i->r.max.x - Inset; bar.min.x = r.min.x = r.max.x - 15; r.min.y = i->r.min.y + Inset; ysz = 90; r.max.y = r.min.y + ysz; if (r.max.y > i->r.max.y){ r.max.y = i->r.max.y; ysz = Dy(r); } draw(i, r, cols[BACK], nil, ZP); l = blocklen(p->blks); if (l > 0){ y0 = ysz * p->froff / l; dy = ysz * p->f.nchars / l; if (dy < 3) dy = 3; } else { y0 = 0; dy = Dy(r); } bar.min.y = r.min.y + y0; bar.max.y = bar.min.y + dy; draw(i, bar, setcol, nil, ZP); border(i, r, 1, cols[BORD], ZP); } /* To avoid blinking, we use double buffering during * scroll operations. This is because we redraw the frame, * and do not shift rectangles around. */ static Image* scrdraw(Panel* p, Image* i) { Rectangle r; assert(p->loaded); r = Rpt(ZP, Pt(Dx(p->rect), Dy(p->rect))); if (i == nil){ i = allocimage(display, r, screen->chan, 0, CBack); } draw(i, r, cols[BACK], nil, ZP); frclear(&p->f, 0); frinit(&p->f, r, p->font, i, cols); filltext(p); drawsel(p); drawscrollbar(p, i); draw(screen, p->rect, i, nil, ZP); return i; } static void scrdone(Panel* p, Image* i) { draw(i, Rpt(ZP, Pt(Dx(i->r), Dy(i->r))), cols[BACK], nil, ZP); frclear(&p->f, 0); p->loaded = 0; freeimage(i); xdraw(p, 0); } void setsel(Panel* p, int ss, int se) { if (ss < se){ p->ss = ss; p->se = se; } else { p->ss = se; p->se = ss; } } double jumpscale(Panel* p, Point xy) { double dy; double dc; dy = xy.y - p->rect.min.y; if (dy > p->rect.max.y - xy.y) dy = p->rect.max.y - xy.y; dc = blocklen(p->blks) - p->froff; if (dc < p->froff) dc = p->froff; if (dy < 1) dy = 1; return dc / dy; } static int undocmd(Panel* p) { int pos; if (undo(p, &pos)){ dirty(p, hasedits(p), 1); mustshow(p, pos); return 1; } else return 0; } static int redocmd(Panel* p) { int pos; if (redo(p, &pos)){ dirty(p, hasedits(p), 1); mustshow(p, pos); return 1; } else return 0; } static int nlines(Rune* r, int n) { int i, nl; int lc; lc = nl = 0; for (i = 0; i < n; i++) if (r[i] == '\n' || ++lc > 80){ nl++; lc = 0; } return nl; } int textins(Panel* p, Rune* r, int nr, int pos) { int rc; rc = frameins(p, r, nr, pos); p->nlines += nlines(r, nr); event(p, "ins %11d %11d %.*S", pos, nr, nr, r); return rc; } void textdel(Panel* p, Rune* r, int nr, int pos) { if (nr > 0){ nr = framedel(p, r, nr, pos); if (nr){ p->nlines -= nlines(r, nr); event(p, "del %11d %11d", pos, nr); } } } static void writepsel(Panel* p) { char* s; if ( !(p->flags&Pline)) { s = smprint("/devs%s", p->path); writefstr("/dev/sel", s); free(s); } } static int cut(Panel* p, int putsnarf) { char* s; if (p->ss >= p->se) return 0; free(p->snarf); p->nsnarf = p->se - p->ss; p->snarf = emalloc9p((p->nsnarf+1) * sizeof(Rune)); hidesel(p); textdel(p, p->snarf, p->nsnarf, p->ss); p->se = p->s0 = p->ss; drawsel(p); filltext(p); addedit(p, Del, p->snarf, p->nsnarf, p->ss); if (putsnarf){ p->snarf[p->nsnarf] = 0; s = smprint("%S", p->snarf); writefstr("/dev/snarf", s); free(s); } return 1; } static void paste(Panel* p, int pos) { Rune* r; char* s; if (s = readfstr("/dev/snarf")){ r = utf2runes(s, nil, nil); free(s); free(p->snarf); p->snarf = r; p->nsnarf = runeslen(r); if (p->nsnarf){ textins(p, p->snarf, p->nsnarf, pos); p->s0 = p->ss = pos; p->se = pos + p->nsnarf; drawsel(p); addedit(p, Ins, p->snarf, p->nsnarf, pos); } } } static void run(Panel* p, int pos, int onsel) { Rune* r; int s, e; int n; char* c; char* ev; r = gettextword(p, pos, &s, &e); c = smprint("%S", r); if (!wcommand(c)){ if (onsel) ev = "args"; else ev = "exec"; n = e - s; event(p, "%s %11d %S", ev, runenlen(r, n), r); } free(c); free(r); } static void look(Panel* p, int pos) { Rune* r; int s, e; int n; if (r = gettextword(p, pos, &s, &e)){ assert(e >= s); p->ss = s; p->se = e; n = p->se - p->ss; if (n > 0) event(p, "look %11d %S", runenlen(r, n), r); free(r); } } static int findnln(void* a, int , Rune r) { int c = (int)r; int *np = a; if (c == '\n' && --(*np) < 0) return 1; return 0; } /* :xx searches for line number. Else, literal match. */ static void search(Panel* p, char* key) { Tblock* b; int pos; int epos; int ln; Rune* r; Rune* rp; b = p->blks; packblock(b); if (key[0] == ':' && isdigit(key[1])){ ln = strtod(key+1, nil) - 1; if (ln < 0) ln = 0; pos = 0; while(pos < b->nr && ln > 0 && findln(b, &pos)){ pos++; ln--; } epos = pos + 1; if (epos < b->nr && findln(b, &epos)) epos++; } else { r = utf2runes(key, nil, nil); if (p->ss != p->se) pos = p->ss + 1; if (pos < b->nr) rp = runestrstr(b->r + pos, r); else rp = nil; if (rp == nil){ pos = 0; rp = runestrstr(b->r + pos, r); } if (rp == nil) epos = pos = 0; else { pos = rp - b->r; epos = pos + runestrlen(r); } free(r); } if (pos || epos){ p->ss = pos; p->se = epos; mustshow(p, p->ss); drawsel(p); } } static void xmouse12(Panel* p, Cmouse* m, Channel* mc) { int updated; updated = 0; for(;;){ switch(m->buttons){ case 0: return; case 3: if (!updated++) writepsel(p); cut(p, 1); if (p->flags&Pedit) dirty(p, 1, 1); else { undo(p, nil); p->ss = p->se = 0; drawsel(p); } flushimage(display, 1); do { recv(mc, m); } while (m->buttons == 3); break; case 5: if (p->flags&Pedit) if (undo(p, nil)) dirty(p, hasedits(p), 0); flushimage(display, 1); do { recv(mc, m); } while (m->buttons == 5); break; default: recv(mc, m); break; } } } static void xmouse13(Panel* p, Cmouse* m, Channel* mc) { int updated; updated = 0; for(;;){ switch(m->buttons){ case 0: return; case 5: if (p->flags&Pedit){ cut(p, 0); paste(p, p->ss); settextsize(p); dirty(p, 1, 1); } if (!updated++) writepsel(p); flushimage(display, 1); do { recv(mc, m); } while (m->buttons == 5); break; case 3: if (p->flags&Pedit) if (undo(p, nil)) dirty(p, hasedits(p), 0); flushimage(display, 1); do { recv(mc, m); } while (m->buttons == 3); break; default: recv(mc, m); break; } } } static void xmouse1(Panel* p, Cmouse* m, Channel* mc, Point xy, int pos) { Rune* r; int ss, se; int updated; int old; if (m->flag == CMdouble){ r = gettextword(p, pos, &ss, &se); if (r){ free(r); p->ss = ss; p->se = se; writepsel(p); } } else p->ss = p->se = pos; p->s0 = p->ss; drawsel(p); flushimage(display, 1); updated = 0; old = -1; for(;;){ recv(mc, m); xy = m->xy; panelok(p); switch(m->buttons){ case 0: return; case 1: // slide pos = p->froff + frcharofpt(&p->f, xy); if (pos == old){ sleep(10); } else { old = pos; setsel(p, p->s0, pos); drawsel(p); if (p->ss != p->se && !updated++) writepsel(p); flushimage(display, 1); } break; case 3: // 1-2 chord xmouse12(p, m, mc); return; case 5: // 1-3 chort xmouse13(p, m, mc); return; default: do { recv(mc, m); } while (m->buttons); } } } static void xmouse2(Panel* p, Cmouse* m, Channel* mc, Point xy, int pos) { int updated; int old; updated = 0; old = pos; for(;;){ recv(mc, m); xy = m->xy; panelok(p); switch(m->buttons){ case 0: run(p, updated ? p->ss : pos, 0); return; case 3: // 2-1 chord run(p, pos, 1); return; case 2: if (!updated++) p->ss = p->se = p->s0 = pos; pos = p->froff + frcharofpt(&p->f, xy); if (old == pos) sleep(10); else { setsel(p, p->s0, pos); drawsel(p); flushimage(display, 1); } break; } } } static void xmouse3(Panel* p, Cmouse* m, Channel* mc, Point xy, int pos) { double jfactor; int jpos, jy, old; Image* i; recv(mc, m); xy = m->xy; switch(m->buttons){ case 0: look(p, pos); drawsel(p); flushimage(display, 1); break; case 4: // slide i = scrdraw(p, nil); flushimage(display, 1); jfactor = 0; jy = xy.y; old = p->froff; do { panelok(p); if (jfactor == 0){ pos = p->froff; jfactor = jumpscale(p, xy); } jpos = pos + (xy.y - jy) * jfactor; if (jpos == old || !gotopos(p, jpos)) sleep(10); else { old = jpos; scrdraw(p, i);; flushimage(display, 1); } recv(mc, m); xy = m->xy; } while(m->buttons); scrdone(p, i); flushimage(display, 1); break; case 5: // 3-1 chord pos = p->froff + frcharofpt(&p->f, xy); if (pos < p->ss) p->ss = pos; else if (pos > p->se) p->se = pos; drawsel(p); flushimage(display, 1); do { recv(mc, m); } while (m->buttons); break; } } static void xmouse(Panel* p, Cmouse* m, Channel* mc) { int pos; Point xy; panelok(p); if ((p->flags&Pdead) || hidden(p->file)) // paranoia return; if (Dx(p->rect) <= 0 || Dy(p->rect) <= 0) // more paranoia return; xy = m->xy; pos = p->froff + frcharofpt(&p->f, xy); newedit(p); switch(m->buttons){ case 1: xmouse1(p, m, mc, xy, pos); break; case 2: xmouse2(p, m, mc, xy, pos); break; case 4: xmouse3(p, m, mc, xy, pos); break; } } static void sendkeys(Panel* p, int ss, int se) { Tblock* b; int n; Rune* r; if (se - ss <= 0) return; b = blockseek(p->blks, &n, ss); r = emalloc9p((se - ss + 1) * sizeof(Rune)); blockget(b, n, se-ss, r); event(p, "keys %S", r); free(r); } static void xkeyboard(Panel* p, Rune r) { int k = r; Rune buf[2]; int pos; int n; if ((p->flags&Pdead) || hidden(p->file)) // paranoia return; if (Dx(p->rect) <= 10 || Dy(p->rect) <= 10) // more paranoia return; pos = p->ss; switch(k){ case Kbs: if (!(p->flags&Pedit)){ kbevent: event(p, "keys %C", k); return; } if (pos <= 0){ fdprint("top\n"); return; } if (!cut(p, 1) && pos){ pos--; mustshow(p, pos); textdel(p, buf, 1, pos); p->ss = p->se = pos; addedit(p, Del, buf, 1, pos); } dirty(p, 1, 1); filltext(p); break; case 0x17: // ^W. erase word case 0x15: // ^U. erase line if (!(p->flags&Pedit)) goto kbevent; break; case Kdel: event(p, "interrupt"); return; case Kleft: undocmd(p); break; case Kright: redocmd(p); break; case Kup: case Kdown: if (p->flags&Pline) goto kbevent; else { n = p->f.nlines / 3; if (n < 2) n = 2; if (scrollframe(p, (k == Kup) ? -n : n)) xdraw(p, 0); } break; case Kesc: if (!(p->flags&Pedit)) goto kbevent; setsel(p, p->s0, pos); drawsel(p); break; case '\n': if (p->flags&Pline){ setsel(p, p->s0, pos); drawsel(p); run(p, (pos>0 ? pos-1 : 0), 0); break; } if (p->flags&Pedit) event(p, "dirty"); // and fall... default: if (!(p->flags&Pedit)) goto kbevent; cut(p, 1); mustshow(p, pos); if (textins(p, &r, 1, pos) && pos >= p->froff+p->f.nchars-2){ p->ss = p->se = pos+1; scrollframe(p, gettextht(p)/3); xdraw(p, 0); } else p->ss = p->se = pos+1; addedit(p, Ins, &r, 1, pos); dirty(p, 1, 1); if ((p->flags&Pedit) && (r == '\n' || (p->flags&Pline))) settextsize(p); } flushimage(display, 1); } static long xread(Panel* p, void* buf, long cnt, vlong off) { char* s; long l; /* Would be better to have a real blockread here * but this suffices. */ s = block2str(p->blks, &l); cnt = genreadbuf(buf, cnt, off, s, l); free(s); return cnt; } static int validtextpos(Panel* p, int pos) { int l; l = blocklen(p->blks); if (pos < 0) pos = 0; if (pos > l) pos = l; return pos; } static long xwrite(Panel* p, void* buf, long cnt, vlong off) { int pos; int n; Rune* r; int nr; int nout; Tblock* b; Tblock* nb; int nl; char* pb; /* Would be better to have a real blockwrite here * but this suffices. */ assert(buf); pb = buf; if (off == 0LL) p->npartial = 0; if (p->npartial){ if (p->partialoff == off){ pb = emalloc9p(cnt + p->npartial); memmove(pb, p->partial, p->npartial); memmove(pb + p->npartial, buf, cnt); } else { p->npartial = 0; p->partialoff = 0; werrstr("partial rune"); return -1; } } nout = p->npartial + cnt; r = utf2runes(pb, &nout, &nl); if (nout != p->npartial + cnt){ n = p->npartial + cnt - nout; if (n >= UTFmax){ // binary file? p->npartial = 0; free(r); if (pb != buf) free(pb); werrstr("binary file?"); return -1; } p->npartial = n; memmove(p->partial, pb + nout, p->npartial); p->partialoff = off + cnt; } else { p->npartial = 0; p->partialoff = 0; } if (pb != buf) free(pb); nr = runeslen(r); bdprint("data: %d runes [%.*S]\n", nr, nr, r); if (off == 0LL){ pos = 0; blockfree(p->blks); p->blks = newblock(r, nr); p->nlines = nl; } else { pos = off2pos(p->blks, (long)off); b = blockseek(p->blks, &n, pos); b->nr = n; nb = newblock(r, nr); blockcat(b, nb); p->nlines += nl; } p->dfile->length = off+cnt; assert(pos + nr == blocklen(p->blks)); pos = pos + nr; p->mark = validtextpos(p, pos); if (p->flags&Pline) pos = validtextpos(p, pos + nr - 10); p->ss = p->se = p->s0 = pos; settextsize(p); cleanedits(p); if (!hidden(p->file)){ if (p->scroll) mustshow(p, pos); xdraw(p, 0); flushimage(display, 1); } return cnt; } static void xtruncate(Panel* p) { blockfree(p->blks); p->blks = newblock(nil, 0); p->nlines = 0; p->npartial = 0; p->mark = 0; p->dfile->length= 0LL; p->ss = p->se = p->s0 = 0; if (!hidden(p->file)){ hidesel(p); xdraw(p, 0); flushimage(display, 1); } } static int textctlwr(Panel* p, char* s) { char* q; char* r; Rune* rs; int ss, se; int pos, n; int nl; int mustdraw; mustdraw = 0; if (!strncmp(s, "ins ", 4)){ pos = strtod(s + 4, &q); pos = validtextpos(p, pos); n = 0; rs = utf2runes(s + 4 + 11 + 1 + 11 + 1, &n, &nl); if (n>0){ frameins(p, rs, n, pos); addedit(p, Ins, rs, n, pos); p->nlines += nl; } free(rs); } else if (!strncmp(s, "del ", 4)){ pos = strtod(s + 4, &q); pos = validtextpos(p, pos); n = strtod(q, &r); if (n>0){ rs = emalloc9p(sizeof(Rune)*(n+1)); framedel(p, rs, n, pos); addedit(p, Del, rs, n, pos); p->nlines -= nlines(rs, n); free(rs); } } else if (!strncmp(s, "sel ", 4)){ if (!strncmp(s + 4, "all", 3)){ ss = 0; se = blocklen(p->blks); } else { ss = strtod(s + 4, &q); se = strtod(q, &r); ss = off2pos(p->blks, ss); se = off2pos(p->blks, se); ss = validtextpos(p, ss); se = validtextpos(p, se); } if (se < ss) se = ss; p->s0 = p->ss = ss; p->se = se; drawsel(p); } else if (!strncmp(s, "undo", 4)){ if (!undocmd(p)) return -1; } else if (!strncmp(s, "redo", 4)){ if (!redocmd(p)) return -1; } else if (!strncmp(s, "search ", 7)){ search(p, s+7); } else if (!strcmp(s, "cut")){ if (cut(p, 1)){ settextsize(p); dirty(p, 1, 1); } } else if (!strcmp(s, "paste")){ cut(p, 0); paste(p, p->ss); settextsize(p); dirty(p, 1, 1); } else if (!strncmp(s, "mark ", 5)){ p->mark = off2pos(p->blks, atoi(s + 5)); p->mark = validtextpos(p, p->mark); } else if (!strncmp(s, "exec ", 5)){ if (!wcommand(s+5)) event(p, "exec %11d %s", strlen(s+5), s+5); } else if (!strncmp(s, "look ", 5)){ event(p, "look %11d %s", strlen(s+5), s+5); } else if (!strncmp(s, "scroll", 6)) p->scroll = 1; else { werrstr("bad ctl"); return -1; } setctlfilelen(p); return mustdraw; } int gettextwid(Panel* p) { return Dx(p->rect) / stringwidth(p->font, "X"); } int gettextht(Panel* p) { if (p->f.maxlines && p->loaded) return p->f.maxlines; else return Dy(p->rect) / fontheight(p->font); } static long xattrs(Panel* p, char* str, long l) { char sel[100]; seprint(sel, sel+sizeof(sel), "mark %11ld\nsel %11ld %11ld\nsize %11d %11d\n%s", pos2off(p->blks, p->mark), pos2off(p->blks, p->ss), pos2off(p->blks, p->se), gettextwid(p), gettextht(p), (p->scroll ? "scroll\n" : "")); return sprintattrs(p, str, l, sel); } static int xctl(Panel* p, char* ctl) { int r; int odirty; Font* of; of = p->font; odirty = p->flags&Pdirty; r = genctl(p, ctl); if (r < 0) r = textctlwr(p, ctl); else { if (odirty && !(p->flags&Pdirty)) setnoedits(p); if (of != p->font){ if (p->loaded){ frclear(&p->f, 1); frinit(&p->f, p->rect, p->font, screen, cols); } } } if (r >= 0 && !hidden(p->file) && (p->flags&Pline) && settextsize(p)) r = 1; return r; }