From 856abd2f7d2a8b4cef547762a0c125a54ca4c366 Mon Sep 17 00:00:00 2001 From: xfnw Date: Fri, 1 Jul 2022 15:46:23 -0400 Subject: [PATCH] Squashed 'sys/src/cmd/gopher/' content from commit 3680728b6 git-subtree-dir: sys/src/cmd/gopher git-subtree-split: 3680728b631ed65201b397f4ae3e5d1b03be42f9 --- README.md | 25 ++ dat.h | 46 +++ gopher.c | 941 +++++++++++++++++++++++++++++++++++++++++++ gopher.png | Bin 0 -> 34342 bytes icons.h | 200 +++++++++ libpanel/button.c | 189 +++++++++ libpanel/canvas.c | 51 +++ libpanel/draw.c | 322 +++++++++++++++ libpanel/edit.c | 297 ++++++++++++++ libpanel/entry.c | 192 +++++++++ libpanel/event.c | 48 +++ libpanel/frame.c | 39 ++ libpanel/group.c | 38 ++ libpanel/init.c | 13 + libpanel/label.c | 50 +++ libpanel/list.c | 190 +++++++++ libpanel/mem.c | 123 ++++++ libpanel/message.c | 104 +++++ libpanel/mkfile | 34 ++ libpanel/pack.c | 161 ++++++++ libpanel/panel.h | 203 ++++++++++ libpanel/panel.pdf | Bin 0 -> 87888 bytes libpanel/pldefs.h | 105 +++++ libpanel/popup.c | 116 ++++++ libpanel/print.c | 56 +++ libpanel/pulldown.c | 160 ++++++++ libpanel/rtext.c | 383 ++++++++++++++++++ libpanel/rtext.h | 11 + libpanel/scrltest.c | 65 +++ libpanel/scroll.c | 21 + libpanel/scrollbar.c | 146 +++++++ libpanel/slider.c | 97 +++++ libpanel/snarf.c | 58 +++ libpanel/textview.c | 250 ++++++++++++ libpanel/textwin.c | 474 ++++++++++++++++++++++ libpanel/utf.c | 30 ++ mkfile | 19 + 37 files changed, 5257 insertions(+) create mode 100644 README.md create mode 100644 dat.h create mode 100644 gopher.c create mode 100644 gopher.png create mode 100644 icons.h create mode 100644 libpanel/button.c create mode 100644 libpanel/canvas.c create mode 100644 libpanel/draw.c create mode 100644 libpanel/edit.c create mode 100644 libpanel/entry.c create mode 100644 libpanel/event.c create mode 100644 libpanel/frame.c create mode 100644 libpanel/group.c create mode 100644 libpanel/init.c create mode 100644 libpanel/label.c create mode 100644 libpanel/list.c create mode 100644 libpanel/mem.c create mode 100644 libpanel/message.c create mode 100644 libpanel/mkfile create mode 100644 libpanel/pack.c create mode 100644 libpanel/panel.h create mode 100644 libpanel/panel.pdf create mode 100644 libpanel/pldefs.h create mode 100644 libpanel/popup.c create mode 100644 libpanel/print.c create mode 100644 libpanel/pulldown.c create mode 100644 libpanel/rtext.c create mode 100644 libpanel/rtext.h create mode 100644 libpanel/scrltest.c create mode 100644 libpanel/scroll.c create mode 100644 libpanel/scrollbar.c create mode 100644 libpanel/slider.c create mode 100644 libpanel/snarf.c create mode 100644 libpanel/textview.c create mode 100644 libpanel/textwin.c create mode 100644 libpanel/utf.c create mode 100644 mkfile diff --git a/README.md b/README.md new file mode 100644 index 000000000..563b927ce --- /dev/null +++ b/README.md @@ -0,0 +1,25 @@ +gopher +======= +A mostly functional gopher browser for plan9. + +![gopher](gopher.png) + +Most gopher item types are handled: +- Text and submenu items are displayed within the browser +- Images and documents are opened through page(1) +- HTML items are sent to the plumber(4) +- Binary items (bin, dos, uuencoded files and sound) are downloaded to disk +- Other types are not handled (e.g. telnet) + +Following keyboard shortcuts are available: +- b: previous page in history +- n: next page in history +- q: quit + +This has not been thoroughly tested so many bugs are just waiting to be found. + +Usage: +------ +Install with ``mk install`` +Run with ``gopher [address]`` + diff --git a/dat.h b/dat.h new file mode 100644 index 000000000..7a2d6f2fc --- /dev/null +++ b/dat.h @@ -0,0 +1,46 @@ +typedef struct Gmenu Gmenu; +typedef struct Link Link; +typedef struct Hist Hist; + +struct Gmenu +{ + Link *link; + Rtext *text; +}; + +struct Link +{ + char *addr; + char *sel; + int type; +}; + +struct Hist +{ + Hist *p; + Hist *n; + Gmenu *m; +}; + +enum +{ + Ttext, + Tmenu, + Tns, + Terror, + Tbinhex, + Tdos, + Tuuencoded, + Tsearch, + Ttelnet, + Tbinary, + Tmirror, + Tgif, + Timage, + Tt3270, + Tdoc, + Thtml, + Tinfo, + Tsound, + Teof +}; diff --git a/gopher.c b/gopher.c new file mode 100644 index 000000000..55e6b30aa --- /dev/null +++ b/gopher.c @@ -0,0 +1,941 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dat.h" +#include "icons.h" + +void texthit(Panel *p, int b, Rtext *t); +void message(char *s, ...); + +Image *backi; +Image *fwdi; +Image *reloadi; +Panel *root; +Panel *backp; +Panel *fwdp; +Panel *reloadp; +Panel *entryp; +Panel *urlp; +Panel *textp; +Panel *statusp; +Panel *popup; +char *url; +Mouse *mouse; +Hist *hist = nil; +char *bdir; + +enum +{ + Msearch, + Maddbookmark, + Mbookmarks, + Mexit, +}; + +char *menu3[] = { + "search", + "add bookmark", + "bookmarks", + "exit", + 0 +}; + +Link* +mklink(char *addr, char *sel, int type) +{ + Link *l; + + l = malloc(sizeof *l); + if(l==nil) + sysfatal("malloc: %r"); + l->addr = strdup(addr); + l->sel = sel!=nil ? strdup(sel) : nil; + l->type = type; + return l; +} + +Link* +clonelink(Link *l) +{ + if(l==nil) + return nil; + return mklink(l->addr, l->sel, l->type); +} + +int +seltype(char c) +{ + int t; + + t = -c; + switch(c){ + case '0': t = Ttext; break; + case '1': t = Tmenu; break; + case '2': t = Tns; break; + case '3': t = Terror; break; + case '4': t = Tbinhex; break; + case '5': t = Tdos; break; + case '6': t = Tuuencoded; break; + case '7': t = Tsearch; break; + case '8': t = Ttelnet; break; + case '9': t = Tbinary; break; + case '+': t = Tmirror; break; + case 'g': t = Tgif; break; + case 'I': t = Timage; break; + case 'T': t = Tt3270; break; + case 'd': t = Tdoc; break; + case 'h': t = Thtml; break; + case 'i': t = Tinfo; break; + case 's': t = Tsound; break; + case '.': t = Teof; break; + default: break; + } + return t; +} + +static char Typechar[] = { + '0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', '+', 'g', + 'I', 'T', 'd', 'h', 'i', 's', + '.', +}; + +static char *Typestr[] = { + "FILE", "DIR", "NS", "ERR", "HEX", + "DOS", "UU", "?", "TELNET", "BIN", + "MIRROR", "GIF", "IMG", "T3270", "DOC", + "HTML", "", "SND", "EOF", +}; + +char* +seltypestr(int type) +{ + if(type<0) + return smprint("UNKN:%c", (char) -type); + return smprint("%6s", Typestr[type]); +}; + + +Gmenu* +rendermenu(Link *l, Biobuf *bp) +{ + char *s, *f[5], *t; + Gmenu *m; + Link *n; + int type; + + m = malloc(sizeof *m); + if(m==nil) + sysfatal("malloc: %r"); + m->link = clonelink(l); + m->text = nil; + plrtstr(&m->text, 1000000, 0, 0, font, strdup(" "), 0, 0); + for(;;){ + n = nil; + s = Brdstr(bp, '\n', 0); + if(s==nil || s[0]=='.') + break; + type = seltype(s[0]); + getfields(s+1, f, 5, 0, "\t\r\n"); + switch(type){ + case Tinfo: + break; + case Thtml: + n = mklink(strdup(f[1]+4), nil, Thtml); /* +4 skip URL: */ + break; + default: + n = mklink(netmkaddr(f[2], "tcp", f[3]), strdup(f[1]), type); + break; + } + t = strdup(f[0]); + plrtstr(&m->text, 1000000, 8, 0, font, seltypestr(type), PL_HEAD, 0); + if(type == Tinfo || type < 0) + plrtstr(&m->text, 8, 0, 0, font, t, 0, 0); + else + plrtstr(&m->text, 8, 0, 0, font, t, PL_HOT, n); + + } + return m; +} + +Gmenu* +rendertext(Link *l, Biobuf *bp) +{ + Gmenu *m; + String *buf; + int c, n; + + m = malloc(sizeof *m); + if(m==nil) + sysfatal("malloc: %r"); + m->link = clonelink(l); + m->text = nil; + plrtstr(&m->text, 1000000, 0, 0, font, strdup(" "), 0, 0); + n = 0; + buf = s_new(); + for(;;){ + c = Bgetc(bp); + if(c<0) + break; + else if(c=='\r' || c=='\n'){ + if(c=='\r' && Bgetc(bp)!='\n') + Bungetc(bp); + if(n==1 && s_to_c(buf)[0]=='.') + break; + s_terminate(buf); + plrtstr(&m->text, 1000000, 8, 0, font, strdup(s_to_c(buf)), 0, 0); + s_reset(buf); + n = 0; + }else if(c=='\t'){ + n += 4; + s_append(buf, " "); + }else{ + n++; + s_putc(buf, c); + } + } + s_free(buf); + return m; +} + +Gmenu* +render(Link *l) +{ + int fd; + Biobuf *bp; + Gmenu *m; + + fd = dial(l->addr, 0, 0, 0); + if(fd < 0){ + message("unable to connect to %s: %r", l->addr); + return nil; + } + fprint(fd, "%s\r\n", l->sel); + bp = Bfdopen(fd, OREAD); + if(bp==nil){ + close(fd); + sysfatal("bfdopen: %r"); + } + switch(l->type){ + case Tmenu: + m = rendermenu(l, bp); + break; + case Ttext: + m = rendertext(l, bp); + break; + default: + /* TODO error */ + m = nil; + break; + } + Bterm(bp); + close(fd); + return m; +} + +void +message(char *s, ...) +{ + static char buf[1024]; + char *out; + va_list args; + + va_start(args, s); + out = buf + vsnprint(buf, sizeof(buf), s, args); + va_end(args); + *out='\0'; + plinitlabel(statusp, PACKN|FILLX, buf); + pldraw(statusp, screen); + flushimage(display, 1); +} + +Link* +urltolink(char *url) +{ + char *a, *sel, *hostport, *p; + int type; + Link *l; + + a = strdup(url); + hostport = a; + if(strncmp(a, "gopher://", 9) == 0) + hostport += 9; + p = strchr(hostport, '/'); + if(p){ + *p++ = 0; + type = *p ? seltype(*p++) : Tmenu; + if(type < 0) + return nil; + sel = *p ? p : ""; + }else{ + type = Tmenu; + sel = ""; + } + p = strchr(hostport, ':'); + if(p){ + *p++ = 0; + l = mklink(netmkaddr(hostport, "tcp", p), sel, type); + }else{ + l = mklink(netmkaddr(hostport, "tcp", "70"), sel, type); + } + free(a); + return l; +} + +char* +linktourl(Link *l) +{ + char *f[3], *a, *s; + int n; + + a = strdup(l->addr); + n = getfields(a, f, 3, 0, "!"); + if(n != 3) + s = smprint("Url: gopher://%s/%d%s", l->addr, l->type, l->sel); + else if(atoi(f[2])!=70) + s = smprint("Url: gopher://%s:%s/%d%s", f[1], f[2], l->type, l->sel); + else + s = smprint("Url: gopher://%s/%d%s", f[1], l->type, l->sel); + free(a); + return s; +} + +void +seturl(Link *l) +{ + free(url); + url = linktourl(l); +} + +void +show(Gmenu *m) +{ + plinittextview(textp, PACKE|EXPAND, ZP, m->text, texthit); + pldraw(textp, screen); + plinitlabel(urlp, PACKN|FILLX, url); + pldraw(urlp, screen); + message("gopher!"); +} + +void +freetext(Rtext *t){ + Rtext *tt; + Link *l; + + tt = t; + for(; t!=0; t = t->next){ + t->b=0; + free(t->text); + t->text = 0; + if(l = t->user){ + t->user = 0; + free(l->addr); + if(l->sel!=nil && l->sel[0]!=0) + free(l->sel); + free(l); + } + } + plrtfree(tt); +} + +void +freehist(Hist *h) +{ + Hist *n; + Gmenu *m; + + for(n = h->n; h; h = n){ + m = h->m; + freetext(m->text); + if(m->link!=nil){ + free(m->link->addr); + free(m->link->sel); + free(m->link); + } + free(h); + } +} + +void +visit(Link *l, int sethist) +{ + Gmenu *m; + Hist *h; + + seturl(l); + message("loading %s...", url); + m = render(l); + if(m==nil) + return; + show(m); + if(!sethist) + return; + h = malloc(sizeof *h); + if(h == nil) + sysfatal("malloc: %r"); +/* FIXME + if(hist != nil && hist->n != nil) + freehist(hist->n); +*/ + h->p = hist; + h->n = nil; + h->m = m; + hist = h; +} + +void +plumburl(char *u) +{ + int fd; + + fd = plumbopen("send", OWRITE|OCEXEC); + if(fd<0) + return; + plumbsendtext(fd, "gopher", nil, nil, u); + close(fd); +} + +void +dupfds(int fd, ...) +{ + int mfd, n, i; + va_list arg; + Dir *dir; + + va_start(arg, fd); + for(mfd = 0; fd >= 0; fd = va_arg(arg, int), mfd++) + if(fd != mfd) + if(dup(fd, mfd) < 0) + sysfatal("dup: %r"); + va_end(arg); + if((fd = open("/fd", OREAD)) < 0) + sysfatal("open: %r"); + n = dirreadall(fd, &dir); + for(i=0; i= mfd) + close(fd); + } + free(dir); +} + +void +page(Link *l) +{ + int fd; + + fd = dial(l->addr, 0, 0, 0); + if(fd < 0) + sysfatal("dial: %r"); + fprint(fd, "%s\r\n", l->sel); + switch(rfork(RFFDG|RFPROC|RFMEM|RFREND|RFNOWAIT|RFNOTEG)){ + case -1: + fprint(2, "Can't fork!"); + break; + case 0: + dupfds(fd, 1, 2, -1); + execl("/bin/rc", "rc", "-c", "page -w", nil); + _exits(0); + } + close(fd); +} + +void +save(Link *l, char *name){ + char buf[1024]; + int ifd, ofd; + + ifd = dial(l->addr, 0, 0, 0); + if(ifd < 0){ + message("save: %s: %r", name); + return; + } + fprint(ifd, "%s\r\n", l->sel); + ofd=create(name, OWRITE, 0666); + if(ofd < 0){ + message("save: %s: %r", name); + return; + } + switch(rfork(RFNOTEG|RFNAMEG|RFFDG|RFMEM|RFPROC|RFNOWAIT)){ + case -1: + message("Can't fork: %r"); + break; + case 0: + dup(ifd, 0); + close(ifd); + dup(ofd, 1); + close(ofd); + + snprint(buf, sizeof(buf), + "{tput -p || cat} |[2] {aux/statusmsg -k %q >/dev/null || cat >/dev/null}", name); + execl("/bin/rc", "rc", "-c", buf, nil); + exits("exec"); + } + close(ifd); + close(ofd); +} + +void +search(void) +{ + static char last[256]; + char buf[256]; + Reprog *re; + Rtext *tp; + int yoff; + + for(;;){ + if(hist == nil || hist->m == nil || hist->m->text == nil) + return; + strncpy(buf, last, sizeof(buf)-1); + if(eenter("Search for", buf, sizeof(buf), mouse) <= 0) + return; + strncpy(last, buf, sizeof(buf)-1); + re = regcompnl(buf); + if(re == nil){ + message("%r"); + continue; + } + for(tp=hist->m->text;tp;tp=tp->next) + if(tp->flags & PL_SEL) + break; + if(tp == nil) + tp = hist->m->text; + else { + tp->flags &= ~PL_SEL; + tp = tp->next; + } + while(tp != nil){ + tp->flags &= ~PL_SEL; + if(tp->text && *tp->text) + if(regexec(re, tp->text, nil, 0)){ + tp->flags |= PL_SEL; + plsetpostextview(textp, tp->topy); + break; + } + tp = tp->next; + } + free(re); + yoff = plgetpostextview(textp); + plinittextview(textp, PACKE|EXPAND, ZP, hist->m->text, texthit); + plsetpostextview(textp, yoff); + pldraw(textp, screen); + } + +} + +void +addbookmark(void) +{ + Link *l; + char buf[255] = {0}, *f, *u[3]; + int n, fd; + + if(hist==nil) + return; + l = hist->m->link; + n = eenter("Name:", buf, sizeof buf, mouse); + if(n<=0) + return; + f = smprint("%s/bookmarks", bdir); + fd = open(f, OWRITE); + if(fd<0){ + fd = create(f, OWRITE, 0644); + if(fd<0){ + message("cannot open %s", f); + free(f); + return; + } + fprint(fd, "iGOPHER Bookmarks\n"); + fprint(fd, "i=================\n"); + fprint(fd, "i \n"); + } + free(f); + f = strdup(l->addr); + getfields(f, u, 3, 0, "!"); + seek(fd, 0, 2); + fprint(fd, "%c%s\t%s\t%s\t%s\n", Typechar[l->type], buf, l->sel, u[1], u[2]); + fprint(fd, "i%s\n", linktourl(l)); + free(f); + close(fd); + message("added bookmark %s", buf); +} + +void +showbookmarks(void) +{ + char *f; + Biobuf *bp; + Gmenu *m; + + f = smprint("%s/bookmarks", bdir); + bp = Bopen(f, OREAD); + if(bp==nil){ + message("cannot open %s", f); + free(f); + return; + } + m = rendermenu(nil, bp); + show(m); + free(f); + Bterm(bp); +} + +char* +linktofile(Link *l){ + char *n, *s; + + if(l==nil) + return nil; + n = l->sel; + if(n==nil || n[0]==0) + n = "/"; + if(s = strrchr(n, '/')) + n = s+1; + if(n[0]==0) + n = "file"; + return n; +} + +void +texthit(Panel *p, int b, Rtext *t) +{ + Link *l; + char *s, buf[1024] = {0}; + + USED(p); + if(b!=1) + return; + if(t->user==nil) + return; + l = t->user; + switch(l->type){ + case Tmenu: + case Ttext: + visit(l, 1); + break; + case Thtml: + plumburl(l->addr); + break; + case Tdoc: + case Tgif: + case Timage: + page(l); + break; + case Tsearch: + if(eenter("Search:", buf, sizeof buf, mouse)>0){ + s = smprint("%s\t%s", l->sel, buf); + visit(mklink(l->addr, s, Tmenu), 1); + free(s); + } + break; + case Tdos: + case Tbinary: + case Tbinhex: + case Tuuencoded: + snprint(buf, sizeof buf, "%s", linktofile(l)); + if(eenter("Save as:", buf, sizeof buf, mouse)>0){ + save(l, buf); + } + break; + default: + message("unhandled item type '%s'", Typestr[l->type]); + break; + } +} + +void +backhit(Panel *p, int b) +{ + USED(p); + if(b!=1) + return; + if(hist==nil || hist->p==nil) + return; + hist->p->n = hist; + hist = hist->p; + seturl(hist->m->link); + show(hist->m); +} + +void +nexthit(Panel *p, int b) +{ + USED(p); + if(b!=1) + return; + if(hist==nil || hist->n==nil) + return; + hist = hist->n; + seturl(hist->m->link); + show(hist->m); +} + +void +reloadhit(Panel *p, int b) +{ + USED(p); + if(b!=1) + return; + visit(hist->m->link, 0); +} + +void +menuhit(int button, int item) +{ + USED(button); + + switch(item){ + case Msearch: + search(); + break; + case Maddbookmark: + addbookmark(); + break; + case Mbookmarks: + showbookmarks(); + break; + case Mexit: + exits(nil); + break; + } +} + +void +entryhit(Panel *p, char *t) +{ + Link *l; + + USED(p); + switch(strlen(t)){ + case 0: + return; + case 1: + switch(*t){ + case 'b': + backhit(backp, 1); + break; + case 'n': + nexthit(fwdp, 1); + break; + case 'q': + exits(nil); + break; + default: + message("unknown command %s", t); + break; + } + break; + default: + l = urltolink(t); + if(l==nil) + message("invalid url %s", t); + else + visit(l, 1); + } + plinitentry(entryp, PACKN|FILLX, 0, "", entryhit); + pldraw(root, screen); +} + +void +mkpanels(void) +{ + Panel *p, *ybar, *xbar, *m; + + m = plmenu(0, 0, menu3, PACKN|FILLX, menuhit); + root = plpopup(0, EXPAND, 0, 0, m); + p = plgroup(root, PACKN|FILLX); + statusp = pllabel(p, PACKN|FILLX, "gopher!"); + plplacelabel(statusp, PLACEW); + plbutton(p, PACKW|BITMAP|NOBORDER, backi, backhit); + plbutton(p, PACKW|BITMAP|NOBORDER, fwdi, nexthit); + plbutton(p, PACKW|BITMAP|NOBORDER, reloadi, reloadhit); + pllabel(p, PACKW, "Go:"); + entryp = plentry(p, PACKN|FILLX, 0, "", entryhit); + p = plgroup(root, PACKN|FILLX); + urlp = pllabel(p, PACKN|FILLX, ""); + plplacelabel(urlp, PLACEW); + p = plgroup(root, PACKN|EXPAND); + ybar = plscrollbar(p, PACKW|USERFL); + xbar = plscrollbar(p, IGNORE); + textp = pltextview(p, PACKE|EXPAND, ZP, nil, nil); + plscroll(textp, xbar, ybar); + plgrabkb(entryp); +} + +void +eresized(int new) +{ + if(new && getwindow(display, Refnone)<0) + sysfatal("cannot reattach: %r"); + plpack(root, screen->r); + pldraw(root, screen); +} + +Image* +loadicon(Rectangle r, uchar *data, int ndata) +{ + Image *i; + int n; + + i = allocimage(display, r, RGBA32, 0, DNofill); + if(i==nil) + sysfatal("allocimage: %r"); + n = loadimage(i, r, data, ndata); + if(n<0) + sysfatal("loadimage: %r"); + return i; +} + +void +loadicons(void) +{ + Rectangle r = Rect(0,0,16,16); + + backi = loadicon(r, ibackdata, sizeof ibackdata); + fwdi = loadicon(r, ifwddata, sizeof ifwddata); + reloadi = loadicon(r, ireloaddata, sizeof ireloaddata); +} + +void scrolltext(int dy, int whence) +{ + Scroll s; + + s = plgetscroll(textp); + switch(whence){ + case 0: + s.pos.y = dy; + break; + case 1: + s.pos.y += dy; + break; + case 2: + s.pos.y = s.size.y+dy; + break; + } + if(s.pos.y > s.size.y) + s.pos.y = s.size.y; + if(s.pos.y < 0) + s.pos.y = 0; + plsetscroll(textp, s); + /* BUG: there is a redraw issue when scrolling + This fixes the issue albeit not properly */ + pldraw(textp, screen); +} + +void +ensurebdir(void) +{ + char *home, *tmp; + int fd; + + home = getenv("home"); + if(home){ + tmp = smprint("%s/lib", home); + fd = create(tmp, OREAD, DMDIR|0777); + if(fd>0) + close(fd); + free(tmp); + bdir = smprint("%s/lib/gopher", home); + fd = create(bdir, OREAD, DMDIR|0777); + if(fd>0) + close(fd); + }else + bdir = strdup("/tmp"); +} + +void +main(int argc, char *argv[]) +{ + enum { Eplumb = 128 }; + Event e; + Link *l; + char *url; + Plumbmsg *pm; + + if(argc == 2) + url = argv[1]; + else + url = "gopher.floodgap.com"; + quotefmtinstall(); + ensurebdir(); + if(initdraw(nil, nil, "gopher")<0) + sysfatal("initdraw: %r"); + einit(Emouse|Ekeyboard); + plinit(screen->depth); + loadicons(); + mkpanels(); + l = urltolink(url); + if(l==nil) + message("invalid url %s", url); + else + visit(l, 1); + eresized(0); + eplumb(Eplumb, "gopher"); + for(;;){ + switch(event(&e)){ + case Eplumb: + pm = e.v; + if(pm->ndata > 0){ + l = urltolink(pm->data); + if(l!=nil) + visit(l, 1); + } + plumbfree(pm); + break; + case Ekeyboard: + switch(e.kbdc){ + default: + plgrabkb(entryp); + plkeyboard(e.kbdc); + break; + case Khome: + scrolltext(0, 0); + break; + case Kup: + scrolltext(-textp->size.y/4, 1); + break; + case Kpgup: + scrolltext(-textp->size.y/2, 1); + break; + case Kdown: + scrolltext(textp->size.y/4, 1); + break; + case Kpgdown: + scrolltext(textp->size.y/2, 1); + break; + case Kend: + scrolltext(-textp->size.y, 2); + break; + case Kdel: + exits(nil); + break; + } + break; + case Emouse: + mouse = &e.mouse; + if(mouse->buttons & (8|16) && ptinrect(mouse->xy, textp->r)){ + if(mouse->buttons & 8) + scrolltext(textp->r.min.y - mouse->xy.y, 1); + else + scrolltext(mouse->xy.y - textp->r.min.y, 1); + break; + } + plmouse(root, mouse); + /* BUG: there is a redraw issue when scrolling + This fixes the issue albeit not properly */ + //pldraw(textp, screen); + break; + } + } +} + diff --git a/gopher.png b/gopher.png new file mode 100644 index 0000000000000000000000000000000000000000..e3b272fbf7d6e3b17c19ff7345634b99a0221cf8 GIT binary patch literal 34342 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

R#x{^ zJEu90R+K3CQ7TC-YKdu~a+u(ltuxdjhh`QL5tg71tKh2l<;6^jc=GJK>*v z#F@sk&f*~-?o4z<4|}tnd2r#~7nZik5w85Xs@{93QU#{(ZMSEf%xx24unz{R`Q;k= zWs@cZ)Xw`Lz&|No*VXdKzbJHhSnfheM|R|>HcZX3954UKq>$ap@mYLIQ9vK}OLEu^}r3 zv6qMH<$ojBp+nzZ7Yw0BXIXdvjsx|YqnHmDc-Un%wO_+CA4}D=cxZP=hu}w?i<8xc z7Tr3s^|FO2t4c=0RYK0Lw@a-etvdDGO8x)^oDPp#i}FdurfM+yxCzYbbt-bv9>Iwr zu9_D9k6U`-8rDbCzFf#D- zY>g)vQBD|7@02+M3Sj$3@?paB4(t@K+ZQRvxk#xT0mtPJsgamA6Ou{wE5qBznKkdm z0ioVGkwUf`&9a?;u9| zn}{f#&yZqNnJt_hmU@hc<)Ur3%#)5|11&{!pEzZ}5D7i-qOo$jZk)M%7<>?nbv3di zed%%H2K~&tY6)d-am~J+T}oR@JatH-vk5Jq@fTwP;ZCtn$hLUou{PcYQf9yThmrRE zY?|aiAr}&()~eEM8s14V>TVm-gP31g+y#Z4`mH>He#ZZsgQxLtavt}&-znt)LBfAF zn0ZFVwQTrYEiRUNPCFuRcpVIn&mSuxaFH(cQ#7&GN`M!_l%9R!0ug!FUj^BCWNrWA zee6?IyLGbtriIrP|D%Hyh~(@_(s4-`v#hz?zzE>gECoJ5jx~`M6D;1ChUi1>u&h;& zBUX2PBpgIrKpPh-p1b?}r0=OVhKcOa~ggmZ;R%1P%=)*lI8;SU`#eO!7<4e%Ghi(kI2%Tv(5cNmXMBq1Z5K zq-iz-=qKGZOXxbJhEm63k{(mBD_*@6Vj}@@o=vYekX)bIl&q-TB(@+=L~2_^OfDUN z-#VPyzJ~r{te@*3lm$sf#T0Pb*0#@ht))2+9nWOMgkk}yDw04z%7Qa2HFSFZ0YJ`i zZ9@(f{@2JRZHO|`T)ZgF__NMQrWt=6AtH<;>0Ou&-J% zK4#CU9%yXasI%|XAzf94YXfUjt$yTQ;Ta>2Mcfy+w@hkXRr+&rcQ&BSO*tWQrnKmb zE%KhRP3$QU^+t4*tBP8fD5Y*H^`o`6RZ+zL5iHz0KaUMF*W*Xl1H1onc1)MY| z{Km%COA|1FYh# zlaa+TwI~5Z9M~D0>1G${K%O`M>1ND3XF7Muu38*Vcoj6Kk5Bqqts$6L?p3~j$M0G@ zVo8lwBoot_kFA5v9DjjO=q#HfQ;#+Bcg`gw*7sQ8U;Z^wn_|vjoCwyPY>GAkJbqEI z@TTwB*xrUK0Ez*yZ2%t=nb(!~SLaczAdrw}eDh@l z`-Iki9QYEJT6kE~M6k@Ip#i!<)ZbBS*>c41u|`I67}D-q7JuPPIizA(9C~I;IRTEz{~_LCZDu zltCxfx+dPaG>NZ9OFoNz6JwX!8S*M{Rnx^{jf0gQEQFhvc${HdVlMljgvZ|qTnO|8 zU?QO9<*LD8!sS-6K>*Hp&uO|5s*@2eLB0i-;FcZhefr^=2`(KsZ z2~U@v3jZ_(cm|Hc00D|bxfPA(=CeAidGB|`I)n`Ljp=98-2{wJleOxv)UuC41!{U* z!J^t=JI@lotWGWn=?-N8a4#L(by|AzoqKjC?TCjzHH&iV{@KiTGe3rPA<3U3uI?VE z$FErhP;y!CR-hv$Tv3tjOdeynC&Kp>qLW-$i6}jAvw)iey@)NQKe3hi$EUEr;9yfh z!luI1?$XD8Ze>ZtXAO@TE_ZKyOOi-Zg>vZ96#1Y&J5g`S5L+lw$Cee~!xg0%=C zmvYQb0W&oavs%=wzWZ*e=^cU`Vr2R4f&l>d;{d`^v7M|hF74r!EwOmS9Pr$9{*8S7 zM+EGDu?>EWR`bw5Fbbr$t*fDh5Znq=N1nNiV}7os*MS%tYw+C)+?9C5;o^ zQpDuw!IEF-Ve_@SZu1Ow6XC@4L3XusT*0GH*sNS-c!IH&%)YD3`=5?S6@ZZ5Rj_UR zzMAlwr!VSyUiLOic9+gNY1ykW)kjv&nZUu z>c9?<(&kjirV?wc$*&;ph^f3oLP9Az?uAHE%NfK)1>|Bf%B6*-`_6Iow~Rwx!mRm| zaD&6B!1U|+EP#l&ttT>+=dO_~gFGtTrMx&I!yvbisG}503E!LN*#Tf=Wf|Njg9$SN zz|p44CwK|&`MXhTh)Jop!wnMj>=A36rJdeFqkR+9A{GTC|D1dxzyisSNL+<%YL*{! zz9KV_N0%Ls@+hez(m0wzVE{)F;CQXEgR2?4miJ@J)kgJjvslj`f7&%YdmuPAo=KKc zcom_S_@G8KnCUGy@2V9xH#IfTjg{h*Ww75s{*GJp1n+-?S#Ng%A375`=&mbEd)p~jL>jA8^^*@=O|I=n|chq`*64^9NGAZdFRlqg}Q{G;a z%*o!J7=|1+K~95W0cYns>Yw&D08g9cRI@^=8gC7QG zT;w+1XQkO~7?aswG;qx~QodcuJv>dSn7|2=`{EVvmh&cvdiKD{u)9R6I6eKiP#{ri zWQuukepJd^-Ho?1TR^g3ljxD_IPM$d1{v{zANiQGR_cm`%<3drG^K6&MP>~t(a<_e zCKz97Oozft2)3@vn07;be}#nvhw)dkP0KScHmXsc7dHNyk7r-%!{4$Sd-0j-u$&b! zfUDl9*Kn-6Ny!V*?@P=o_UJ81)OR05YUeSX(=T{V<-=T|3@YT#SKOq@Y!uTS8UoBi z7xtnmAV*pbTt!QUyTR$rpF3wN;x4BzIWE1oBw2Q2D(rJE6I7iBa%Ps*7o*sN*}%c9 zi)`$Y41TFySp$iIRpPWs4Z4?2fXkPjtTq834!gRpkyLb4i#HjQ!6_p%#FBRMsE?!_ ze|aY_tsEg7t<0vxq~qs}CEFLWkXG{gQyn3-1`A0`MMM*C(9^sVIfGHoZ`Wg~61Qwx zrqIeb8A1^HYKD}&p9i|#yx0GT?=ub@IjXN`Q$>n*O0uZ(ZA31eP0zaN++aU>H^Q?K zss28TV~N$`p+x#75yErot8aAF-L1t#058$R%5L^^YCDP%6d)*C@wIlI{TgWN$!|?* zOY6Vc{Ec24cR3*fQa=F(&OjZ-=}!M)L%yZLDu3`%#W14)s-PADEMFoLUC6!KKy7lZ zGp>3MK8sKZ&IU&D<#mZJ>b)dww;#AmWR(lkN|*cG{ErJYHfBtSBVf77Tp1d5*1BPS zn;8J&k4-qmxv~Gkihu5_K+wm(Z|>8_>#Q9aJ`;{%tUxo&_QK8ffl%nn=as6XCCOdO zH#n?!&OK|2hNg!65O7q-nDN}c8?f*FhIH9Wtf^LXsACGzPI^Gy?$0lA`qgaKl4fZm z|QtOf`W|9Ddfk_edBRQQsPXKP1ujb4=&{l-BeT3kP z2D(U7fqM$eoMs#|^IQ|!E3tFgDD)fm$4U=F|UG9NfA#3?vVk`xVmA7a96ox zZXT!x!&xbDU&qw?Q~b~xBq6Jsh#tUplV!Ye9*=W|`Q9m9%e1$if$)FcQ`yzK48+6h zCiKExCE7EzFVo`tTS^H5jpE%}*+GAGv*t&BtlNB?y%OSuFa;q1~<|@0;2;xtyiWu>ob!MDVYS8qL3@}69N{}kCFdxe}I($h!Or~5&Uo96vT4_ zK`f}Zmr#T|>A6q$>XD%h2m%3&zvVwk@eaknt9lXwjFsiYP{1 zWJU2SjMO=d{&`^-@09sMR! zth2dd$op(-c!)jx9$>hXm3M*1|B$a2qsBqEpUmz_lkdtr%KcWFexP;D5up@2q651% zs8kyl{RNzp!Is?wo%HJN+tBh3Hb*@_kR5i3?xSm)hgqV@f~U z`4qbITvMevN|v+m_~@YYUTDqQ=5ED526oCqOt`f|+QLr<(~RK}?znDw`zFC+zw(+D zu_@hsNjgv&4JKdKHDFWM?_#V)dzBJ6all23w6jtY%lFFZkld=K=S$SkCa+kfEFVF* zY6NBhZFXAU8A=__xErIoI114ND3w2Xek{&-BA8#96_!>uY)(!Kut{eCeXT6-+K6U% zA9iFGdrlh7;l2>)1g=rqqkFtB&7_NO4mca$dN1W~l=RhfCG#PVbAh*JB$LB@SGmCK z8c;%);oaD`2{D2B*V-Q3IHTR|gj1DaW$H>`O0H9(i9drk?oD8^(C)Y-z!4Y;(BgU= zf0Tjoc2wrY^D~6pvc|2<1q$}(r_2?^e(PEoOlXmM;H_6=+3b-|d#EJNwV7?5OsaCq zPkqyw(0|RAP$ajSc~_ZN9+2$#a#cQUazzOQ(v@MGm}v<~-53x2)Ef}YTgr4~lGf7k zX+A62<`Q$MaPx1;q5IZ3En%Hd-x~k99TftoOQb)$ad<6!k53id~bg_H?W!OI>H$!&*lNzyThtw5k(WXN* z8#>TsOb)wpFhlOn!fY2~p1~?n-P#IE*=$B@W=6)lI{U-No~%-_H$v}q?ev5_Vp=i+ z0}*beaR7f8D(6VKnTrL+r#oHL-LDXQiONDH8JIBLp2nX!T*=yh7h()3Pjwgm#q4SR zi`h%~7qiFzFJ|vQGS5<9s}XgmN=@gL(Js1x!U(Fx*F>$M{?ee0548lESh%t8sbkccP;ElzwjJ7dBPPKczn#9MWMI4x97j*ohjYET zfA!^ODh)!gz0~^G3c_RVbDzBME7L)2w8V}_HfO~?PZt=~@+oVSaCuC=JKYm~)zj{| z@si~%ocrVc!IhkAD!;aj+y4fER9>%%89e3xmAv`R&LmLoZHH)<%;pTd0=cQf9DQIW zW|j*uODf1iH;dZrUo1&-MtS>nh%(~W1~z4c31mHcy+`eTfO3HbDwNGq4nn6a`qWH! z_)hRa5!!u+^m?{45`PMmbD!vDqEDjMr7@W-E&QsxoTIZuqkxCAX0%Io;qr>z02g_7 z;t7pN_z6j-`AJ;1>V!D+`AIg)j}tP@kH0HCXrB;lrkrqSiu`XIzGPSQ%jA${e4u~t z$VN+Mg6-UkMQHi&a6m7@=`^C}m$( zl5A!r?v)32$}5Oy+!fzFHj;2<25H? z5j%C)Devo~V_ov8O*xNH%yYq~zN&_{-yjH31Qh~4_d{{X(3;o*jJ$UJ7%~r^vg$R@ zhgImb*-gU-CXw^H3O({~Y34l1mU+;q;ttIbxb&uUHQco0s@WnQ3OGQ5F_r0p}Q9M*E)m z5H+Rb9yZ;j1bGMnyG6>b^4OzPYKx{}_SqwsHCOw3M;lLr;D-yUqsQjH`597|Qx)6s zj)bBdR~m@EKcxwsuk>!cPVtZKe@{-djI0n0z-*OV@M=kIKc>K4cvw zgu{^3?Fl9KgeSQsr5yct_dkvBgXF$zhPz6(>!liBs2B^B=gJxk745MbbN3WgI6tGL zzl{w_xp#8{vn!Apl=FmQz+GzsF^^wx8gWW|82Fj`a5Gbd_mY|n`KQCMMb)itpWq^@ zQNK>Ylv2+`2j^jC(^m-;yAKHDJ^PY&u zyvYGNg!UCE0kC=F@cd(0p4krOP4wZ?-;I)8vk$k#+Kquk;QW6xy?{{-B=G>XL=sNW zua>`y2WU%nf#tW=1yYI?tizhVZ(Em}jH%4^I|h3z0`qM6S}s6EAbmULi+xOPjnOAY z?#(L}2y~hKHw4bkX?+UdU4h0?yf=L55hAiyvsV!*Fnv`2eaagV7i$W)wm`}sFN3w| zC*EycU{m3ygJqJRK80Gx^sW^fSvD8V}KHjlgD`~03n-yL@ zed?tSa6eg+5k3q_lA&6-Vv%R)!rMA$8es=cs-1iUpy?x&abEmx-{=RR>P9*=4ra>z zkp5lrp6eHnbe<70w{u5o($9p(*Bc)@`pp#M0LXpyX2Im=~<{fkX1cBpJ ziQPY=H~hCZ_x|48ri(od;_1E7IOif3DmBXovdmB=zT*n+jpjQ28I(p5>soh(+QWcOS*szN zmkq%_U<%5{40<5|wYmuDI3jSst8wYb7}NS>&&7W#g=D-t3XsPLk)3b=(CVe5WSpdg{IORUYes z7rEQ0N2`SH_{LJgSj@df1x02Mv(N4g4&-R$X{oEv6J8lp!mhHXcw@S$ox-bZcV6a^D(CO86%~yj!tLCZZPZ|$mer|>XLc8MBmCUCGL~! zda^9W7$|gJ_63+W*sGdm6Vm%2z}tCyH8>OWRhL!OPohu4q(ll&BnC@wn`q#W(*5K- z?{?JA<^9OmS%E@knNu$_fot*(NG!%S)YWfbH;M1?w~snkd#q_6TeqhAEVmJPm))>j ztG+Fv{*QgZAkXt^aaQg6x6i7#lAnf+iOhaD8mZAupajGGct&-D`ok&Kx$6SDsuWKt z12n8!g#%O<&D(|CnQwC4a@wRaZ{{$8tpAFm+>{?YqR-u(qixL%f__S$p14L^|EWj? z5~i5{eSN?X|BpBMKkVYxMhE{47iT>4m_~!wGN}I9|8T~$eoCHMvf-YH>aRcke^3DZ zZ%Bm}R*vUFdQ=5AeTyTBh(8c2P9)iB&ua&+^6C({*EfoYO+53Bf>vt;BS&Js2WL#p6}UUuJ8 zmLIJ9j|NpAx?bGehP)q6F;p@g(sLt>cv;8ZxQm1@$NK(Jq)R!Y0z{X@uR=Y(x{R!` zOO(zIm}J9?=p6VWHY+~zEJ*iW6=UPMLvS%}U$GaOHG6Qv+(vYER zvfkBmjbtmp_r*jXzE) z*RobK-^MxHv!5nAC@&=#OBtm3SqXd{S(*HMXVb(YQlVf?h2J62fJ^G*m9Uic@;tLz z$G#CMr?UE6HHK}?X*U9NY{ZX2TSbiY6C7zAixb~cl91G2DhSZu@Z3>@=^c z@$$oMvq1^_Vap6dc;>;MQ+68Om2az>vMqB0GJpsxaF5EI%|g}=rL%rw+LdWHW_JU8 z_SKqd^O2X~dfTg-ZY;TxCinU~@6F~Rh9gzhwm&Z)TylDCSmK>h$}{G}6x?Jw4p@;= z02Q4~$HO(9)V@bw^j=Lo-eAjfMFSgm4A^!Wq$b9^dD+qHIw zo4XuJR#xoL$yTVgnX<}WJhYwt$_&_XjPz!Ot%SF_8^?onQe%CMJAZ&eZZcNS zC=kfDkF{`=Ns-tT7nSLM2b9SwloH*U=F38)@Gf;7Jp*R`fb+~8&^K1wu1poP=s+FX z+C8xvWK6xY3?!A@X+cPf~x)Kx%>qNv))m!4NY>@RzB z-nHet-NT7w1ytR`M)J&X;D8p+&-qRV+}HSr<2mE`X4MZ9yavzk*#K{IkK&Mq6mz4& zZng|i5-?=vhKp!=MAYL$yo$#PbFf1dL5}eceHsno&-0lh@WO|idWi+f_;ZQdAY)&* zb#vh{T=Xtj6t?S^M-UU$tG%Y$Z8H*mCmTLW0PnNOdhAaMv?50gn7g6pN(Ub_^M#(F zoNw+OQ7NKTT?K&(LP!rrDb5pOv3(+P3z!DEb~atyahccRT;IL2C&KbiSCpj4&ft1~ z1wG%{K344RNJbOQ%Io!y#wnAaI!SuaOF`z~t_IYQ3wMQk(;2u;R(z~BuCGjDb@wk7 z(RY;fdbOm1%3>5T(AY}%OBHD~&^dYw)iGFawEN)h)e_g)D;i!Exvk_cz_+XuIP}1{ z$@U$@Q5kA8gPmJSwD>Hyvza(dx1qR_N+kRUPZKt%9Xt3tf)Dnv8bjy362kKwwx_ww z=aYy;H}+m~Dxf-MTS)Rq=apN$h7{56fJ-2wKonCD6VUa&-qY;66*M#$eSCDh2z;+h zUmpmT-+7tv#I=+I(s&&_m2&d27q9!UjU$i0KsCuUjT(2 z@QZ!@4lr2&-Qp7Wza!*78yxX4XDq9hZdAoA6Q&omP@DVXOjAYnlrkSlzYM!e_<9F8EV+MDU}f=2qTJgd74B;BtR7 zf3wl9^O|JvG#=hK`>>@)kdkFv;q@7N=2jzY^f94sch>i;z{x3o|Misk`Z%H~W6R>M zz_7yGFN9%&6u)I|=t}4%%|?@?s|oJyALLdp4+_ti){cZ_+?EQ8k1ZtEkwTs3Q>ZJ% zB_!O!B&|Cz2H@v>$sMMbF4Lyk;>FHm9-EF81p?&75O{W8k3ODl;HhOhb;!7yc&bU*j17>id z@H(=y3Jam~>;urt&5zr0GoFqt><vl%u8vYv#{<9C>~`x*L3zP>XWurzv-^eX6v|%0j6m%%Q8V z<YM6N%<`{o8B?7%5qj@LDUreddbic&tVN<~lcxW8vaf{QJ%8ZfKud@H4qCXe<7dGp$B%Rs(odr^BGP z@@9Hf`GR+9>^hiL>EYNu1tALp6NvJvHt`e;HE#Qc$AtCN$;ah)oCiN8p0-Mb z9V6>U^7jMcbsSzOb4&FjE_e37(vFHp1tTQGy9MX6u-*f)%GLv7IVzx^>F%>#84!*6 zXkpsD-Pn1ziwmcPbk$8LfD>Xyr@BthK`fG_70PcHvTr3OQd!*N-=}0niLoLSwMF5X zK>-E**Uc)0-CUt)cCldJUTgQW*5TN|)ax-?lmKfFelG_HPVwQ%1A;EJ!INnz&_SpT z(m3!fal*-d*5%~eO zgH84sMi@TF#N=dD4mmn~9w6rf%yl-las5QFfVJCvKEpLKtgo-0=CUyoX(byoI^in@ z0-=EH<<43BX~cT--VFkCZlSmrbOxz?;yx=B@1okaseL^bBGVl4%szC z=oq*p^AyctA?^U;RLnGQ1!8G;o}AOY@Sh8L=3=g9t{*>+k(FqE9E;7%9Gb) zb_o3vmkl{KS1tnjHQ+J17%~WBMynraD zgdJ_A9-XIY3o-J{uJgMAt19ZGY8>eI$F!BGtk~zq@%9@iZ)JRXbBglgRM~IlTmBP^`R;Eg2~aF}mvW@BO-i?SuT{(p4z47FzFsJ0fzs{? zX{;|%d!V0`wC= zvza2&?p(czMD3KDZdm`yg;PJlZ09R7eeE5^yOzS-bt0oq@vaP8paHXm`G|QSckDI1 zc`2C!9chAW!h#dXFk0E|B8aPX@sQ)fEa<5(O)V}uQzq!t+*%M#LUsrZ)Zg9(UkTi% z`YgD2j{Q1%8?^sR!SdFAec9UN(dZbctWbbqq6cS-8=#NBbvmnup?Pkh(HeDF?&*(Bha9^Hdcw~L0%OXKxs)}5{wQ{i6s}N@iZG%X$h6B=RYoUYg$J7W z(NlVw7vi<^yTQkmHx!XpEnQ-c5=S8U5&>LC>1BhGw#E?(3TVKd0HF|V z+)G1Iy}4%)@8?4`lRQ$e-Sl-RTJD+Qy3PcuEBUVG$8Y_0ikV~gom6faYV+=GG@*Ro za^3JCVPcFWxHf$bH@Gq5q5*IWh!%RrTh&ipo1NbZN044~!Q3Re)msVILbv9AitX;>F5`1U9{;NjR9kK0J+5u~#aRXsX_mi2B^U@+IPn~`cQ&2^VELc_h-eAw0chP_90w|2RZ%Q!ZmLd6&Qf5PD&Z>$*gDJa7&b1T}<9 z2D$rS&%BAsZCy##V#j>Q>nn7<9VXYwwGgZpbdazjotTKi+***5ase{nK^~9)Jy()6 zBoa1JxS-i^^YhMbrc#-ntO(z*%Tr~)*>x9!T8p;I)IwwUZK2^2H+{%br-IV;sTQJe z_T_%4(E2#ht)-_5FhMMS@is08NlyYH`#S1)a6;xg+%17IJDxFnqz)>*oN@=qB^Iuy zw-Eb&usYRPGkZK6bOhw+$Vw%qgj@t|-Fg$X#!Dl*-JbCx<>S@Pn~a#SkCpnybYa9Q zL`BiA5sERZnWQBzf64Uu$-Y5SrhNSK?CuuQ@vK)BcdbX&B`bL;wtpW8a_@W{X*9d! z!E{iq_Lzh^EU(A@aoC7FuL_QQd97B@^GIgjwncL|QK7TgMUup;WDaSIp0te39{Z&q zbK(8Y3`yaZib@%`qQ_;xF#tRz=@jzt?Yrj77SebvWb_~}5Q+@cjcZBDQe6-Q&VQ@hDz?2LsIMX0_9Wm%3C+CT}5 zx;cBljs4tCZov3h?6H#f4Y9>Y)NUj9+wRum)%Xd(fCg&4g%%DB1_UtUuBQ|`7qFj^ zLRWZM8HHS3yVTZT+mta^4KqdMj%qUKW&0`g7tOE1XKQ@~hCdoSYX)!%Z#M$smGdFK zZAF*m-!0F5!{sar(q7F?)ggtzjDF``gb>Ujt&5_kR{{3QU8`G?<}T< zC*_!X^-oH@iXjq6UZWpHPX{vH0fYB8@GKm}{+ttm8(!ZN<5bvKXet9bQH^BZc^5wc zGc8i#jA}QIecRvZKYZ9z7ip*pf2D$op#$9j%8tPmT^fJlpDz9!>$ajKpI3DpYbZVx zALs8|B0QQ5t3pscEk*pCbMuP0v&wO?&!3o>UIXx5L49gO2;YU8*y1|BoQ*lrOHzn= z%Z1K`s!&dN`@7LG`;LB1H7N%bw~WdwjjPO^Sn#q&OjWm#_5MWYy@Q+C_ZQHkm=R-O zdTba|W2?&~Y?!hq13}G$pSG-P9fI7$(+CQwW`DDK#0ra!e*Yq=LUEEe!9GbsdyL_{n=DXw`e|10+`6{8o%_k2slN>#DrsFlUYZIxFl1;n8~T`Lq7unQ;HkhIl0E;V1PPTIv}0lw z(1d4cN_{BqY@U5qtD<{6ppRUZP?Z&fV(m1pTAulbtzDjpzRX^;6JE!MFR}r?zdJVdZPlu-@1Jw>)5#60|-b9$oPfDmgGXbL!Zw zHpchrTh%*}WE#5R0juyI+&_mt#c!nF>3CT&@|W#)TyqjlI5B|6sFI2~YNVzV=e8BY zg%sC|bclv9vUI=L51ouxzAmE(mVN`9;T6(mw6a5JTnS(}NxAJ>FV8xux0(v<;cQaU!hP_?Bn#;Kk#h-`xWv=nu zeka`z>j4XGx+v6=H4Qw?Z5&2wDCAI@q)cf-Jw_PwUnMPLba%RaKz}{|=T +#include +#include +#include +#include +#include "pldefs.h" +typedef struct Button Button; +struct Button{ + int btype; /* button type */ + Icon *icon; /* what to write on the button */ + int check; /* for check/radio buttons */ + void (*hit)(Panel *, int, int); /* call back user code on check/radio hit */ + void (*menuhit)(int, int); /* call back user code on menu item hit */ + void (*pl_buttonhit)(Panel *, int); /* call back user code on button hit */ + int index; /* arg to menuhit */ + int buttons; +}; +/* + * Button types + */ +#define BUTTON 1 +#define CHECK 2 +#define RADIO 3 +void pl_drawbutton(Panel *p){ + Rectangle r; + Button *bp; + bp=p->data; + r=pl_boxf(p->b, p->r, p->flags, p->state); + switch(bp->btype){ + case CHECK: + r=pl_check(p->b, r, bp->check); + break; + case RADIO: + r=pl_radio(p->b, r, bp->check); + break; + } + pl_drawicon(p->b, r, PLACECEN, p->flags, bp->icon); +} +int pl_hitbutton(Panel *p, Mouse *m){ + int oldstate, hitme; + Panel *sib; + Button *bp; + bp=p->data; + oldstate=p->state; + if(m->buttons&OUT){ + hitme=0; + p->state=UP; + } + else if(m->buttons&7){ + hitme=0; + p->state=DOWN; + bp->buttons=m->buttons; + } + else{ /* mouse inside, but no buttons down */ + hitme=p->state==DOWN; + p->state=UP; + } + if(hitme) switch(bp->btype){ + case CHECK: + if(hitme) bp->check=!bp->check; + break; + case RADIO: + if(bp->check) bp->check=0; + else{ + if(p->parent){ + for(sib=p->parent->child;sib;sib=sib->next){ + if(sib->hit==pl_hitbutton + && ((Button *)sib->data)->btype==RADIO + && ((Button *)sib->data)->check){ + ((Button *)sib->data)->check=0; + pldraw(sib, p->b); + } + } + } + bp->check=1; + } + break; + } + if(hitme || oldstate!=p->state) pldraw(p, p->b); + if(hitme && bp->hit){ + bp->hit(p, bp->buttons, bp->check); + p->state=UP; + } + return 0; +} +void pl_typebutton(Panel *g, Rune c){ + USED(g, c); +} +Point pl_getsizebutton(Panel *p, Point children){ + Point s; + int ckw; + Button *bp; + USED(children); /* shouldn't have any children */ + bp=p->data; + s=pl_iconsize(p->flags, bp->icon); + if(bp->btype!=BUTTON){ + ckw=pl_ckwid(); + if(s.ystate); +} +void pl_childspacebutton(Panel *g, Point *ul, Point *size){ + USED(g, ul, size); +} +void pl_initbtype(Panel *v, int flags, Icon *icon, void (*hit)(Panel *, int, int), int btype){ + Button *bp; + bp=v->data; + v->flags=flags|LEAF; + v->state=UP; + v->draw=pl_drawbutton; + v->hit=pl_hitbutton; + v->type=pl_typebutton; + v->getsize=pl_getsizebutton; + v->childspace=pl_childspacebutton; + bp->btype=btype; + bp->check=0; + bp->hit=hit; + bp->icon=icon; + switch(btype){ + case BUTTON: v->kind="button"; break; + case CHECK: v->kind="checkbutton"; break; + case RADIO: v->kind="radiobutton"; break; + } +} +void pl_buttonhit(Panel *p, int buttons, int check){ + USED(check); + if(((Button *)p->data)->pl_buttonhit) ((Button *)p->data)->pl_buttonhit(p, buttons); +} +void plinitbutton(Panel *p, int flags, Icon *icon, void (*hit)(Panel *, int)){ + ((Button *)p->data)->pl_buttonhit=hit; + pl_initbtype(p, flags, icon, pl_buttonhit, BUTTON); +} +void plinitcheckbutton(Panel *p, int flags, Icon *icon, void (*hit)(Panel *, int, int)){ + pl_initbtype(p, flags, icon, hit, CHECK); +} +void plinitradiobutton(Panel *p, int flags, Icon *icon, void (*hit)(Panel *, int, int)){ + pl_initbtype(p, flags, icon, hit, RADIO); +} +Panel *plbutton(Panel *parent, int flags, Icon *icon, void (*hit)(Panel *, int)){ + Panel *p; + p=pl_newpanel(parent, sizeof(Button)); + plinitbutton(p, flags, icon, hit); + return p; +} +Panel *plcheckbutton(Panel *parent, int flags, Icon *icon, void (*hit)(Panel *, int, int)){ + Panel *p; + p=pl_newpanel(parent, sizeof(Button)); + plinitcheckbutton(p, flags, icon, hit); + return p; +} +Panel *plradiobutton(Panel *parent, int flags, Icon *icon, void (*hit)(Panel *, int, int)){ + Panel *p; + p=pl_newpanel(parent, sizeof(Button)); + plinitradiobutton(p, flags, icon, hit); + return p; +} +void pl_hitmenu(Panel *p, int buttons){ + void (*hit)(int, int); + hit=((Button *)p->data)->menuhit; + if(hit) hit(buttons, ((Button *)p->data)->index); +} +void plinitmenu(Panel *v, int flags, Icon **item, int cflags, void (*hit)(int, int)){ + Panel *b; + int i; + v->flags=flags; + v->kind="menu"; + if(v->child){ + plfree(v->child); + v->child=0; + } + for(i=0;item[i];i++){ + b=plbutton(v, cflags, item[i], pl_hitmenu); + ((Button *)b->data)->menuhit=hit; + ((Button *)b->data)->index=i; + } +} +Panel *plmenu(Panel *parent, int flags, Icon **item, int cflags, void (*hit)(int, int)){ + Panel *v; + v=plgroup(parent, flags); + plinitmenu(v, flags, item, cflags, hit); + return v; +} +void plsetbutton(Panel *p, int val){ + ((Button *)p->data)->check=val; +} diff --git a/libpanel/canvas.c b/libpanel/canvas.c new file mode 100644 index 000000000..c0ebbc793 --- /dev/null +++ b/libpanel/canvas.c @@ -0,0 +1,51 @@ +#include +#include +#include +#include +#include +#include "pldefs.h" +typedef struct Canvas Canvas; +struct Canvas{ + void (*draw)(Panel *); + void (*hit)(Panel *, Mouse *); +}; +void pl_drawcanvas(Panel *p){ + Canvas *c; + c=p->data; + if(c->draw) c->draw(p); +} +int pl_hitcanvas(Panel *p, Mouse *m){ + Canvas *c; + c=p->data; + if(c->hit) c->hit(p, m); + return 0; +} +void pl_typecanvas(Panel *p, Rune c){ + USED(p, c); +} +Point pl_getsizecanvas(Panel *p, Point children){ + USED(p, children); + return Pt(0,0); +} +void pl_childspacecanvas(Panel *p, Point *ul, Point *size){ + USED(p, ul, size); +} +void plinitcanvas(Panel *v, int flags, void (*draw)(Panel *), void (*hit)(Panel *, Mouse *)){ + Canvas *c; + v->flags=flags|LEAF; + v->draw=pl_drawcanvas; + v->hit=pl_hitcanvas; + v->type=pl_typecanvas; + v->getsize=pl_getsizecanvas; + v->childspace=pl_childspacecanvas; + v->kind="canvas"; + c=v->data; + c->draw=draw; + c->hit=hit; +} +Panel *plcanvas(Panel *parent, int flags, void (*draw)(Panel *), void (*hit)(Panel *, Mouse *)){ + Panel *p; + p=pl_newpanel(parent, sizeof(Canvas)); + plinitcanvas(p, flags, draw, hit); + return p; +} diff --git a/libpanel/draw.c b/libpanel/draw.c new file mode 100644 index 000000000..edb9d3443 --- /dev/null +++ b/libpanel/draw.c @@ -0,0 +1,322 @@ +#include +#include +#include +#include +#include +#include "pldefs.h" +#define PWID 1 /* width of label border */ +#define BWID 1 /* width of button relief */ +#define FWID 2 /* width of frame relief */ +#define SPACE 1 /* space inside relief of button or frame */ +#define CKSIZE 3 /* size of check mark */ +#define CKSPACE 2 /* space around check mark */ +#define CKWID 1 /* width of frame around check mark */ +#define CKINSET 1 /* space around check mark frame */ +#define CKBORDER 2 /* space around X inside frame */ +static int plldepth; +static Image *pl_white, *pl_light, *pl_dark, *pl_black, *pl_hilit; +int pl_drawinit(int ldepth){ + plldepth=ldepth; + pl_white=allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xFFFFFFFF); + //pl_light=allocimagemix(display, DPalebluegreen, DWhite); + pl_light=display->white; + //pl_dark =allocimage(display, Rect(0,0,1,1), screen->chan, 1, DPurpleblue); + pl_dark = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, 0X999999FF); + pl_black=allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x000000FF); + pl_hilit=allocimage(display, Rect(0,0,1,1), CHAN1(CAlpha,8), 1, 0x80); + if(pl_white==0 || pl_light==0 || pl_black==0 || pl_dark==0) return 0; + return 1; +} +void pl_relief(Image *b, Image *ul, Image *lr, Rectangle r, int wid){ + int x, y; + draw(b, Rect(r.min.x, r.max.y-wid, r.max.x, r.max.y), lr, 0, ZP); /* bottom */ + draw(b, Rect(r.max.x-wid, r.min.y, r.max.x, r.max.y), lr, 0, ZP); /* right */ + draw(b, Rect(r.min.x, r.min.y, r.min.x+wid, r.max.y), ul, 0, ZP); /* left */ + draw(b, Rect(r.min.x, r.min.y, r.max.x, r.min.y+wid), ul, 0, ZP); /* top */ + for(x=0;x!=wid;x++) for(y=wid-1-x;y!=wid;y++){ + draw(b, rectaddpt(Rect(0,0,1,1), Pt(x+r.max.x-wid, y+r.min.y)), lr, 0, ZP); + draw(b, rectaddpt(Rect(0,0,1,1), Pt(x+r.min.x, y+r.max.y-wid)), lr, 0, ZP); + } +} +Rectangle pl_boxoutline(Image *b, Rectangle r, int style, int fill){ + if(plldepth==0) switch(style){ + case UP: + pl_relief(b, pl_black, pl_black, r, BWID); + r=insetrect(r, BWID); + if(fill) draw(b, r, pl_white, 0, ZP); + else border(b, r, SPACE, pl_white, ZP); + break; + case DOWN: + case DOWN1: + case DOWN2: + case DOWN3: + pl_relief(b, pl_black, pl_black, r, BWID); + r=insetrect(r, BWID); + if(fill) draw(b, r, pl_black, 0, ZP); + border(b, r, SPACE, pl_black, ZP); + break; + case PASSIVE: + if(fill) draw(b, r, pl_white, 0, ZP); + r=insetrect(r, PWID); + if(!fill) border(b, r, SPACE, pl_white, ZP); + break; + case FRAME: + pl_relief(b, pl_white, pl_black, r, FWID); + r=insetrect(r, FWID); + pl_relief(b, pl_black, pl_white, r, FWID); + r=insetrect(r, FWID); + if(fill) draw(b, r, pl_white, 0, ZP); + else border(b, r, SPACE, pl_white, ZP); + break; + } + else switch(style){ + case UP: + /* + pl_relief(b, pl_white, pl_black, r, BWID); + r=insetrect(r, BWID); + if(fill) draw(b, r, pl_light, 0, ZP); + else border(b, r, SPACE, pl_white, ZP); + */ + draw(b, r, pl_light, 0, ZP); + border(b, r, 1, pl_black, ZP); + break; + case DOWN: + case DOWN1: + case DOWN2: + case DOWN3: + pl_relief(b, pl_black, pl_white, r, BWID); + r=insetrect(r, BWID); + if(fill) draw(b, r, pl_dark, 0, ZP); + else border(b, r, SPACE, pl_black, ZP); + break; + case PASSIVE: + if(fill) draw(b, r, pl_light, 0, ZP); + r=insetrect(r, PWID); + if(!fill) border(b, r, SPACE, pl_white, ZP); + break; + case FRAME: + border(b, r, 1, pl_black, ZP); + /* + pl_relief(b, pl_white, pl_black, r, FWID); + r=insetrect(r, FWID); + pl_relief(b, pl_black, pl_white, r, FWID); + r=insetrect(r, FWID); + if(fill) draw(b, r, pl_light, 0, ZP); + else border(b, r, SPACE, pl_white, ZP); + */ + break; + } + return insetrect(r, SPACE); +} +Rectangle pl_outline(Image *b, Rectangle r, int style){ + return pl_boxoutline(b, r, style, 0); +} +Rectangle pl_box(Image *b, Rectangle r, int style){ + return pl_boxoutline(b, r, style, 1); +} +Rectangle pl_boxoutlinef(Image *b, Rectangle r, int flags, int style, int fill){ + switch(style){ + case UP: + draw(b, r, pl_light, 0, ZP); + if(!(flags&NOBORDER)) + border(b, r, 1, pl_black, ZP); + break; + case DOWN: + case DOWN1: + case DOWN2: + case DOWN3: + if(!(flags&NOBORDER)) + pl_relief(b, pl_black, pl_white, r, BWID); + r=insetrect(r, BWID); + if(fill) draw(b, r, pl_dark, 0, ZP); + else if(!(flags&NOBORDER)) border(b, r, SPACE, pl_black, ZP); + break; + case PASSIVE: + if(fill) draw(b, r, pl_light, 0, ZP); + r=insetrect(r, PWID); + if(!fill) border(b, r, SPACE, pl_white, ZP); + break; + case FRAME: + border(b, r, 1, pl_black, ZP); + break; + } + return insetrect(r, SPACE); +} +Rectangle pl_outlinef(Image *b, Rectangle r, int flags, int style){ + return pl_boxoutlinef(b, r, flags, style, 0); +} +Rectangle pl_boxf(Image *b, Rectangle r, int flags, int style){ + return pl_boxoutlinef(b, r, flags, style, 1); +} +Point pl_boxsize(Point interior, int state){ + switch(state){ + case UP: + case DOWN: + case DOWN1: + case DOWN2: + case DOWN3: + return addpt(interior, Pt(2*(BWID+SPACE), 2*(BWID+SPACE))); + case PASSIVE: + return addpt(interior, Pt(2*(PWID+SPACE), 2*(PWID+SPACE))); + case FRAME: + return addpt(interior, Pt(4*FWID+2*SPACE, 4*FWID+2*SPACE)); + } + return Pt(0, 0); +} +void pl_interior(int state, Point *ul, Point *size){ + switch(state){ + case UP: + case DOWN: + case DOWN1: + case DOWN2: + case DOWN3: + *ul=addpt(*ul, Pt(BWID+SPACE, BWID+SPACE)); + *size=subpt(*size, Pt(2*(BWID+SPACE), 2*(BWID+SPACE))); + break; + case PASSIVE: + *ul=addpt(*ul, Pt(PWID+SPACE, PWID+SPACE)); + *size=subpt(*size, Pt(2*(PWID+SPACE), 2*(PWID+SPACE))); + break; + case FRAME: + *ul=addpt(*ul, Pt(2*FWID+SPACE, 2*FWID+SPACE)); + *size=subpt(*size, Pt(4*FWID+2*SPACE, 4*FWID+2*SPACE)); + } +} + +void pl_drawicon(Image *b, Rectangle r, int stick, int flags, Icon *s){ + Rectangle save; + Point ul, offs; + ul=r.min; + offs=subpt(subpt(r.max, r.min), pl_iconsize(flags, s)); + switch(stick){ + case PLACENW: break; + case PLACEN: ul.x+=offs.x/2; break; + case PLACENE: ul.x+=offs.x; break; + case PLACEW: ul.y+=offs.y/2; break; + case PLACECEN: ul.x+=offs.x/2; ul.y+=offs.y/2; break; + case PLACEE: ul.x+=offs.x; break; + case PLACESW: ul.y+=offs.y; break; + case PLACES: ul.x+=offs.x/2; ul.y+=offs.y; break; + case PLACESE: ul.x+=offs.x; ul.y+=offs.y; break; + } + save=b->clipr; + if(!rectclip(&r, save)) + return; + replclipr(b, b->repl, r); + if(flags&BITMAP) draw(b, Rpt(ul, addpt(ul, pl_iconsize(flags, s))), s, 0, ZP); + else string(b, ul, pl_black, ZP, font, s); + replclipr(b, b->repl, save); +} +/* + * Place a check mark at the left end of r. Return the unused space. + * Caller must guarantee that r.max.x-r.min.x>=r.max.y-r.min.y! + */ +Rectangle pl_radio(Image *b, Rectangle r, int val){ + Rectangle remainder; + remainder=r; + r.max.x=r.min.x+r.max.y-r.min.y; + remainder.min.x=r.max.x; + r=insetrect(r, CKINSET); + if(plldepth==0) + pl_relief(b, pl_black, pl_black, r, CKWID); + else + pl_relief(b, pl_black, pl_white, r, CKWID); + r=insetrect(r, CKWID); + if(plldepth==0) + draw(b, r, pl_white, 0, ZP); + else + draw(b, r, pl_light, 0, ZP); + if(val) draw(b, insetrect(r, CKSPACE), pl_black, 0, ZP); + return remainder; +} +Rectangle pl_check(Image *b, Rectangle r, int val){ + Rectangle remainder; + remainder=r; + r.max.x=r.min.x+r.max.y-r.min.y; + remainder.min.x=r.max.x; + r=insetrect(r, CKINSET); + if(plldepth==0) + pl_relief(b, pl_black, pl_black, r, CKWID); + else + pl_relief(b, pl_black, pl_white, r, CKWID); + r=insetrect(r, CKWID); + if(plldepth==0) + draw(b, r, pl_white, 0, ZP); + else + draw(b, r, pl_light, 0, ZP); + r=insetrect(r, CKBORDER); + if(val){ + line(b, Pt(r.min.x, r.min.y+1), Pt(r.max.x-1, r.max.y ), Endsquare, Endsquare, 0, pl_black, ZP); + line(b, Pt(r.min.x, r.min.y ), Pt(r.max.x, r.max.y ), Endsquare, Endsquare, 0, pl_black, ZP); + line(b, Pt(r.min.x+1, r.min.y ), Pt(r.max.x, r.max.y-1), Endsquare, Endsquare, 0, pl_black, ZP); + line(b, Pt(r.min.x , r.max.y-2), Pt(r.max.x-1, r.min.y-1), Endsquare, Endsquare, 0, pl_black, ZP); + line(b, Pt(r.min.x, r.max.y-1), Pt(r.max.x, r.min.y-1), Endsquare, Endsquare, 0, pl_black, ZP); + line(b, Pt(r.min.x+1, r.max.y-1), Pt(r.max.x, r.min.y ), Endsquare, Endsquare, 0, pl_black, ZP); + } + return remainder; +} +int pl_ckwid(void){ + return 2*(CKINSET+CKSPACE+CKWID)+CKSIZE; +} +void pl_sliderupd(Image *b, Rectangle r1, int dir, int lo, int hi){ + Rectangle r2, r3; + r2=r1; + r3=r1; + if(lo<0) lo=0; + if(hi<=lo) hi=lo+1; + switch(dir){ + case HORIZ: + r1.max.x=r1.min.x+lo; + r2.min.x=r1.max.x; + r2.max.x=r1.min.x+hi; + if(r2.max.x>r3.max.x) r2.max.x=r3.max.x; + r3.min.x=r2.max.x; + break; + case VERT: + r1.max.y=r1.min.y+lo; + r2.min.y=r1.max.y; + r2.max.y=r1.min.y+hi; + if(r2.max.y>r3.max.y) r2.max.y=r3.max.y; + r3.min.y=r2.max.y; + break; + } + draw(b, r1, pl_light, 0, ZP); + draw(b, r2, pl_dark, 0, ZP); + draw(b, r3, pl_light, 0, ZP); +} +void pl_draw1(Panel *p, Image *b); +void pl_drawall(Panel *p, Image *b){ + if(p->flags&INVIS || p->flags&IGNORE) return; + p->b=b; + p->draw(p); + for(p=p->child;p;p=p->next) pl_draw1(p, b); +} +void pl_draw1(Panel *p, Image *b){ + if(b!=0) + pl_drawall(p, b); +} +void pldraw(Panel *p, Image *b){ + pl_draw1(p, b); +} +void pl_invis(Panel *p, int v){ + for(;p;p=p->next){ + if(v) p->flags|=INVIS; else p->flags&=~INVIS; + pl_invis(p->child, v); + } +} +Point pl_iconsize(int flags, Icon *p){ + if(flags&BITMAP) return subpt(((Image *)p)->r.max, ((Image *)p)->r.min); + return stringsize(font, (char *)p); +} +void pl_highlight(Image *b, Rectangle r){ + draw(b, r, pl_dark, pl_hilit, ZP); +} +void pl_clr(Image *b, Rectangle r){ + draw(b, r, display->white, 0, ZP); +} +void pl_fill(Image *b, Rectangle r){ + draw(b, r, plldepth==0? pl_white : pl_light, 0, ZP); +} +void pl_cpy(Image *b, Point dst, Rectangle src){ + draw(b, Rpt(dst, addpt(dst, subpt(src.max, src.min))), b, 0, src.min); +} diff --git a/libpanel/edit.c b/libpanel/edit.c new file mode 100644 index 000000000..987843135 --- /dev/null +++ b/libpanel/edit.c @@ -0,0 +1,297 @@ +/* + * Interface includes: + * void plescroll(Panel *p, int top); + * move the given character position onto the top line + * void plegetsel(Panel *p, int *sel0, int *sel1); + * read the selection back + * int plelen(Panel *p); + * read the length of the text back + * Rune *pleget(Panel *p); + * get a pointer to the text + * void plesel(Panel *p, int sel0, int sel1); + * set the selection -- adjusts hiliting + * void plepaste(Panel *p, Rune *text, int ntext); + * replace the selection with the given text + */ +#include +#include +#include +#include +#include +#include "pldefs.h" +#include + +typedef struct Edit Edit; +struct Edit{ + Point minsize; + void (*hit)(Panel *); + int sel0, sel1; + Textwin *t; + Rune *text; + int ntext; +}; +void pl_drawedit(Panel *p){ + Edit *ep; + Panel *sb; + ep=p->data; + if(ep->t==0){ + ep->t=twnew(p->b, font, ep->text, ep->ntext); + if(ep->t==0){ + fprint(2, "pl_drawedit: can't allocate\n"); + exits("no mem"); + } + } + ep->t->b=p->b; + twreshape(ep->t, p->r); + twhilite(ep->t, ep->sel0, ep->sel1, 1); + sb=p->yscroller; + if(sb && sb->setscrollbar) + sb->setscrollbar(sb, ep->t->top, ep->t->bot, ep->t->etext-ep->t->text); +} + +char *pl_snarfedit(Panel *p){ + int s0, s1; + Rune *t; + t=pleget(p); + plegetsel(p, &s0, &s1); + if(t==0 || s0>=s1) + return nil; + return smprint("%.*S", s1-s0, t+s0); +} +void pl_pasteedit(Panel *p, char *s){ + Rune *t; + if(t=runesmprint("%s", s)){ + plepaste(p, t, runestrlen(t)); + free(t); + } +} + +/* + * Should do double-clicks: + * If ep->sel0==ep->sel1 on entry and the + * call to twselect returns the same selection, then + * expand selections (| marks possible selection points, ... is expanded selection) + * <|...|> <> must nest + * (|...|) () must nest + * [|...|] [] must nest + * {|...|} {} must nest + * '|...|' no ' in ... + * "|...|" no " in ... + * \n|...|\n either newline may be the corresponding end of text + * include the trailing newline in the selection + * ...|I... I and ... are characters satisfying pl_idchar(I) + * ...I| + */ +int pl_hitedit(Panel *p, Mouse *m){ + Edit *ep; + ep=p->data; + if(ep->t && m->buttons&1){ + plgrabkb(p); + ep->t->b=p->b; + twhilite(ep->t, ep->sel0, ep->sel1, 0); + twselect(ep->t, m); + ep->sel0=ep->t->sel0; + ep->sel1=ep->t->sel1; + if((m->buttons&7)==3){ + plsnarf(p); + plepaste(p, 0, 0); /* cut */ + } + else if((m->buttons&7)==5) + plpaste(p); + else if(ep->hit) + (*ep->hit)(p); + } + return 0; +} +void pl_scrolledit(Panel *p, int dir, int buttons, int num, int den){ + Edit *ep; + Textwin *t; + Panel *sb; + int index, nline; + if(dir!=VERT) return; + ep=p->data; + t=ep->t; + if(t==0) return; + t->b=p->b; + switch(buttons){ + default: + return; + case 1: /* top line moves to mouse position */ + nline=(t->r.max.y-t->r.min.y)/t->hgt*num/den; + index=t->top; + while(index!=0 && nline!=0) + if(t->text[--index]=='\n') --nline; + break; + case 2: /* absolute */ + index=(t->etext-t->text)*num/den; + break; + case 4: /* mouse points at new top line */ + index=twpt2rune(t, + Pt(t->r.min.x, t->r.min.y+(t->r.max.y-t->r.min.y)*num/den)); + break; + } + while(index!=0 && t->text[index-1]!='\n') --index; + if(index!=t->top){ + twhilite(ep->t, ep->sel0, ep->sel1, 0); + twscroll(t, index); + p->scr.pos.y=t->top; + twhilite(ep->t, ep->sel0, ep->sel1, 1); + sb=p->yscroller; + if(sb && sb->setscrollbar) + sb->setscrollbar(sb, t->top, t->bot, t->etext-t->text); + } +} +void pl_typeedit(Panel *p, Rune c){ + Edit *ep; + Textwin *t; + int bot, scrolled; + Panel *sb; + ep=p->data; + t=ep->t; + if(t==0) return; + t->b=p->b; + twhilite(t, ep->sel0, ep->sel1, 0); + switch(c){ + case Kesc: + plsnarf(p); + plepaste(p, 0, 0); /* cut */ + break; + case Kdel: /* clear */ + ep->sel0=0; + ep->sel1=plelen(p); + plepaste(p, 0, 0); /* cut */ + break; + case Kbs: /* ^H: erase character */ + if(ep->sel0!=0) --ep->sel0; + twreplace(t, ep->sel0, ep->sel1, 0, 0); + break; + case Knack: /* ^U: erase line */ + while(ep->sel0!=0 && t->text[ep->sel0-1]!='\n') --ep->sel0; + twreplace(t, ep->sel0, ep->sel1, 0, 0); + break; + case Ketb: /* ^W: erase word */ + while(ep->sel0!=0 && !pl_idchar(t->text[ep->sel0-1])) --ep->sel0; + while(ep->sel0!=0 && pl_idchar(t->text[ep->sel0-1])) --ep->sel0; + twreplace(t, ep->sel0, ep->sel1, 0, 0); + break; + default: + if((c & 0xFF00) == KF || (c & 0xFF00) == Spec) + break; + twreplace(t, ep->sel0, ep->sel1, &c, 1); + ++ep->sel0; + break; + } + ep->sel1=ep->sel0; + /* + * Scroll up until ep->sel0 is above t->bot. + */ + scrolled=0; + do{ + bot=t->bot; + if(ep->sel0<=bot) break; + twscroll(t, twpt2rune(t, Pt(t->r.min.x, t->r.min.y+font->height))); + scrolled++; + }while(bot!=t->bot); + if(scrolled){ + sb=p->yscroller; + if(sb && sb->setscrollbar) + sb->setscrollbar(sb, t->top, t->bot, t->etext-t->text); + } + twhilite(t, ep->sel0, ep->sel1, 1); +} +Point pl_getsizeedit(Panel *p, Point children){ + USED(children); + return pl_boxsize(((Edit *)p->data)->minsize, p->state); +} +void pl_childspaceedit(Panel *g, Point *ul, Point *size){ + USED(g, ul, size); +} +void pl_freeedit(Panel *p){ + Edit *ep; + ep=p->data; + if(ep->t) twfree(ep->t); + ep->t=0; +} +void plinitedit(Panel *v, int flags, Point minsize, Rune *text, int ntext, void (*hit)(Panel *)){ + Edit *ep; + ep=v->data; + v->flags=flags|LEAF; + v->state=UP; + v->draw=pl_drawedit; + v->hit=pl_hitedit; + v->type=pl_typeedit; + v->getsize=pl_getsizeedit; + v->childspace=pl_childspaceedit; + v->free=pl_freeedit; + v->snarf=pl_snarfedit; + v->paste=pl_pasteedit; + v->kind="edit"; + ep->hit=hit; + ep->minsize=minsize; + ep->text=text; + ep->ntext=ntext; + if(ep->t!=0) twfree(ep->t); + ep->t=0; + ep->sel0=-1; + ep->sel1=-1; + v->scroll=pl_scrolledit; + v->scr.pos=Pt(0,0); + v->scr.size=Pt(ntext,0); +} +Panel *pledit(Panel *parent, int flags, Point minsize, Rune *text, int ntext, void (*hit)(Panel *)){ + Panel *v; + v=pl_newpanel(parent, sizeof(Edit)); + ((Edit *)v->data)->t=0; + plinitedit(v, flags, minsize, text, ntext, hit); + return v; +} +void plescroll(Panel *p, int top){ + Textwin *t; + t=((Edit*)p->data)->t; + if(t) twscroll(t, top); +} +void plegetsel(Panel *p, int *sel0, int *sel1){ + Edit *ep; + ep=p->data; + *sel0=ep->sel0; + *sel1=ep->sel1; +} +int plelen(Panel *p){ + Textwin *t; + t=((Edit*)p->data)->t; + if(t==0) return 0; + return t->etext-t->text; +} +Rune *pleget(Panel *p){ + Textwin *t; + t=((Edit*)p->data)->t; + if(t==0) return 0; + return t->text; +} +void plesel(Panel *p, int sel0, int sel1){ + Edit *ep; + ep=p->data; + if(ep->t==0) return; + ep->t->b=p->b; + twhilite(ep->t, ep->sel0, ep->sel1, 0); + ep->sel0=sel0; + ep->sel1=sel1; + twhilite(ep->t, ep->sel0, ep->sel1, 1); +} +void plepaste(Panel *p, Rune *text, int ntext){ + Edit *ep; + ep=p->data; + if(ep->t==0) return; + ep->t->b=p->b; + twhilite(ep->t, ep->sel0, ep->sel1, 0); + twreplace(ep->t, ep->sel0, ep->sel1, text, ntext); + ep->sel1=ep->sel0+ntext; + twhilite(ep->t, ep->sel0, ep->sel1, 1); + p->scr.size.y=ep->t->etext-ep->t->text; + p->scr.pos.y=ep->t->top; +} +void plemove(Panel *p, Point d){ + Edit *ep; + ep=p->data; + if(ep->t && !eqpt(d, Pt(0,0))) twmove(ep->t, d); +} diff --git a/libpanel/entry.c b/libpanel/entry.c new file mode 100644 index 000000000..19649d31e --- /dev/null +++ b/libpanel/entry.c @@ -0,0 +1,192 @@ +#include +#include +#include +#include +#include +#include "pldefs.h" +#include + +typedef struct Entry Entry; +struct Entry{ + char *entry; + char *entp; + char *eent; + void (*hit)(Panel *, char *); + Point minsize; +}; +#define SLACK 7 /* enough for one extra rune and ◀ and a nul */ +char *pl_snarfentry(Panel *p){ + Entry *ep; + int n; + + if(p->flags&USERFL) /* no snarfing from password entry */ + return nil; + ep=p->data; + n=utfnlen(ep->entry, ep->entp-ep->entry); + if(n<1) return nil; + return smprint("%.*s", n, ep->entry); +} +void pl_pasteentry(Panel *p, char *s){ + Entry *ep; + char *e; + int n, m; + + ep=p->data; + n=ep->entp-ep->entry; + m=strlen(s); + e=pl_erealloc(ep->entry,n+m+SLACK); + ep->entry=e; + e+=n; + strncpy(e, s, m); + e+=m; + *e='\0'; + ep->entp=ep->eent=e; + pldraw(p, p->b); +} +void pl_drawentry(Panel *p){ + Rectangle r; + Entry *ep; + char *s; + + ep=p->data; + r=pl_box(p->b, p->r, p->state); + s=ep->entry; + if(p->flags & USERFL){ + char *p; + s=strdup(s); + for(p=s; *p; p++) + *p='*'; + } + if(stringwidth(font, s)<=r.max.x-r.min.x) + pl_drawicon(p->b, r, PLACEW, 0, s); + else + pl_drawicon(p->b, r, PLACEE, 0, s); + if(s != ep->entry) + free(s); +} +int pl_hitentry(Panel *p, Mouse *m){ + if((m->buttons&7)==1){ + plgrabkb(p); + + p->state=DOWN; + pldraw(p, p->b); + while(m->buttons&1){ + int old; + old=m->buttons; + if(display->bufp > display->buf) + flushimage(display, 1); + *m=emouse(); + if((old&7)==1){ + if((m->buttons&7)==3){ + Entry *ep; + + plsnarf(p); + + /* cut */ + ep=p->data; + ep->entp=ep->entry; + *ep->entp='\0'; + pldraw(p, p->b); + } + if((m->buttons&7)==5) + plpaste(p); + } + } + p->state=UP; + pldraw(p, p->b); + } + return 0; +} +void pl_typeentry(Panel *p, Rune c){ + int n; + Entry *ep; + ep=p->data; + switch(c){ + case '\n': + case '\r': + *ep->entp='\0'; + if(ep->hit) ep->hit(p, ep->entry); + return; + case Kesc: + plsnarf(p); + /* no break */ + case Kdel: /* clear */ + case Knack: /* ^U: erase line */ + ep->entp=ep->entry; + *ep->entp='\0'; + break; + case Kbs: /* ^H: erase character */ + while(ep->entp!=ep->entry && !pl_rune1st(ep->entp[-1])) *--ep->entp='\0'; + if(ep->entp!=ep->entry) *--ep->entp='\0'; + break; + case Ketb: /* ^W: erase word */ + while(ep->entp!=ep->entry && !pl_idchar(ep->entp[-1])) + --ep->entp; + while(ep->entp!=ep->entry && pl_idchar(ep->entp[-1])) + --ep->entp; + *ep->entp='\0'; + break; + default: + if(c < 0x20 || (c & 0xFF00) == KF || (c & 0xFF00) == Spec) + break; + ep->entp+=runetochar(ep->entp, &c); + if(ep->entp>ep->eent){ + n=ep->entp-ep->entry; + ep->entry=pl_erealloc(ep->entry, n+100+SLACK); + ep->entp=ep->entry+n; + ep->eent=ep->entp+100; + } + *ep->entp='\0'; + break; + } + pldraw(p, p->b); +} +Point pl_getsizeentry(Panel *p, Point children){ + USED(children); + return pl_boxsize(((Entry *)p->data)->minsize, p->state); +} +void pl_childspaceentry(Panel *p, Point *ul, Point *size){ + USED(p, ul, size); +} +void pl_freeentry(Panel *p){ + Entry *ep; + ep = p->data; + free(ep->entry); + ep->entry = ep->eent = ep->entp = 0; +} +void plinitentry(Panel *v, int flags, int wid, char *str, void (*hit)(Panel *, char *)){ + int elen; + Entry *ep; + ep=v->data; + v->flags=flags|LEAF; + v->state=UP; + v->draw=pl_drawentry; + v->hit=pl_hitentry; + v->type=pl_typeentry; + v->getsize=pl_getsizeentry; + v->childspace=pl_childspaceentry; + ep->minsize=Pt(wid, font->height); + v->free=pl_freeentry; + v->snarf=pl_snarfentry; + v->paste=pl_pasteentry; + elen=100; + if(str) elen+=strlen(str); + ep->entry=pl_erealloc(ep->entry, elen+SLACK); + ep->eent=ep->entry+elen; + strecpy(ep->entry, ep->eent, str ? str : ""); + ep->entp=ep->entry+strlen(ep->entry); + ep->hit=hit; + v->kind="entry"; +} +Panel *plentry(Panel *parent, int flags, int wid, char *str, void (*hit)(Panel *, char *)){ + Panel *v; + v=pl_newpanel(parent, sizeof(Entry)); + plinitentry(v, flags, wid, str, hit); + return v; +} +char *plentryval(Panel *p){ + Entry *ep; + ep=p->data; + *ep->entp='\0'; + return ep->entry; +} diff --git a/libpanel/event.c b/libpanel/event.c new file mode 100644 index 000000000..4a9355d58 --- /dev/null +++ b/libpanel/event.c @@ -0,0 +1,48 @@ +#include +#include +#include +#include +#include +#include "pldefs.h" + +void plgrabkb(Panel *g){ + plkbfocus=g; +} +void plkeyboard(Rune c){ + if(plkbfocus) + plkbfocus->type(plkbfocus, c); +} + +/* + * Return the most leafward, highest priority panel containing p + */ +Panel *pl_ptinpanel(Point p, Panel *g){ + Panel *v; + for(;g;g=g->next) if(ptinrect(p, g->r)){ + v=pl_ptinpanel(p, g->child); + if(v && v->pri(v, p)>=g->pri(g, p)) return v; + return g; + } + return 0; +} +void plmouse(Panel *g, Mouse *m){ + Panel *hit, *last; + if(g->flags&REMOUSE) + hit=g->lastmouse; + else{ + hit=pl_ptinpanel(m->xy, g); + last=g->lastmouse; + if(last && last!=hit){ + m->buttons|=OUT; + last->hit(last, m); + m->buttons&=~OUT; + } + } + if(hit){ + if(hit->hit(hit, m)) + g->flags|=REMOUSE; + else + g->flags&=~REMOUSE; + g->lastmouse=hit; + } +} diff --git a/libpanel/frame.c b/libpanel/frame.c new file mode 100644 index 000000000..cbfac12d7 --- /dev/null +++ b/libpanel/frame.c @@ -0,0 +1,39 @@ +#include +#include +#include +#include +#include +#include "pldefs.h" +void pl_drawframe(Panel *p){ + pl_box(p->b, p->r, FRAME); +} +int pl_hitframe(Panel *p, Mouse *m){ + USED(p, m); + return 0; +} +void pl_typeframe(Panel *p, Rune c){ + USED(p, c); +} +Point pl_getsizeframe(Panel *p, Point children){ + USED(p); + return pl_boxsize(children, FRAME); +} +void pl_childspaceframe(Panel *p, Point *ul, Point *size){ + USED(p); + pl_interior(FRAME, ul, size); +} +void plinitframe(Panel *v, int flags){ + v->flags=flags; + v->draw=pl_drawframe; + v->hit=pl_hitframe; + v->type=pl_typeframe; + v->getsize=pl_getsizeframe; + v->childspace=pl_childspaceframe; + v->kind="frame"; +} +Panel *plframe(Panel *parent, int flags){ + Panel *p; + p=pl_newpanel(parent, 0); + plinitframe(p, flags); + return p; +} diff --git a/libpanel/group.c b/libpanel/group.c new file mode 100644 index 000000000..7a0fa7e74 --- /dev/null +++ b/libpanel/group.c @@ -0,0 +1,38 @@ +#include +#include +#include +#include +#include +#include "pldefs.h" +void pl_drawgroup(Panel *p){ + USED(p); +} +int pl_hitgroup(Panel *p, Mouse *m){ + USED(p, m); + return 0; +} +void pl_typegroup(Panel *p, Rune c){ + USED(p, c); +} +Point pl_getsizegroup(Panel *p, Point children){ + USED(p); + return children; +} +void pl_childspacegroup(Panel *p, Point *ul, Point *size){ + USED(p, ul, size); +} +void plinitgroup(Panel *v, int flags){ + v->flags=flags; + v->draw=pl_drawgroup; + v->hit=pl_hitgroup; + v->type=pl_typegroup; + v->getsize=pl_getsizegroup; + v->childspace=pl_childspacegroup; + v->kind="group"; +} +Panel *plgroup(Panel *parent, int flags){ + Panel *p; + p=pl_newpanel(parent, 0); + plinitgroup(p, flags); + return p; +} diff --git a/libpanel/init.c b/libpanel/init.c new file mode 100644 index 000000000..452909722 --- /dev/null +++ b/libpanel/init.c @@ -0,0 +1,13 @@ +#include +#include +#include +#include +#include +#include "pldefs.h" +/* + * Just a wrapper for all the initialization routines + */ +int plinit(int ldepth){ + if(!pl_drawinit(ldepth)) return 0; + return 1; +} diff --git a/libpanel/label.c b/libpanel/label.c new file mode 100644 index 000000000..c7f16be54 --- /dev/null +++ b/libpanel/label.c @@ -0,0 +1,50 @@ +#include +#include +#include +#include +#include +#include "pldefs.h" +typedef struct Label Label; +struct Label{ + int placement; + Icon *icon; +}; +void pl_drawlabel(Panel *p){ + Label *l; + l=p->data; + pl_drawicon(p->b, pl_box(p->b, p->r, PASSIVE), l->placement, p->flags, l->icon); +} +int pl_hitlabel(Panel *p, Mouse *m){ + USED(p, m); + return 0; +} +void pl_typelabel(Panel *p, Rune c){ + USED(p, c); +} +Point pl_getsizelabel(Panel *p, Point children){ + USED(children); /* shouldn't have any children */ + return pl_boxsize(pl_iconsize(p->flags, ((Label *)p->data)->icon), PASSIVE); +} +void pl_childspacelabel(Panel *g, Point *ul, Point *size){ + USED(g, ul, size); +} +void plinitlabel(Panel *v, int flags, Icon *icon){ + v->flags=flags|LEAF; + ((Label *)(v->data))->icon=icon; + v->draw=pl_drawlabel; + v->hit=pl_hitlabel; + v->type=pl_typelabel; + v->getsize=pl_getsizelabel; + v->childspace=pl_childspacelabel; + v->kind="label"; +} +Panel *pllabel(Panel *parent, int flags, Icon *icon){ + Panel *p; + p=pl_newpanel(parent, sizeof(Label)); + plinitlabel(p, flags, icon); + plplacelabel(p, PLACECEN); + return p; +} +void plplacelabel(Panel *p, int placement){ + ((Label *)(p->data))->placement=placement; +} diff --git a/libpanel/list.c b/libpanel/list.c new file mode 100644 index 000000000..8f4b0766f --- /dev/null +++ b/libpanel/list.c @@ -0,0 +1,190 @@ +#include +#include +#include +#include +#include +#include "pldefs.h" +typedef struct List List; +struct List{ + void (*hit)(Panel *, int, int); /* call user back on hit */ + char *(*gen)(Panel *, int); /* return text given index or 0 if out of range */ + int lo; /* indices of first, last items displayed */ + int sel; /* index of hilited item */ + int len; /* # of items in list */ + Rectangle listr; + Point minsize; + int buttons; +}; +#define MAXHGT 12 +void pl_listsel(Panel *p, int sel, int on){ + List *lp; + int hi; + Rectangle r; + lp=p->data; + hi=lp->lo+(lp->listr.max.y-lp->listr.min.y)/font->height; + if(lp->lo>=0 && lp->lo<=sel && sellen){ + r=lp->listr; + r.min.y+=(sel-lp->lo)*font->height; + r.max.y=r.min.y+font->height; + if(on) + pl_highlight(p->b, r); + else{ + pl_fill(p->b, r); + pl_drawicon(p->b, r, PLACEW, 0, lp->gen(p, sel)); + } + } +} +void pl_liststrings(Panel *p, int lo, int hi, Rectangle r){ + Panel *sb; + List *lp; + char *s; + int i; + lp=p->data; + for(i=lo;i!=hi && (s=lp->gen(p, i));i++){ + r.max.y=r.min.y+font->height; + pl_drawicon(p->b, r, PLACEW, 0, s); + r.min.y+=font->height; + } + if(lo<=lp->sel && lp->selsel, 1); + sb=p->yscroller; + if(sb && sb->setscrollbar) + sb->setscrollbar(sb, lp->lo, + lp->lo+(lp->listr.max.y-lp->listr.min.y)/font->height, lp->len); +} +void pl_drawlist(Panel *p){ + List *lp; + lp=p->data; + lp->listr=pl_box(p->b, p->r, UP); + pl_liststrings(p, lp->lo, lp->lo+(lp->listr.max.y-lp->listr.min.y)/font->height, + lp->listr); +} +int pl_hitlist(Panel *p, Mouse *m){ + int oldsel, hitme; + Point ul, size; + List *lp; + lp=p->data; + hitme=0; + ul=p->r.min; + size=subpt(p->r.max, p->r.min); + pl_interior(p->state, &ul, &size); + oldsel=lp->sel; + if(m->buttons&OUT){ + p->state=UP; + if(m->buttons&~OUT) lp->sel=-1; + } + else if(p->state==DOWN || m->buttons&7){ + lp->sel=(m->xy.y-ul.y)/font->height+lp->lo; + if(m->buttons&7){ + lp->buttons=m->buttons; + p->state=DOWN; + } + else{ + hitme=1; + p->state=UP; + } + } + if(oldsel!=lp->sel){ + pl_listsel(p, oldsel, 0); + pl_listsel(p, lp->sel, 1); + } + if(hitme && 0<=lp->sel && lp->sellen && lp->hit) + lp->hit(p, lp->buttons, lp->sel); + return 0; +} +void pl_scrolllist(Panel *p, int dir, int buttons, int val, int len){ + Point ul, size; + int nlist, oldlo, hi, nline, y; + List *lp; + Rectangle r; + lp=p->data; + ul=p->r.min; + size=subpt(p->r.max, p->r.min); + pl_interior(p->state, &ul, &size); + nlist=size.y/font->height; + oldlo=lp->lo; + if(dir==VERT) switch(buttons){ + case 1: lp->lo-=nlist*val/len; break; + case 2: lp->lo=lp->len*val/len; break; + case 4: lp->lo+=nlist*val/len; break; + } + if(lp->lo<0) lp->lo=0; + if(lp->lo>=lp->len) lp->lo=lp->len-1; + if(lp->lo==oldlo) return; + p->scr.pos.y=lp->lo; + r=lp->listr; + nline=(r.max.y-r.min.y)/font->height; + hi=lp->lo+nline; + if(hi<=oldlo || lp->lo>=oldlo+nline){ + pl_box(p->b, r, PASSIVE); + pl_liststrings(p, lp->lo, hi, r); + } + else if(lp->lolo)*font->height; + pl_cpy(p->b, Pt(r.min.x, y), + Rect(r.min.x, r.min.y, r.max.x, r.min.y+(hi-oldlo)*font->height)); + r.max.y=y; + pl_box(p->b, r, PASSIVE); + pl_liststrings(p, lp->lo, oldlo, r); + } + else{ + pl_cpy(p->b, r.min, Rect(r.min.x, r.min.y+(lp->lo-oldlo)*font->height, + r.max.x, r.max.y)); + r.min.y=r.min.y+(oldlo+nline-lp->lo)*font->height; + pl_box(p->b, r, PASSIVE); + pl_liststrings(p, oldlo+nline, hi, r); + } +} +void pl_typelist(Panel *g, Rune c){ + USED(g, c); +} +Point pl_getsizelist(Panel *p, Point children){ + USED(children); + return pl_boxsize(((List *)p->data)->minsize, p->state); +} +void pl_childspacelist(Panel *g, Point *ul, Point *size){ + USED(g, ul, size); +} +void plinitlist(Panel *v, int flags, char *(*gen)(Panel *, int), int nlist, void (*hit)(Panel *, int, int)){ + List *lp; + int wid, max; + char *str; + lp=v->data; + v->flags=flags|LEAF; + v->state=UP; + v->draw=pl_drawlist; + v->hit=pl_hitlist; + v->type=pl_typelist; + v->getsize=pl_getsizelist; + v->childspace=pl_childspacelist; + lp->gen=gen; + lp->hit=hit; + max=0; + for(lp->len=0;str=gen(v, lp->len);lp->len++){ + wid=stringwidth(font, str); + if(wid>max) max=wid; + } + if(flags&(FILLX|EXPAND)){ + for(lp->len=0;gen(v, lp->len);lp->len++); + lp->minsize=Pt(0, nlist*font->height); + } + else{ + max=0; + for(lp->len=0;str=gen(v, lp->len);lp->len++){ + wid=stringwidth(font, str); + if(wid>max) max=wid; + } + lp->minsize=Pt(max, nlist*font->height); + } + lp->sel=-1; + lp->lo=0; + v->scroll=pl_scrolllist; + v->scr.pos=Pt(0,0); + v->scr.size=Pt(0,lp->len); + v->kind="list"; +} +Panel *pllist(Panel *parent, int flags, char *(*gen)(Panel *, int), int nlist, void (*hit)(Panel *, int, int)){ + Panel *v; + v=pl_newpanel(parent, sizeof(List)); + plinitlist(v, flags, gen, nlist, hit); + return v; +} diff --git a/libpanel/mem.c b/libpanel/mem.c new file mode 100644 index 000000000..4816df5b3 --- /dev/null +++ b/libpanel/mem.c @@ -0,0 +1,123 @@ +#include +#include +#include +#include +#include +#include "pldefs.h" +void *pl_emalloc(int n){ + void *v; + v=mallocz(n, 1); + if(v==0){ + fprint(2, "Can't malloc!\n"); + exits("no mem"); + } + setmalloctag(v, getcallerpc(&n)); + return v; +} +void *pl_erealloc(void *v, int n) +{ + v=realloc(v, n); + if(v==0){ + fprint(2, "Can't realloc!\n"); + exits("no mem"); + } + setrealloctag(v, getcallerpc(&v)); + return v; +} +void pl_unexpected(Panel *g, char *rou){ + fprint(2, "%s called unexpectedly (%s %#p)\n", rou, g->kind, g); + abort(); +} +void pl_drawerror(Panel *g){ + pl_unexpected(g, "draw"); +} +int pl_hiterror(Panel *g, Mouse *m){ + USED(m); + pl_unexpected(g, "hit"); + return 0; +} +void pl_typeerror(Panel *g, Rune c){ + USED(c); + pl_unexpected(g, "type"); +} +Point pl_getsizeerror(Panel *g, Point childsize){ + pl_unexpected(g, "getsize"); + return childsize; +} +void pl_childspaceerror(Panel *g, Point *ul, Point *size){ + USED(ul, size); + pl_unexpected(g, "childspace"); +} +void pl_scrollerror(Panel *g, int dir, int button, int num, int den){ + USED(dir, button, num, den); + pl_unexpected(g, "scroll"); +} +void pl_setscrollbarerror(Panel *g, int top, int bot, int den){ + USED(top, bot, den); + pl_unexpected(g, "setscrollbar"); +} +int pl_prinormal(Panel *, Point){ + return PRI_NORMAL; +} +Panel *pl_newpanel(Panel *parent, int ndata){ + Panel *v; + if(parent && parent->flags&LEAF){ + fprint(2, "newpanel: can't create child of %s %#p\n", parent->kind, parent); + exits("bad newpanel"); + } + v=pl_emalloc(sizeof(Panel)); + v->r=Rect(0,0,0,0); + v->flags=0; + v->ipad=Pt(0,0); + v->pad=Pt(0,0); + v->size=Pt(0,0); + v->sizereq=Pt(0,0); + v->lastmouse=0; + v->next=0; + v->child=0; + v->echild=0; + v->b=0; + v->pri=pl_prinormal; + v->scrollee=0; + v->xscroller=0; + v->yscroller=0; + v->parent=parent; + v->scr.pos=Pt(0,0); + v->scr.size=Pt(0,0); + if(parent){ + if(parent->child==0) + parent->child=v; + else + parent->echild->next=v; + parent->echild=v; + } + v->draw=pl_drawerror; + v->hit=pl_hiterror; + v->type=pl_typeerror; + v->getsize=pl_getsizeerror; + v->childspace=pl_childspaceerror; + v->scroll=pl_scrollerror; + v->setscrollbar=pl_setscrollbarerror; + v->free=0; + v->snarf=0; + v->paste=0; + if(ndata) + v->data=pl_emalloc(ndata); + else + v->data=0; + return v; +} +void plfree(Panel *p){ + Panel *cp, *ncp; + if(p==0) + return; + if(p==plkbfocus) + plkbfocus=0; + for(cp=p->child;cp;cp=ncp){ + ncp=cp->next; + plfree(cp); + } + if(p->free) p->free(p); + if(p->data) free(p->data); + free(p); +} diff --git a/libpanel/message.c b/libpanel/message.c new file mode 100644 index 000000000..7c3dbed07 --- /dev/null +++ b/libpanel/message.c @@ -0,0 +1,104 @@ +#include +#include +#include +#include +#include +#include "pldefs.h" +typedef struct Message Message; +struct Message{ + char *text; + Point minsize; +}; +void pl_textmsg(Image *b, Rectangle r, Font *f, char *s){ + char *start, *end; /* of line */ + Point where; + int lwid, c, wid; + where=r.min; + wid=r.max.x-r.min.x; + do{ + start=s; + lwid=0; + end=s; + do{ + for(;*s!=' ' && *s!='\0';s=pl_nextrune(s)) lwid+=pl_runewidth(f, s); + if(lwid>wid) break; + end=s; + for(;*s==' ';s=pl_nextrune(s)) lwid+=pl_runewidth(f, s); + }while(*s!='\0'); + if(end==start) /* can't even fit one word on line! */ + end=s; + c=*end; + *end='\0'; + string(b, where, display->black, ZP, f, start); + *end=c; + where.y+=font->height; + s=end; + while(*s==' ') s=pl_nextrune(s); + }while(*s!='\0'); +} +Point pl_foldsize(Font *f, char *s, int wid){ + char *start, *end; /* of line */ + Point size; + int lwid, ewid; + size=Pt(0,0); + do{ + start=s; + lwid=0; + end=s; + ewid=lwid; + do{ + for(;*s!=' ' && *s!='\0';s=pl_nextrune(s)) lwid+=pl_runewidth(f, s); + if(lwid>wid) break; + end=s; + ewid=lwid; + for(;*s==' ';s=pl_nextrune(s)) lwid+=pl_runewidth(f, s); + }while(*s!='\0'); + if(end==start){ /* can't even fit one word on line! */ + ewid=lwid; + end=s; + } + if(ewid>size.x) size.x=ewid; + size.y+=font->height; + s=end; + while(*s==' ') s=pl_nextrune(s); + }while(*s!='\0'); + return size; +} +void pl_drawmessage(Panel *p){ + pl_textmsg(p->b, pl_box(p->b, p->r, PASSIVE), font, ((Message *)p->data)->text); +} +int pl_hitmessage(Panel *g, Mouse *m){ + USED(g, m); + return 0; +} +void pl_typemessage(Panel *g, Rune c){ + USED(g, c); +} +Point pl_getsizemessage(Panel *p, Point children){ + Message *mp; + USED(children); + mp=p->data; + return pl_boxsize(pl_foldsize(font, mp->text, mp->minsize.x), PASSIVE); +} +void pl_childspacemessage(Panel *p, Point *ul, Point *size){ + USED(p, ul, size); +} +void plinitmessage(Panel *v, int flags, int wid, char *msg){ + Message *mp; + mp=v->data; + v->flags=flags|LEAF; + v->draw=pl_drawmessage; + v->hit=pl_hitmessage; + v->type=pl_typemessage; + v->getsize=pl_getsizemessage; + v->childspace=pl_childspacemessage; + mp->text=msg; + mp->minsize=Pt(wid, font->height); + v->kind="message"; +} +Panel *plmessage(Panel *parent, int flags, int wid, char *msg){ + Panel *v; + v=pl_newpanel(parent, sizeof(Message)); + plinitmessage(v, flags, wid, msg); + return v; +} diff --git a/libpanel/mkfile b/libpanel/mkfile new file mode 100644 index 000000000..5939cb6a6 --- /dev/null +++ b/libpanel/mkfile @@ -0,0 +1,34 @@ + +#include +#include +#include +#include +#include "pldefs.h" +int pl_max(int a, int b){ + return a>b?a:b; +} +Point pl_sizesibs(Panel *p){ + Point s; + if(p==0) return Pt(0,0); + s=pl_sizesibs(p->next); + switch(p->flags&PACK){ + case PACKN: + case PACKS: + s.x=pl_max(s.x, p->sizereq.x); + s.y+=p->sizereq.y; + break; + case PACKE: + case PACKW: + s.x+=p->sizereq.x; + s.y=pl_max(s.y, p->sizereq.y); + break; + } + return s; +} +/* + * Compute the requested size of p and its descendants. + */ +void pl_sizereq(Panel *p){ + Panel *cp; + Point maxsize; + maxsize=Pt(0,0); + for(cp=p->child;cp;cp=cp->next){ + pl_sizereq(cp); + if(cp->sizereq.x>maxsize.x) maxsize.x=cp->sizereq.x; + if(cp->sizereq.y>maxsize.y) maxsize.y=cp->sizereq.y; + } + for(cp=p->child;cp;cp=cp->next){ + if(cp->flags&MAXX) cp->sizereq.x=maxsize.x; + if(cp->flags&MAXY) cp->sizereq.y=maxsize.y; + } + p->childreq=pl_sizesibs(p->child); + p->sizereq=addpt(addpt(p->getsize(p, p->childreq), p->ipad), p->pad); + if(p->flags&FIXEDX) p->sizereq.x=p->fixedsize.x; + if(p->flags&FIXEDY) p->sizereq.y=p->fixedsize.y; +} +Point pl_getshare(Panel *p){ + Point share; + if(p==0) return Pt(0,0); + share=pl_getshare(p->next); + if(p->flags&EXPAND) switch(p->flags&PACK){ + case PACKN: + case PACKS: + if(share.x==0) share.x=1; + share.y++; + break; + case PACKE: + case PACKW: + share.x++; + if(share.y==0) share.y=1; + break; + } + return share; +} +/* + * Set the sizes and rectangles of p and its descendants, given their requested sizes. + */ +void pl_setrect(Panel *p, Point ul, Point avail){ + Point space, newul, newspace, slack, share; + int l; + Panel *c; + p->size=subpt(p->sizereq, p->pad); + ul=addpt(ul, divpt(p->pad, 2)); + avail=subpt(avail, p->pad); + if(p->size.x>avail.x) + p->size.x = avail.x; + if(p->size.y>avail.y) + p->size.y = avail.y; + if(p->flags&(FILLX|EXPAND)) p->size.x=avail.x; + if(p->flags&(FILLY|EXPAND)) p->size.y=avail.y; + switch(p->flags&PLACE){ + case PLACECEN: ul.x+=(avail.x-p->size.x)/2; ul.y+=(avail.y-p->size.y)/2; break; + case PLACES: ul.x+=(avail.x-p->size.x)/2; ul.y+= avail.y-p->size.y ; break; + case PLACEE: ul.x+= avail.x-p->size.x ; ul.y+=(avail.y-p->size.y)/2; break; + case PLACEW: ul.y+=(avail.y-p->size.y)/2; break; + case PLACEN: ul.x+=(avail.x-p->size.x)/2; break; + case PLACENE: ul.x+= avail.x-p->size.x ; break; + case PLACENW: break; + case PLACESE: ul.x+= avail.x-p->size.x ; ul.y+= avail.y-p->size.y ; break; + case PLACESW: ul.y+= avail.y-p->size.y ; break; + } + p->r=Rpt(ul, addpt(ul, p->size)); + space=p->size; + p->childspace(p, &ul, &space); + slack=subpt(space, p->childreq); + share=pl_getshare(p->child); + for(c=p->child;c;c=c->next){ + if(c->flags&IGNORE) continue; + if(c->flags&EXPAND){ + switch(c->flags&PACK){ + case PACKN: + case PACKS: + c->sizereq.x+=slack.x; + l=slack.y/share.y; + c->sizereq.y+=l; + slack.y-=l; + --share.y; + break; + case PACKE: + case PACKW: + l=slack.x/share.x; + c->sizereq.x+=l; + slack.x-=l; + --share.x; + c->sizereq.y+=slack.y; + break; + } + } + switch(c->flags&PACK){ + case PACKN: + newul=Pt(ul.x, ul.y+c->sizereq.y); + newspace=Pt(space.x, space.y-c->sizereq.y); + pl_setrect(c, ul, Pt(space.x, c->sizereq.y)); + break; + case PACKW: + newul=Pt(ul.x+c->sizereq.x, ul.y); + newspace=Pt(space.x-c->sizereq.x, space.y); + pl_setrect(c, ul, Pt(c->sizereq.x, space.y)); + break; + case PACKS: + newul=ul; + newspace=Pt(space.x, space.y-c->sizereq.y); + pl_setrect(c, Pt(ul.x, ul.y+space.y-c->sizereq.y), + Pt(space.x, c->sizereq.y)); + break; + case PACKE: + newul=ul; + newspace=Pt(space.x-c->sizereq.x, space.y); + pl_setrect(c, Pt(ul.x+space.x-c->sizereq.x, ul.y), + Pt(c->sizereq.x, space.y)); + break; + } + ul=newul; + space=newspace; + } +} +void plpack(Panel *p, Rectangle where){ + pl_sizereq(p); + pl_setrect(p, where.min, subpt(where.max, where.min)); +} +/* + * move an already-packed panel so that p->r=raddp(p->r, d) + */ +void plmove(Panel *p, Point d){ + if(strcmp(p->kind, "edit") == 0) /* sorry */ + plemove(p, d); + p->r=rectaddpt(p->r, d); + for(p=p->child;p;p=p->next) plmove(p, d); +} diff --git a/libpanel/panel.h b/libpanel/panel.h new file mode 100644 index 000000000..5d5134c28 --- /dev/null +++ b/libpanel/panel.h @@ -0,0 +1,203 @@ +//#pragma src "/sys/src/libpanel" +//#pragma lib "libpanel.a" +typedef struct Scroll Scroll; +typedef struct Panel Panel; /* a Graphical User Interface element */ +typedef struct Rtext Rtext; /* formattable text */ +typedef void Icon; /* Always used as Icon * -- Image or char */ +typedef struct Idol Idol; /* A picture/text combo */ +struct Scroll{ + Point pos, size; +}; +struct Rtext{ + int flags; /* responds to hits? text selection? */ + void *user; /* user data */ + int space; /* how much space before, if no break */ + int indent; /* how much space before, after a break */ + int voff; /* vertical offset (for subscripts and superscripts) */ + Image *b; /* what to display, if nonzero */ + Panel *p; /* what to display, if nonzero and b==0 */ + Font *font; /* font in which to draw text */ + char *text; /* what to display, if b==0 and p==0 */ + Rtext *next; /* next piece */ + /* private below */ + Rtext *nextline; /* links line to line */ + Rtext *last; /* last, for append */ + Rectangle r; /* where to draw, if origin were Pt(0,0) */ + int topy; /* y coord of top of line */ + int wid; /* not including space */ +}; +struct Panel{ + Point ipad, pad; /* extra space inside and outside */ + Point fixedsize; /* size of Panel, if FIXED */ + int user; /* available for user */ + void *userp; /* available for user */ + Rectangle r; /* where the Panel goes */ + /* private below */ + Panel *next; /* It's a list! */ + Panel *child, *echild, *parent; /* No, it's a tree! */ + Image *b; /* where we're drawn */ + int flags; /* position flags, see below */ + char *kind; /* what kind of panel? */ + int state; /* for hitting & drawing purposes */ + Point size; /* space for this Panel */ + Point sizereq; /* size requested by this Panel */ + Point childreq; /* total size needed by children */ + Panel *lastmouse; /* who got the last mouse event? */ + Panel *scrollee; /* pointer to scrolled window */ + Panel *xscroller, *yscroller; /* pointers to scroll bars */ + Scroll scr; /* scroll data */ + void *data; /* kind-specific data */ + void (*draw)(Panel *); /* draw panel and children */ + int (*pri)(Panel *, Point); /* priority for hitting */ + int (*hit)(Panel *, Mouse *); /* process mouse event */ + void (*type)(Panel *, Rune); /* process keyboard event */ + Point (*getsize)(Panel *, Point); /* return size, given child size */ + void (*childspace)(Panel *, Point *, Point *); /* child ul & size given our size */ + void (*scroll)(Panel *, int, int, int, int); /* scroll bar to scrollee */ + void (*setscrollbar)(Panel *, int, int, int); /* scrollee to scroll bar */ + void (*free)(Panel *); /* free fields of data when done */ + char* (*snarf)(Panel *); /* snarf text from panel */ + void (*paste)(Panel *, char *); /* paste text into panel */ +}; +/* + * Panel flags + */ +#define PACK 0x0007 /* which side of the parent is the Panel attached to? */ +#define PACKN 0x0000 +#define PACKE 0x0001 +#define PACKS 0x0002 +#define PACKW 0x0003 +#define PACKCEN 0x0004 /* only used by pulldown */ +#define FILLX 0x0008 /* grow horizontally to fill the available space */ +#define FILLY 0x0010 /* grow vertically to fill the available space */ +#define PLACE 0x01e0 /* which side of its space should the Panel adhere to? */ +#define PLACECEN 0x0000 +#define PLACES 0x0020 +#define PLACEE 0x0040 +#define PLACEW 0x0060 +#define PLACEN 0x0080 +#define PLACENE 0x00a0 +#define PLACENW 0x00c0 +#define PLACESE 0x00e0 +#define PLACESW 0x0100 +#define EXPAND 0x0200 /* use up all extra space in the parent */ +#define FIXED 0x0c00 /* don't pass children's size requests through to parent */ +#define FIXEDX 0x0400 +#define FIXEDY 0x0800 +#define MAXX 0x1000 /* make x size as big as biggest sibling's */ +#define MAXY 0x2000 /* make y size as big as biggest sibling's */ +#define BITMAP 0x4000 /* text argument is a bitmap, not a string */ +#define NOBORDER 0x8000 +/* pldefs.h flags 0x08000-0x40000 */ +#define IGNORE 0x080000 /* ignore this panel totally */ +#define USERFL 0x100000 /* start of user flag */ + +/* + * An extra bit in Mouse.buttons + */ +#define OUT 8 /* Mouse.buttons bit, set when mouse leaves Panel */ +/* + * Priorities + */ +#define PRI_NORMAL 0 /* ordinary panels */ +#define PRI_POPUP 1 /* popup menus */ +#define PRI_SCROLLBAR 2 /* scroll bars */ + +/* Rtext.flags */ +#define PL_HOT 1 +#define PL_SEL 2 +#define PL_STR 4 +#define PL_HEAD 8 + +Panel *plkbfocus; /* the panel in keyboard focus */ + +int plinit(int); /* initialization */ +void plpack(Panel *, Rectangle); /* figure out where to put the Panel & children */ +void plmove(Panel *, Point); /* move an already-packed panel to a new location */ +void pldraw(Panel *, Image *); /* display the panel on the bitmap */ +void plfree(Panel *); /* give back space */ +void plgrabkb(Panel *); /* this Panel should receive keyboard events */ +void plkeyboard(Rune); /* send a keyboard event to the appropriate Panel */ +void plmouse(Panel *, Mouse *); /* send a Mouse event to a Panel tree */ +void plscroll(Panel *, Panel *, Panel *); /* link up scroll bars */ +char *plentryval(Panel *); /* entry delivers its value */ +void plsetbutton(Panel *, int); /* set or clear the mark on a button */ +void plsetslider(Panel *, int, int); /* set the value of a slider */ +Rune *pleget(Panel *); /* get the text from an edit window */ +int plelen(Panel *); /* get the length of the text from an edit window */ +void plegetsel(Panel *, int *, int *); /* get the selection from an edit window */ +void plepaste(Panel *, Rune *, int); /* paste in an edit window */ +void plesel(Panel *, int, int); /* set the selection in an edit window */ +void plescroll(Panel *, int); /* scroll an edit window */ +Scroll plgetscroll(Panel *); /* get scrolling information from panel */ +void plsetscroll(Panel *, Scroll); /* set scrolling information */ +void plplacelabel(Panel *, int); /* label placement */ + +/* + * Panel creation & reinitialization functions + */ +Panel *plbutton(Panel *pl, int, Icon *, void (*)(Panel *pl, int)); +Panel *plcanvas(Panel *pl, int, void (*)(Panel *), void (*)(Panel *pl, Mouse *)); +Panel *plcheckbutton(Panel *pl, int, Icon *, void (*)(Panel *pl, int, int)); +Panel *pledit(Panel *, int, Point, Rune *, int, void (*)(Panel *)); +Panel *plentry(Panel *pl, int, int, char *, void (*)(Panel *pl, char *)); +Panel *plframe(Panel *pl, int); +Panel *plgroup(Panel *pl, int); +Panel *plidollist(Panel*, int, Point, Font*, Idol*, void (*)(Panel*, int, void*)); +Panel *pllabel(Panel *pl, int, Icon *); +Panel *pllist(Panel *pl, int, char *(*)(Panel *, int), int, void(*)(Panel *pl, int, int)); +Panel *plmenu(Panel *pl, int, Icon **, int, void (*)(int, int)); +Panel *plmenubar(Panel *pl, int, int, Icon *, Panel *pl, Icon *, ...); +Panel *plmessage(Panel *pl, int, int, char *); +Panel *plpopup(Panel *pl, int, Panel *pl, Panel *pl, Panel *pl); +Panel *plpulldown(Panel *pl, int, Icon *, Panel *pl, int); +Panel *plradiobutton(Panel *pl, int, Icon *, void (*)(Panel *pl, int, int)); +Panel *plscrollbar(Panel *plparent, int flags); +Panel *plslider(Panel *pl, int, Point, void(*)(Panel *pl, int, int, int)); +Panel *pltextview(Panel *, int, Point, Rtext *, void (*)(Panel *, int, Rtext *)); +void plinitbutton(Panel *, int, Icon *, void (*)(Panel *, int)); +void plinitcanvas(Panel *, int, void (*)(Panel *), void (*)(Panel *, Mouse *)); +void plinitcheckbutton(Panel *, int, Icon *, void (*)(Panel *, int, int)); +void plinitedit(Panel *, int, Point, Rune *, int, void (*)(Panel *)); +void plinitentry(Panel *, int, int, char *, void (*)(Panel *, char *)); +void plinitframe(Panel *, int); +void plinitgroup(Panel *, int); +void plinitidollist(Panel*, int, Point, Font*, Idol*, void (*)(Panel*, int, void*)); +void plinitlabel(Panel *, int, Icon *); +void plinitlist(Panel *, int, char *(*)(Panel *, int), int, void(*)(Panel *, int, int)); +void plinitmenu(Panel *, int, Icon **, int, void (*)(int, int)); +void plinitmessage(Panel *, int, int, char *); +void plinitpopup(Panel *, int, Panel *, Panel *, Panel *); +void plinitpulldown(Panel *, int, Icon *, Panel *, int); +void plinitradiobutton(Panel *, int, Icon *, void (*)(Panel *, int, int)); +void plinitscrollbar(Panel *parent, int flags); +void plinitslider(Panel *, int, Point, void(*)(Panel *, int, int, int)); +void plinittextview(Panel *, int, Point, Rtext *, void (*)(Panel *, int, Rtext *)); +/* + * Rtext constructors & destructor + */ +Rtext *plrtstr(Rtext **, int, int, int, Font *, char *, int, void *); +Rtext *plrtbitmap(Rtext **, int, int, int, Image *, int, void *); +Rtext *plrtpanel(Rtext **, int, int, int, Panel *, void *); +void plrtfree(Rtext *); +void plrtseltext(Rtext *, Rtext *, Rtext *); +char *plrtsnarftext(Rtext *); + +int plgetpostextview(Panel *); +void plsetpostextview(Panel *, int); + +/* + * Idols + */ +Idol *plmkidol(Idol**, Image*, Image*, char*, void*); +void plfreeidol(Idol*); +Point plidolsize(Idol*, Font*, int); +void *plidollistgetsel(Panel*); + +/* + * Snarf + */ +void plputsnarf(char *); +char *plgetsnarf(void); +void plsnarf(Panel *); /* snarf a panel */ +void plpaste(Panel *); /* paste a panel */ diff --git a/libpanel/panel.pdf b/libpanel/panel.pdf new file mode 100644 index 0000000000000000000000000000000000000000..7c84c14e0a03699c88ab9842e5339fc2b0449974 GIT binary patch literal 87888 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

mHq{`~nU@KRl-e;K+K=R!ie8FW;>N?b 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 literal 0 HcmV?d00001 diff --git a/libpanel/pldefs.h b/libpanel/pldefs.h new file mode 100644 index 000000000..786b5e8e3 --- /dev/null +++ b/libpanel/pldefs.h @@ -0,0 +1,105 @@ +/* + * Definitions for internal use only + */ +/* + * Variable-font text routines + * These could make a separate library. + */ +Point pl_rtfmt(Rtext *, int); +void pl_rtdraw(Image *, Rectangle, Rtext *, Point); +void pl_rtredraw(Image *, Rectangle, Rtext *, Point, Point, int); +Rtext *pl_rthit(Rtext *, Point, Point, Point); +#define HITME 0x08000 /* tells ptinpanel not to look at children */ +#define LEAF 0x10000 /* newpanel will refuse to attach children */ +#define INVIS 0x20000 /* don't draw this */ +#define REMOUSE 0x40000 /* send next mouse event here, even if not inside */ +/* + * States, also styles + */ +enum{ + UP, + DOWN1, + DOWN2, + DOWN3, + DOWN, + PASSIVE, + FRAME +}; +/* + * Scroll flags + */ +enum{ + SCROLLUP, + SCROLLDOWN, + SCROLLABSY, + SCROLLLEFT, + SCROLLRIGHT, + SCROLLABSX, +}; +/* + * Scrollbar, slider orientations + */ +enum{ + HORIZ, + VERT +}; +Panel *pl_newpanel(Panel *, int); /* make a new Panel, given parent & data size */ +void *pl_emalloc(int); /* allocate some space, exit on error */ +void *pl_erealloc(void*,int); /* reallocate some space, exit on error */ +void pl_print(Panel *); /* print a Panel tree */ +Panel *pl_ptinpanel(Point, Panel *); /* highest-priority subpanel containing point */ +/* + * Drawing primitives + */ +int pl_drawinit(int); +Rectangle pl_box(Image *, Rectangle, int); +Rectangle pl_boxf(Image *b, Rectangle r, int flags, int style); +Rectangle pl_outline(Image *, Rectangle, int); +Point pl_boxsize(Point, int); +void pl_interior(int, Point *, Point *); +void pl_drawicon(Image *, Rectangle, int, int, Icon *); +Rectangle pl_check(Image *, Rectangle, int); +Rectangle pl_radio(Image *, Rectangle, int); +int pl_ckwid(void); +void pl_sliderupd(Image *, Rectangle, int, int, int); +void pl_invis(Panel *, int); +Point pl_iconsize(int, Icon *); +void pl_highlight(Image *, Rectangle); +void pl_clr(Image *, Rectangle); +void pl_fill(Image *, Rectangle); +void pl_cpy(Image *, Point, Rectangle); + +/* + * Rune mangling functions + */ +int pl_idchar(int); +int pl_rune1st(int); +char *pl_nextrune(char *); +int pl_runewidth(Font *, char *); +/* + * Fixed-font Text-window routines + * These could be separated out into a separate library. + */ +typedef struct Textwin Textwin; +struct Textwin{ + Rune *text, *etext, *eslack; /* text, with some slack off the end */ + int top, bot; /* range of runes visible on screen */ + int sel0, sel1; /* selection */ + Point *loc, *eloc; /* ul corners of visible runes (+1 more at end!) */ + Image *b; /* bitmap the text is drawn in */ + Rectangle r; /* rectangle the text is drawn in */ + Font *font; /* font text is drawn in */ + int hgt; /* same as font->height */ + int tabstop; /* tab settings are every tabstop pixels */ + int mintab; /* the minimum size of a tab */ +}; +Textwin *twnew(Image *, Font *, Rune *, int); +void twfree(Textwin *); +void twhilite(Textwin *, int, int, int); +void twselect(Textwin *, Mouse *); +void twreplace(Textwin *, int, int, Rune *, int); +void twscroll(Textwin *, int); +int twpt2rune(Textwin *, Point); +void twreshape(Textwin *, Rectangle); +void twmove(Textwin *, Point); +void plemove(Panel *, Point); diff --git a/libpanel/popup.c b/libpanel/popup.c new file mode 100644 index 000000000..ea3bde177 --- /dev/null +++ b/libpanel/popup.c @@ -0,0 +1,116 @@ +/* + * popup + * looks like a group, except diverts hits on certain buttons to + * panels that it temporarily pops up. + */ +#include +#include +#include +#include +#include +#include "pldefs.h" +typedef struct Popup Popup; +struct Popup{ + Image *save; /* where to save what the popup covers */ + Panel *pop[3]; /* what to pop up */ +}; +void pl_drawpopup(Panel *p){ + USED(p); +} +int pl_hitpopup(Panel *g, Mouse *m){ + Panel *p; + Point d; + Popup *pp; + + pp=g->data; + if(g->state==UP){ + switch(m->buttons&7){ + case 0: p=g->child; break; + case 1: p=pp->pop[0]; g->state=DOWN1; break; + case 2: p=pp->pop[1]; g->state=DOWN2; break; + case 4: p=pp->pop[2]; g->state=DOWN3; break; + default: p=0; break; + } + if(p==0){ + p=g->child; + g->state=DOWN; + } + else if(g->state!=UP){ + plpack(p, screen->clipr); + if(p->lastmouse) + d=subpt(m->xy, divpt(addpt(p->lastmouse->r.min, + p->lastmouse->r.max), 2)); + else + d=subpt(m->xy, divpt(addpt(p->r.min, p->r.max), 2)); + if(p->r.min.x+d.xr.min.x) d.x=g->r.min.x-p->r.min.x; + if(p->r.max.x+d.x>g->r.max.x) d.x=g->r.max.x-p->r.max.x; + if(p->r.min.y+d.yr.min.y) d.y=g->r.min.y-p->r.min.y; + if(p->r.max.y+d.y>g->r.max.y) d.y=g->r.max.y-p->r.max.y; + plmove(p, d); + pp->save=allocimage(display, p->r, g->b->chan, 0, DNofill); + if(pp->save!=0) draw(pp->save, p->r, g->b, 0, p->r.min); + pl_invis(p, 0); + pldraw(p, g->b); + } + } + else{ + switch(g->state){ + default: SET(p); break; /* can't happen! */ + case DOWN1: p=pp->pop[0]; break; + case DOWN2: p=pp->pop[1]; break; + case DOWN3: p=pp->pop[2]; break; + case DOWN: p=g->child; break; + } + if((m->buttons&7)==0){ + if(g->state!=DOWN){ + if(pp->save!=0){ + draw(g->b, p->r, pp->save, 0, p->r.min); + freeimage(pp->save); + pp->save=0; + } + pl_invis(p, 1); + } + g->state=UP; + } + } + plmouse(p, m); + if((m->buttons&7)==0) + g->state=UP; + return (m->buttons&7)!=0; +} +void pl_typepopup(Panel *g, Rune c){ + USED(g, c); +} +Point pl_getsizepopup(Panel *g, Point children){ + USED(g); + return children; +} +void pl_childspacepopup(Panel *g, Point *ul, Point *size){ + USED(g, ul, size); +} +int pl_pripopup(Panel *, Point){ + return PRI_POPUP; +} +void plinitpopup(Panel *v, int flags, Panel *pop0, Panel *pop1, Panel *pop2){ + Popup *pp; + pp=v->data; + v->flags=flags; + v->pri=pl_pripopup; + v->state=UP; + v->draw=pl_drawpopup; + v->hit=pl_hitpopup; + v->type=pl_typepopup; + v->getsize=pl_getsizepopup; + v->childspace=pl_childspacepopup; + pp->pop[0]=pop0; + pp->pop[1]=pop1; + pp->pop[2]=pop2; + pp->save=0; + v->kind="popup"; +} +Panel *plpopup(Panel *parent, int flags, Panel *pop0, Panel *pop1, Panel *pop2){ + Panel *v; + v=pl_newpanel(parent, sizeof(Popup)); + plinitpopup(v, flags, pop0, pop1, pop2); + return v; +} diff --git a/libpanel/print.c b/libpanel/print.c new file mode 100644 index 000000000..a3bd70210 --- /dev/null +++ b/libpanel/print.c @@ -0,0 +1,56 @@ +#include +#include +#include +#include +#include +#include "pldefs.h" +void pl_iprint(int indent, char *fmt, ...){ + char buf[8192]; + va_list arg; + memset(buf, '\t', indent); + va_start(arg, fmt); + write(1, buf, vsnprint(buf+indent, sizeof(buf)-indent, fmt, arg)); + va_end(arg); +} +void pl_ipprint(Panel *p, int n){ + Panel *c; + char *place, *stick; + pl_iprint(n, "%s (0x%.8x)\n", p->kind, p); + pl_iprint(n, " r=(%d %d, %d %d)\n", + p->r.min.x, p->r.min.y, p->r.max.x, p->r.max.y); + switch(p->flags&PACK){ + default: SET(place); break; + case PACKN: place="n"; break; + case PACKE: place="e"; break; + case PACKS: place="s"; break; + case PACKW: place="w"; break; + } + switch(p->flags&PLACE){ + default: SET(stick); break; + case PLACECEN: stick=""; break; + case PLACES: stick=" stick s"; break; + case PLACEE: stick=" stick e"; break; + case PLACEW: stick=" stick w"; break; + case PLACEN: stick=" stick n"; break; + case PLACENE: stick=" stick ne"; break; + case PLACENW: stick=" stick nw"; break; + case PLACESE: stick=" stick se"; break; + case PLACESW: stick=" stick sw"; break; + } + pl_iprint(n, " place %s%s%s%s%s%s\n", + place, + p->flags&FILLX?" fill x":"", + p->flags&FILLY?" fill y":"", + stick, + p->flags&EXPAND?" expand":"", + p->flags&FIXED?" fixed":""); + if(!eqpt(p->pad, Pt(0, 0))) pl_iprint(n, " pad=%d,%d)\n", p->pad.x, p->pad.y); + if(!eqpt(p->ipad, Pt(0, 0))) pl_iprint(n, " ipad=%d,%d)\n", p->ipad.x, p->ipad.y); + pl_iprint(n, " size=(%d,%d), sizereq=(%d,%d)\n", + p->size.x, p->size.y, p->sizereq.x, p->sizereq.y); + for(c=p->child;c;c=c->next) + pl_ipprint(c, n+1); +} +void pl_print(Panel *p){ + pl_ipprint(p, 0); +} diff --git a/libpanel/pulldown.c b/libpanel/pulldown.c new file mode 100644 index 000000000..3b05d2f97 --- /dev/null +++ b/libpanel/pulldown.c @@ -0,0 +1,160 @@ +/* + * pulldown + * makes a button that pops up a panel when hit + */ +#include +#include +#include +#include +#include +#include "pldefs.h" +typedef struct Pulldown Pulldown; +struct Pulldown{ + Icon *icon; /* button label */ + Panel *pull; /* Panel to pull down */ + int side; /* which side of the button to put the panel on */ + Image *save; /* where to save what we draw the panel on */ +}; +void pl_drawpulldown(Panel *p){ + pl_drawicon(p->b, pl_box(p->b, p->r, p->state), PLACECEN, + p->flags, ((Pulldown *)p->data)->icon); +} +int pl_hitpulldown(Panel *g, Mouse *m){ + int oldstate, passon; + Rectangle r; + Panel *p, *hitme; + Pulldown *pp; + pp=g->data; + oldstate=g->state; + p=pp->pull; + hitme=0; + switch(g->state){ + case UP: + if(!ptinrect(m->xy, g->r)) + g->state=UP; + else if(m->buttons&7){ + r=g->b->r; + p->flags&=~PLACE; + switch(pp->side){ + case PACKN: + r.min.x=g->r.min.x; + r.max.y=g->r.min.y; + p->flags|=PLACESW; + break; + case PACKS: + r.min.x=g->r.min.x; + r.min.y=g->r.max.y; + p->flags|=PLACENW; + break; + case PACKE: + r.min.x=g->r.max.x; + r.min.y=g->r.min.y; + p->flags|=PLACENW; + break; + case PACKW: + r.max.x=g->r.min.x; + r.min.y=g->r.min.y; + p->flags|=PLACENE; + break; + case PACKCEN: + r.min=g->r.min; + p->flags|=PLACENW; + break; + } + plpack(p, r); + pp->save=allocimage(display, p->r, g->b->chan, 0, DNofill); + if(pp->save!=0) draw(pp->save, p->r, g->b, 0, p->r.min); + pl_invis(p, 0); + pldraw(p, g->b); + g->state=DOWN; + } + break; + case DOWN: + if(!ptinrect(m->xy, g->r)){ + switch(pp->side){ + default: SET(passon); break; /* doesn't happen */ + case PACKN: passon=m->xy.yr.min.y; break; + case PACKS: passon=m->xy.y>=g->r.max.y; break; + case PACKE: passon=m->xy.x>=g->r.max.x; break; + case PACKW: passon=m->xy.xr.min.x; break; + case PACKCEN: passon=1; break; + } + if(passon){ + hitme=p; + if((m->buttons&7)==0) g->state=UP; + } + else g->state=UP; + } + else if((m->buttons&7)==0) g->state=UP; + else hitme=p; + if(g->state!=DOWN && pp->save){ + draw(g->b, p->r, pp->save, 0, p->r.min); + freeimage(pp->save); + pp->save=0; + pl_invis(p, 1); + hitme=p; + } + } + if(g->state!=oldstate) pldraw(g, g->b); + if(hitme) plmouse(hitme, m); + return g->state==DOWN; +} +void pl_typepulldown(Panel *p, Rune c){ + USED(p, c); +} +Point pl_getsizepulldown(Panel *p, Point children){ + USED(p, children); + return pl_boxsize(pl_iconsize(p->flags, ((Pulldown *)p->data)->icon), p->state); +} +void pl_childspacepulldown(Panel *p, Point *ul, Point *size){ + USED(p, ul, size); +} +void plinitpulldown(Panel *v, int flags, Icon *icon, Panel *pullthis, int side){ + Pulldown *pp; + pp=v->data; + v->flags=flags|LEAF; + v->draw=pl_drawpulldown; + v->hit=pl_hitpulldown; + v->type=pl_typepulldown; + v->getsize=pl_getsizepulldown; + v->childspace=pl_childspacepulldown; + pp->pull=pullthis; + pp->side=side; + pp->icon=icon; + v->kind="pulldown"; +} +Panel *plpulldown(Panel *parent, int flags, Icon *icon, Panel *pullthis, int side){ + Panel *v; + v=pl_newpanel(parent, sizeof(Pulldown)); + v->state=UP; + ((Pulldown *)v->data)->save=0; + plinitpulldown(v, flags, icon, pullthis, side); + return v; +} +Panel *plmenubar(Panel *parent, int flags, int cflags, Icon *l1, Panel *m1, Icon *l2, ...){ + Panel *v; + va_list arg; + Icon *s; + int pulldir; + switch(cflags&PACK){ + default: + SET(pulldir); + break; + case PACKE: + case PACKW: + pulldir=PACKS; + break; + case PACKN: + case PACKS: + pulldir=PACKE; + break; + } + v=plgroup(parent, flags); + va_start(arg, cflags); + while((s=va_arg(arg, Icon *))!=0) + plpulldown(v, cflags, s, va_arg(arg, Panel *), pulldir); + va_end(arg); + USED(l1, m1, l2); + v->kind="menubar"; + return v; +} diff --git a/libpanel/rtext.c b/libpanel/rtext.c new file mode 100644 index 000000000..7e4d0f367 --- /dev/null +++ b/libpanel/rtext.c @@ -0,0 +1,383 @@ +/* + * Rich text with images. + * Should there be an offset field, to do subscripts & kerning? + */ +#include +#include +#include +#include +#include +#include "pldefs.h" +#include "rtext.h" + +#define LEAD 4 /* extra space between lines */ +#define BORD 2 /* extra border for images */ + +static Image *head, *blue; + +Rtext *pl_rtnew(Rtext **t, int space, int indent, int voff, Image *b, Panel *p, Font *f, char *s, int flags, void *user){ + Rtext *new; + new=pl_emalloc(sizeof(Rtext)); + new->flags=flags; + new->user=user; + new->space=space; + new->indent=indent; + new->voff=voff; + new->b=b; + new->p=p; + new->font=f; + new->text=s; + new->next=0; + new->nextline=0; + new->r=Rect(0,0,0,0); + if(*t) + (*t)->last->next=new; + else + *t=new; + (*t)->last=new; + return new; +} +Rtext *plrtpanel(Rtext **t, int space, int indent, int voff, Panel *p, void *user){ + return pl_rtnew(t, space, indent, voff, 0, p, 0, 0, 1, user); +} +Rtext *plrtstr(Rtext **t, int space, int indent, int voff, Font *f, char *s, int flags, void *user){ + return pl_rtnew(t, space, indent, voff, 0, 0, f, s, flags, user); +} +Rtext *plrtbitmap(Rtext **t, int space, int indent, int voff, Image *b, int flags, void *user){ + return pl_rtnew(t, space, indent, voff, b, 0, 0, 0, flags, user); +} +void plrtfree(Rtext *t){ + Rtext *next; + while(t){ + next=t->next; + free(t); + t=next; + } +} +int pl_tabmin, pl_tabsize; +void pltabsize(int min, int size){ + pl_tabmin=min; + pl_tabsize=size; +} +int pl_space(int space, int pos, int indent){ + if(space>=0) return space; + switch(PL_OP(space)){ + default: + return 0; + case PL_TAB: + return ((pos-indent+pl_tabmin)/pl_tabsize+PL_ARG(space))*pl_tabsize+indent-pos; + } +} +/* + * initialize rectangles & nextlines of text starting at t, + * galley width is wid. Returns the total width/height of the text + */ +Point pl_rtfmt(Rtext *t, int wid){ + Rtext *tp, *eline; + int ascent, descent, x, space, a, d, w, topy, indent, maxwid; + Point p; + + p=Pt(0,0); + eline=t; + maxwid=0; + while(t){ + ascent=0; + descent=0; + indent=space=pl_space(t->indent, 0, 0); + x=0; + tp=t; + for(;;){ + if(tp->b){ + a=tp->b->r.max.y-tp->b->r.min.y+BORD; + d=BORD; + w=tp->b->repl?wid-x:tp->b->r.max.x-tp->b->r.min.x+BORD*2; + } + else if(tp->p){ + /* what if plpack fails? */ + plpack(tp->p, Rect(0,0,wid,wid)); + plmove(tp->p, subpt(Pt(0,0), tp->p->r.min)); + a=tp->p->r.max.y-tp->p->r.min.y; + d=0; + w=tp->p->r.max.x-tp->p->r.min.x; + } + else{ + a=tp->font->ascent; + d=tp->font->height-a; + w=tp->wid=stringwidth(tp->font, tp->text); + } + a-=tp->voff,d+=tp->voff; + if(x+w+space>wid) break; + if(a>ascent) ascent=a; + if(d>descent) descent=d; + x+=w+space; + tp=tp->next; + if(tp==0){ + eline=0; + break; + } + space=pl_space(tp->space, x, indent); + if(space) eline=tp; + } + if(eline==t){ /* No progress! Force fit the first block! */ + if(tp==t){ + if(a>ascent) ascent=a; + if(d>descent) descent=d; + eline=tp->next; + }else + eline=tp; + } + topy=p.y; + p.y+=ascent; + p.x=indent=pl_space(t->indent, 0, 0); + for(;;){ + t->topy=topy; + t->r.min.x=p.x; + p.y+=t->voff; + if(t->b){ + t->r.max.y=p.y+BORD; + t->r.min.y=p.y-(t->b->r.max.y-t->b->r.min.y)-BORD; + p.x+=t->b->repl?wid-p.x:(t->b->r.max.x-t->b->r.min.x)+BORD*2; + } + else if(t->p){ + t->r.max.y=p.y; + t->r.min.y=p.y-t->p->r.max.y; + p.x+=t->p->r.max.x; + } + else{ + t->r.min.y=p.y-t->font->ascent; + t->r.max.y=t->r.min.y+t->font->height; + p.x+=t->wid; + } + p.y-=t->voff; + t->r.max.x=p.x; + t->nextline=eline; + t=t->next; + if(t==eline) break; + p.x+=pl_space(t->space, p.x, indent); + } + if(p.x>maxwid) maxwid=p.x; + p.y+=descent+LEAD; + } + return Pt(maxwid, p.y); +} + +/* + * If we draw the text in a backup bitmap and copy it onto the screen, + * the bitmap pointers in all the subpanels point to the wrong bitmap. + * This code fixes them. + */ +void pl_stuffbitmap(Panel *p, Image *b){ + p->b=b; + for(p=p->child;p;p=p->next) + pl_stuffbitmap(p, b); +} + +void pl_rtdraw(Image *b, Rectangle r, Rtext *t, Point offs){ + static Image *backup; + Point lp, sp; + Rectangle dr; + Image *bb; + + bb = b; + if(backup==0 || backup->chan!=b->chan || rectinrect(r, backup->r)==0){ + freeimage(backup); + backup=allocimage(display, bb->r, bb->chan, 0, DNofill); + } + if(backup) + b=backup; + pl_clr(b, r); + lp=ZP; + sp=ZP; + offs=subpt(r.min, offs); + for(;t;t=t->next) if(!eqrect(t->r, Rect(0,0,0,0))){ + dr=rectaddpt(t->r, offs); + if(dr.max.y>r.min.y + && dr.min.yr.min.x + && dr.min.xb){ + draw(b, insetrect(dr, BORD), t->b, 0, t->b->r.min); + if(t->flags&PL_HOT) border(b, dr, 1, display->black, ZP); + if(t->flags&PL_STR) { + line(b, Pt(dr.min.x, dr.min.y), Pt(dr.max.x, dr.max.y), + Endsquare, Endsquare, 0, + display->black, ZP); + line(b, Pt(dr.min.x, dr.max.y), Pt(dr.max.x, dr.min.y), + Endsquare, Endsquare, 0, + display->black, ZP); + } + if(t->flags&PL_SEL) + pl_highlight(b, dr); + } + else if(t->p){ + plmove(t->p, subpt(dr.min, t->p->r.min)); + pldraw(t->p, b); + if(b!=bb) + pl_stuffbitmap(t->p, bb); + } + else{ + if(t->flags&PL_HEAD){ + if(head==nil) + head=allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xAAAAAAFF); + string(b, dr.min, head, ZP, t->font, t->text); + }else if(t->flags&PL_HOT){ + if(blue==nil) + blue=allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x0000FFFF); + string(b, dr.min, blue, ZP, t->font, t->text); + }else + string(b, dr.min, display->black, ZP, t->font, t->text); + if(t->flags&PL_SEL) + pl_highlight(b, dr); + if(t->flags&PL_STR){ + int y = dr.max.y - t->font->height/2; + if(sp.y != y) + sp = Pt(dr.min.x, y); + line(b, sp, Pt(dr.max.x, y), + Endsquare, Endsquare, 0, + display->black, ZP); + sp = Pt(dr.max.x, y); + } else + sp = ZP; + /*if(t->flags&PL_HOT){ + int y = dr.max.y - 1; + if(lp.y != y) + lp = Pt(dr.min.x, y); + line(b, lp, Pt(dr.max.x, y), + Endsquare, Endsquare, 0, + display->black, ZP); + lp = Pt(dr.max.x, y); + } else*/ + lp = ZP; + continue; + } + lp = ZP; + sp = ZP; + } + } + if(b!=bb) + draw(bb, r, b, 0, r.min); +} +/* + * Reposition text already drawn in the window. + * We just move the pixels and update the positions of any + * enclosed panels + */ +void pl_reposition(Rtext *t, Image *b, Point p, Rectangle r){ + Point offs; + pl_cpy(b, p, r); + offs=subpt(p, r.min); + for(;t;t=t->next) + if(!eqrect(t->r, Rect(0,0,0,0)) && !t->b && t->p) + plmove(t->p, offs); +} +/* + * Rectangle r of Image b contains an image of Rtext t, offset by oldoffs. + * Redraw the text to have offset yoffs. + */ +void pl_rtredraw(Image *b, Rectangle r, Rtext *t, Point offs, Point oldoffs, int dir){ + int d, size; + + if(dir==VERT){ + d=oldoffs.y-offs.y; + size=r.max.y-r.min.y; + if(d>=size || -d>=size) /* move more than screenful */ + pl_rtdraw(b, r, t, offs); + else if(d<0){ /* down */ + pl_reposition(t, b, r.min, + Rect(r.min.x, r.min.y-d, r.max.x, r.max.y)); + pl_rtdraw(b, Rect(r.min.x, r.max.y+d, r.max.x, r.max.y), + t, Pt(offs.x, offs.y+size+d)); + } + else if(d>0){ /* up */ + pl_reposition(t, b, Pt(r.min.x, r.min.y+d), + Rect(r.min.x, r.min.y, r.max.x, r.max.y-d)); + pl_rtdraw(b, Rect(r.min.x, r.min.y, r.max.x, r.min.y+d), + t, offs); + } + }else{ /* dir==HORIZ */ + d=oldoffs.x-offs.x; + size=r.max.x-r.min.x; + if(d>=size || -d>=size) /* move more than screenful */ + pl_rtdraw(b, r, t, offs); + else if(d<0){ /* right */ + pl_reposition(t, b, r.min, + Rect(r.min.x-d, r.min.y, r.max.x, r.max.y)); + pl_rtdraw(b, Rect(r.max.x+d, r.min.y, r.max.x, r.max.y), + t, Pt(offs.x+size+d, offs.y)); + } + else if(d>0){ /* left */ + pl_reposition(t, b, Pt(r.min.x+d, r.min.y), + Rect(r.min.x, r.min.y, r.max.x-d, r.max.y)); + pl_rtdraw(b, Rect(r.min.x, r.min.y, r.min.x+d, r.max.y), + t, offs); + } + } +} +Rtext *pl_rthit(Rtext *t, Point offs, Point p, Point ul){ + Rectangle r; + Point lp; + if(t==0) return 0; + p.x+=offs.x-ul.x; + p.y+=offs.y-ul.y; + while(t->nextline && t->nextline->topy<=p.y) t=t->nextline; + lp=ZP; + for(;t!=0;t=t->next){ + if(t->topy>p.y) return 0; + r = t->r; + if((t->flags&PL_HOT) != 0 && t->b == nil && t->p == nil){ + if(lp.y == r.max.y && lp.x < r.min.x) + r.min.x=lp.x; + lp=r.max; + } else + lp=ZP; + if(ptinrect(p, r)) return t; + } + return 0; +} + +void plrtseltext(Rtext *t, Rtext *s, Rtext *e){ + while(t){ + t->flags &= ~PL_SEL; + t = t->next; + } + if(s==0 || e==0) + return; + for(t=s; t!=0 && t!=e; t=t->next) + ; + if(t==e){ + for(t=s; t!=e; t=t->next) + t->flags |= PL_SEL; + }else{ + for(t=e; t!=s; t=t->next) + t->flags |= PL_SEL; + } + t->flags |= PL_SEL; +} + +char *plrtsnarftext(Rtext *w){ + char *b, *p, *e, *t; + int n; + + b=p=e=0; + for(; w; w = w->next){ + if((w->flags&PL_SEL)==0 || w->text==0) + continue; + n = strlen(w->text)+64; + if(p+n >= e){ + n = (p+n+64)-b; + t = pl_erealloc(b, n); + p = t+(p-b); + e = t+n; + b = t; + } + if(w->space == 0) + p += sprint(p, "%s", w->text); + else if(w->space > 0) + p += sprint(p, " %s", w->text); + else if(PL_OP(w->space) == PL_TAB) + p += sprint(p, "\t%s", w->text); + if(w->nextline == w->next) + p += sprint(p, "\n"); + } + return b; +} diff --git a/libpanel/rtext.h b/libpanel/rtext.h new file mode 100644 index 000000000..78a4b0ce9 --- /dev/null +++ b/libpanel/rtext.h @@ -0,0 +1,11 @@ +/* + * Rtext definitions + */ +#define PL_NOPBIT 4 +#define PL_NARGBIT 12 +#define PL_ARGMASK ((1< +#include +#include +#include +#include +Panel *root, *list; +char *genlist(Panel *, int which){ + static char buf[7]; + if(which<0 || 26<=which) return 0; + sprint(buf, "item %c", which+'a'); + return buf; +} +void hitgen(Panel *p, int buttons, int sel){ + USED(p, buttons, sel); +} +void ereshaped(Rectangle r){ + screen.r=r; + r=inset(r, 4); + plpack(root, r); + bitblt(&screen, screen.r.min, &screen, screen.r, Zero); + pldraw(root, &screen); +} +void done(Panel *p, int buttons){ + USED(p, buttons); + bitblt(&screen, screen.r.min, &screen, screen.r, Zero); + exits(0); +} +Panel *msg; +void message(char *s, ...){ + char buf[1024], *out; + va_list arg; + va_start(arg, s); + out = doprint(buf, buf+sizeof(buf), s, arg); + va_end(arg); + *out='\0'; + plinitlabel(msg, PACKN|FILLX, buf); + pldraw(msg, &screen); +} +Scroll s; +void save(Panel *p, int buttons){ + USED(p, buttons); + s=plgetscroll(list); + message("save %d %d %d %d", s); +} +void revert(Panel *p, int buttons){ + USED(p, buttons); + plsetscroll(list, s, &screen); + message("revert %d %d %d %d", s); +} +void main(void){ + Panel *g; + binit(0,0,0); + einit(Emouse); + plinit(screen.ldepth); + root=plgroup(0, 0); + g=plgroup(root, PACKN|EXPAND); + list=pllist(g, PACKE|EXPAND, genlist, 8, hitgen); + plscroll(list, 0, plscrollbar(g, PACKW)); + msg=pllabel(root, PACKN|FILLX, ""); + plbutton(root, PACKW, "save", save); + plbutton(root, PACKW, "revert", revert); + plbutton(root, PACKE, "done", done); + ereshaped(screen.r); + for(;;) plmouse(root, emouse(), &screen); +} diff --git a/libpanel/scroll.c b/libpanel/scroll.c new file mode 100644 index 000000000..2c8489e5b --- /dev/null +++ b/libpanel/scroll.c @@ -0,0 +1,21 @@ +#include +#include +#include +#include +#include +#include "pldefs.h" +void plscroll(Panel *scrollee, Panel *xscroller, Panel *yscroller){ + scrollee->xscroller=xscroller; + scrollee->yscroller=yscroller; + if(xscroller) xscroller->scrollee=scrollee; + if(yscroller) yscroller->scrollee=scrollee; +} +Scroll plgetscroll(Panel *p){ + return p->scr; +} +void plsetscroll(Panel *p, Scroll s){ + if(p->scroll){ + if(s.size.x) p->scroll(p, HORIZ, 2, s.pos.x, s.size.x); + if(s.size.y) p->scroll(p, VERT, 2, s.pos.y, s.size.y); + } +} diff --git a/libpanel/scrollbar.c b/libpanel/scrollbar.c new file mode 100644 index 000000000..d538f6879 --- /dev/null +++ b/libpanel/scrollbar.c @@ -0,0 +1,146 @@ +#include +#include +#include +#include +#include +#include "pldefs.h" +typedef struct Scrollbar Scrollbar; +struct Scrollbar{ + int dir; /* HORIZ or VERT */ + int lo, hi; /* setting, in screen coordinates */ + int buttons; /* saved mouse buttons for transmittal to scrollee */ + Rectangle interior; + Point minsize; +}; +#define SBWID 8 /* should come from draw.c? */ +void pl_drawscrollbar(Panel *p){ + Scrollbar *sp; + sp=p->data; + sp->interior=pl_outline(p->b, p->r, p->state); + pl_sliderupd(p->b, sp->interior, sp->dir, sp->lo, sp->hi); +} +int pl_hitscrollbar(Panel *g, Mouse *m){ + int oldstate, pos, len, dy; + Point ul, size; + Scrollbar *sp; + sp=g->data; + ul=g->r.min; + size=subpt(g->r.max, g->r.min); + pl_interior(g->state, &ul, &size); + oldstate=g->state; + if(!(g->flags & USERFL) && (m->buttons&OUT || !ptinrect(m->xy, g->r))){ + m->buttons&=~OUT; + g->state=UP; + goto out; + } + if(sp->dir==HORIZ){ + pos=m->xy.x-ul.x; + len=size.x; + } + else{ + pos=m->xy.y-ul.y; + len=size.y; + } + if(pos<0) pos=0; + else if(pos>len) pos=len; + if(m->buttons&7){ + g->state=DOWN; + sp->buttons=m->buttons; + switch(m->buttons){ + case 1: + dy=pos*(sp->hi-sp->lo)/len; + pl_sliderupd(g->b, sp->interior, sp->dir, sp->lo-dy, + sp->hi-dy); + break; + case 2: + if(g->scrollee && g->scrollee->scroll) + g->scrollee->scroll(g->scrollee, sp->dir, + m->buttons, pos, len); + break; + case 4: + dy=pos*(sp->hi-sp->lo)/len; + pl_sliderupd(g->b, sp->interior, sp->dir, sp->lo+dy, + sp->hi+dy); + break; + } + } + else{ + if(!(sp->buttons&2) && g->state==DOWN && g->scrollee && g->scrollee->scroll) + g->scrollee->scroll(g->scrollee, sp->dir, sp->buttons, + pos, len); + g->state=UP; + } +out: + if(oldstate!=g->state) pldraw(g, g->b); + return g->state==DOWN; +} +void pl_typescrollbar(Panel *p, Rune c){ + USED(p, c); +} +Point pl_getsizescrollbar(Panel *p, Point children){ + USED(children); + return pl_boxsize(((Scrollbar *)p->data)->minsize, p->state); +} +void pl_childspacescrollbar(Panel *p, Point *ul, Point *size){ + USED(p, ul, size); +} +/* + * Arguments lo, hi and len are in the scrollee's natural coordinates + */ +void pl_setscrollbarscrollbar(Panel *p, int lo, int hi, int len){ + Point ul, size; + int mylen; + Scrollbar *sp; + sp=p->data; + ul=p->r.min; + size=subpt(p->r.max, p->r.min); + pl_interior(p->state, &ul, &size); + mylen=sp->dir==HORIZ?size.x:size.y; + if(len==0) len=1; + sp->lo=lo*mylen/len; + sp->hi=hi*mylen/len; + if(sp->lo<0) sp->lo=0; + if(sp->lo>=mylen) sp->hi=mylen-1; + if(sp->hi<=sp->lo) sp->hi=sp->lo+1; + if(sp->hi>mylen) sp->hi=mylen; + pldraw(p, p->b); +} +int pl_priscrollbar(Panel *, Point){ + return PRI_SCROLLBAR; +} +void plinitscrollbar(Panel *v, int flags){ + Scrollbar *sp; + sp=v->data; + v->flags=flags|LEAF; + v->pri=pl_priscrollbar; + v->state=UP; + v->draw=pl_drawscrollbar; + v->hit=pl_hitscrollbar; + v->type=pl_typescrollbar; + v->getsize=pl_getsizescrollbar; + v->childspace=pl_childspacescrollbar; + v->setscrollbar=pl_setscrollbarscrollbar; + switch(flags&PACK){ + case PACKN: + case PACKS: + sp->dir=HORIZ; + sp->minsize=Pt(0, SBWID); + v->flags|=FILLX; + break; + case PACKE: + case PACKW: + sp->dir=VERT; + sp->minsize=Pt(SBWID, 0); + v->flags|=FILLY; + break; + } + sp->lo=0; + sp->hi=0; + v->kind="scrollbar"; +} +Panel *plscrollbar(Panel *parent, int flags){ + Panel *v; + v=pl_newpanel(parent, sizeof(Scrollbar)); + plinitscrollbar(v, flags); + return v; +} diff --git a/libpanel/slider.c b/libpanel/slider.c new file mode 100644 index 000000000..150a7feff --- /dev/null +++ b/libpanel/slider.c @@ -0,0 +1,97 @@ +#include +#include +#include +#include +#include +#include "pldefs.h" +typedef struct Slider Slider; +struct Slider{ + int dir; /* HORIZ or VERT */ + int val; /* setting, in screen coordinates */ + Point minsize; + void (*hit)(Panel *, int, int, int); /* call back to user when slider changes */ + int buttons; +}; +void pl_drawslider(Panel *p){ + Rectangle r; + Slider *sp; + sp=p->data; + r=pl_box(p->b, p->r, UP); + switch(sp->dir){ + case HORIZ: pl_sliderupd(p->b, r, sp->dir, 0, sp->val); break; + case VERT: pl_sliderupd(p->b, r, sp->dir, r.max.y-sp->val, r.max.y); break; + } +} +int pl_hitslider(Panel *p, Mouse *m){ + int oldstate, oldval, len; + Point ul, size; + Slider *sp; + sp=p->data; + ul=p->r.min; + size=subpt(p->r.max, p->r.min); + pl_interior(p->state, &ul, &size); + oldstate=p->state; + oldval=sp->val; + SET(len); + if(m->buttons&OUT) + p->state=UP; + else if(m->buttons&7){ + p->state=DOWN; + sp->buttons=m->buttons; + if(sp->dir==HORIZ){ + sp->val=m->xy.x-ul.x; + len=size.x; + } + else{ + sp->val=ul.y+size.y-m->xy.y; + len=size.y; + } + if(sp->val<0) sp->val=0; + else if(sp->val>len) sp->val=len; + } + else /* mouse inside, but no buttons down */ + p->state=UP; + if(oldval!=sp->val || oldstate!=p->state) pldraw(p, p->b); + if(oldval!=sp->val && sp->hit) sp->hit(p, sp->buttons, sp->val, len); + return 0; +} +void pl_typeslider(Panel *p, Rune c){ + USED(p, c); +} +Point pl_getsizeslider(Panel *p, Point children){ + USED(children); + return pl_boxsize(((Slider *)p->data)->minsize, p->state); +} +void pl_childspaceslider(Panel *g, Point *ul, Point *size){ + USED(g, ul, size); +} +void plinitslider(Panel *v, int flags, Point size, void (*hit)(Panel *, int, int, int)){ + Slider *sp; + sp=v->data; + v->r=Rect(0,0,size.x,size.y); + v->flags=flags|LEAF; + v->state=UP; + v->draw=pl_drawslider; + v->hit=pl_hitslider; + v->type=pl_typeslider; + v->getsize=pl_getsizeslider; + v->childspace=pl_childspaceslider; + sp->minsize=size; + sp->dir=size.x>size.y?HORIZ:VERT; + sp->hit=hit; + v->kind="slider"; +} +Panel *plslider(Panel *parent, int flags, Point size, void (*hit)(Panel *, int, int, int)){ + Panel *p; + p=pl_newpanel(parent, sizeof(Slider)); + plinitslider(p, flags, size, hit); + return p; +} +void plsetslider(Panel *p, int value, int range){ + Slider *sp; + sp=p->data; + if(value<0) value=0; + else if(value>range) value=range; + if(sp->dir==HORIZ) sp->val=value*(p->r.max.x-p->r.min.x)/range; + else sp->val=value*(p->r.max.y-p->r.min.y)/range; +} diff --git a/libpanel/snarf.c b/libpanel/snarf.c new file mode 100644 index 000000000..b11cb36f7 --- /dev/null +++ b/libpanel/snarf.c @@ -0,0 +1,58 @@ +#include +#include +#include +#include +#include +#include "pldefs.h" + +void plputsnarf(char *s){ + int fd; + + if(s==0 || *s=='\0') + return; + if((fd=open("/dev/snarf", OWRITE|OTRUNC))>=0){ + write(fd, s, strlen(s)); + close(fd); + } +} +char *plgetsnarf(void){ + int fd, n, r; + char *s; + + if((fd=open("/dev/snarf", OREAD))<0) + return nil; + n=0; + s=nil; + for(;;){ + s=pl_erealloc(s, n+1024); + if((r = read(fd, s+n, 1024)) <= 0) + break; + n += r; + } + close(fd); + if(n <= 0){ + free(s); + return nil; + } + s[n] = '\0'; + return s; +} +void plsnarf(Panel *p){ + char *s; + + if(p==0 || p->snarf==0) + return; + s=p->snarf(p); + plputsnarf(s); + free(s); +} +void plpaste(Panel *p){ + char *s; + + if(p==0 || p->paste==0) + return; + if(s=plgetsnarf()){ + p->paste(p, s); + free(s); + } +} diff --git a/libpanel/textview.c b/libpanel/textview.c new file mode 100644 index 000000000..6d561660e --- /dev/null +++ b/libpanel/textview.c @@ -0,0 +1,250 @@ +/* + * Fonted text viewer, calls out to code in rtext.c + * + * Should redo this to copy the already-visible parts on scrolling & only + * update the newly appearing stuff -- then the offscreen assembly bitmap can go away. + */ +#include +#include +#include +#include +#include +#include "pldefs.h" + +typedef struct Textview Textview; +struct Textview{ + void (*hit)(Panel *, int, Rtext *); /* call back to user on hit */ + Rtext *text; /* text */ + Point offs; /* offset of left/top of screen */ + Rtext *hitword; /* text to hilite */ + Rtext *hitfirst; /* first word in range select */ + int twid; /* text width (visible) */ + int thgt; /* text height (total) */ + int maxwid; /* width of longest line */ + Point minsize; /* smallest acceptible window size */ + int buttons; +}; + +void pl_setscrpos(Panel *p, Textview *tp, Rectangle r){ + Panel *sb; + int lo, hi; + + lo=tp->offs.y; + hi=lo+r.max.y-r.min.y; /* wrong? */ + sb=p->yscroller; + if(sb && sb->setscrollbar) + sb->setscrollbar(sb, lo, hi, tp->thgt); + lo=tp->offs.x; + hi=lo+r.max.x-r.min.x; + sb=p->xscroller; + if(sb && sb->setscrollbar) + sb->setscrollbar(sb, lo, hi, tp->maxwid); +} +void pl_drawtextview(Panel *p){ + int twid; + Rectangle r; + Textview *tp; + Point size; + + tp=p->data; + r=pl_outline(p->b, p->r, UP); + twid=r.max.x-r.min.x; + if(twid!=tp->twid){ + tp->twid=twid; + size=pl_rtfmt(tp->text, tp->twid); + p->scr.size.x=tp->maxwid=size.x; + p->scr.size.y=tp->thgt=size.y; + } + p->scr.pos = tp->offs; + pl_rtdraw(p->b, r, tp->text, tp->offs); + pl_setscrpos(p, tp, r); +} +/* + * If t is a panel word, pass the mouse event on to it + */ +void pl_passon(Rtext *t, Mouse *m){ + if(t && t->b==0 && t->p!=0) + plmouse(t->p, m); +} +int pl_hittextview(Panel *p, Mouse *m){ + Rtext *oldhitword, *oldhitfirst; + int hitme, oldstate; + Point ul, size; + Textview *tp; + + tp=p->data; + hitme=0; + oldstate=p->state; + oldhitword=tp->hitword; + oldhitfirst=tp->hitfirst; + if(oldhitword==oldhitfirst) + pl_passon(oldhitword, m); + if(m->buttons&OUT) + p->state=UP; + else if(m->buttons&7){ + p->state=DOWN; + tp->buttons=m->buttons; + if(oldhitword==0 || oldhitword->p==0 || (oldhitword->p->flags&REMOUSE)==0){ + ul=p->r.min; + size=subpt(p->r.max, p->r.min); + pl_interior(p->state, &ul, &size); + tp->hitword=pl_rthit(tp->text, tp->offs, m->xy, ul); + if(tp->hitword==0) + if(oldhitword!=0 && oldstate==DOWN) + tp->hitword=oldhitword; + else + tp->hitfirst=0; + if(tp->hitword!=0 && oldstate!=DOWN) + tp->hitfirst=tp->hitword; + } + } + else{ + if(p->state==DOWN) hitme=1; + p->state=UP; + } + if(tp->hitfirst!=oldhitfirst || tp->hitword!=oldhitword){ + plrtseltext(tp->text, tp->hitword, tp->hitfirst); + pl_drawtextview(p); + if(tp->hitword==tp->hitfirst) + pl_passon(tp->hitword, m); + } + if(hitme && tp->hit && tp->hitword!=0 && tp->hitword==tp->hitfirst){ + plrtseltext(tp->text, 0, 0); + pl_drawtextview(p); + tp->hit(p, tp->buttons, tp->hitword); + tp->hitword=0; + tp->hitfirst=0; + } + return 0; +} +void pl_scrolltextview(Panel *p, int dir, int buttons, int num, int den){ + int xoffs, yoffs; + Point ul, size; + Textview *tp; + Rectangle r; + + tp=p->data; + ul=p->r.min; + size=subpt(p->r.max, p->r.min); + pl_interior(p->state, &ul, &size); + if(dir==VERT){ + switch(buttons){ + default: + SET(yoffs); + break; + case 1: /* left -- top moves to pointer */ + yoffs=(vlong)tp->offs.y-num*size.y/den; + if(yoffs<0) yoffs=0; + break; + case 2: /* middle -- absolute index of file */ + yoffs=(vlong)tp->thgt*num/den; + break; + case 4: /* right -- line pointed at moves to top */ + yoffs=tp->offs.y+(vlong)num*size.y/den; + if(yoffs>tp->thgt) yoffs=tp->thgt; + break; + } + if(yoffs!=tp->offs.y){ + r=pl_outline(p->b, p->r, p->state); + pl_rtredraw(p->b, r, tp->text, + Pt(tp->offs.x, yoffs), tp->offs, dir); + p->scr.pos.y=tp->offs.y=yoffs; + pl_setscrpos(p, tp, r); + } + }else{ /* dir==HORIZ */ + switch(buttons){ + default: + SET(xoffs); + break; + case 1: /* left */ + xoffs=(vlong)tp->offs.x-num*size.x/den; + if(xoffs<0) xoffs=0; + break; + case 2: /* middle */ + xoffs=(vlong)tp->maxwid*num/den; + break; + case 4: /* right */ + xoffs=tp->offs.x+(vlong)num*size.x/den; + if(xoffs>tp->maxwid) xoffs=tp->maxwid; + break; + } + if(xoffs!=tp->offs.x){ + r=pl_outline(p->b, p->r, p->state); + pl_rtredraw(p->b, r, tp->text, + Pt(xoffs, tp->offs.y), tp->offs, dir); + p->scr.pos.x=tp->offs.x=xoffs; + pl_setscrpos(p, tp, r); + } + } +} +void pl_typetextview(Panel *g, Rune c){ + USED(g, c); +} +Point pl_getsizetextview(Panel *p, Point children){ + USED(children); + return pl_boxsize(((Textview *)p->data)->minsize, p->state); +} +void pl_childspacetextview(Panel *g, Point *ul, Point *size){ + USED(g, ul, size); +} +/* + * Priority depends on what thing inside the panel we're pointing at. + */ +int pl_pritextview(Panel *p, Point xy){ + Point ul, size; + Textview *tp; + Rtext *h; + tp=p->data; + ul=p->r.min; + size=subpt(p->r.max, p->r.min); + pl_interior(p->state, &ul, &size); + h=pl_rthit(tp->text, tp->offs, xy, ul); + if(h && h->b==0 && h->p!=0){ + p=pl_ptinpanel(xy, h->p); + if(p) return p->pri(p, xy); + } + return PRI_NORMAL; +} + +char* pl_snarftextview(Panel *p){ + return plrtsnarftext(((Textview *)p->data)->text); +} + +void plinittextview(Panel *v, int flags, Point minsize, Rtext *t, void (*hit)(Panel *, int, Rtext *)){ + Textview *tp; + tp=v->data; + v->flags=flags|LEAF; + v->state=UP; + v->draw=pl_drawtextview; + v->hit=pl_hittextview; + v->type=pl_typetextview; + v->getsize=pl_getsizetextview; + v->childspace=pl_childspacetextview; + v->kind="textview"; + v->pri=pl_pritextview; + tp->hit=hit; + tp->minsize=minsize; + tp->text=t; + tp->offs=ZP; + tp->hitfirst=0; + tp->hitword=0; + v->scroll=pl_scrolltextview; + v->snarf=pl_snarftextview; + tp->twid=-1; + tp->maxwid=0; + v->scr.pos=Pt(0,0); + v->scr.size=Pt(0,1); +} +Panel *pltextview(Panel *parent, int flags, Point minsize, Rtext *t, void (*hit)(Panel *, int, Rtext *)){ + Panel *v; + v=pl_newpanel(parent, sizeof(Textview)); + plinittextview(v, flags, minsize, t, hit); + return v; +} +int plgetpostextview(Panel *p){ + return ((Textview *)p->data)->offs.y; +} +void plsetpostextview(Panel *p, int yoffs){ + ((Textview *)p->data)->offs.y=yoffs; + pldraw(p, p->b); +} diff --git a/libpanel/textwin.c b/libpanel/textwin.c new file mode 100644 index 000000000..cf49b1e46 --- /dev/null +++ b/libpanel/textwin.c @@ -0,0 +1,474 @@ +/* + * Text windows + * void twhilite(Textwin *t, int sel0, int sel1, int on) + * hilite (on=1) or unhilite (on=0) a range of characters + * void twselect(Textwin *t, Mouse *m) + * set t->sel0, t->sel1 from mouse input. + * Also hilites selection. + * Caller should first unhilite previous selection. + * void twreplace(Textwin *t, int r0, int r1, Rune *ins, int nins) + * Replace the given range of characters with the given insertion. + * Caller should unhilite selection while this is called. + * void twscroll(Textwin *t, int top) + * Character with index top moves to the top line of the screen. + * int twpt2rune(Textwin *t, Point p) + * which character is displayed at point p? + * void twreshape(Textwin *t, Rectangle r) + * save r and redraw the text + * Textwin *twnew(Bitmap *b, Font *f, Rune *text, int ntext) + * create a new text window + * void twfree(Textwin *t) + * get rid of a surplus Textwin + */ +#include +#include +#include +#include +#include +#include "pldefs.h" + +#define SLACK 100 + +/* + * Is text at point a before or after that at point b? + */ +int tw_before(Textwin *t, Point a, Point b){ + return a.yhgt && a.xloc+(t->bot-t->top); + for(lp=t->loc;lp!=el;lp++) + if(tw_before(t, p, *lp)){ + if(lp==t->loc) return t->top; + return lp-t->loc+t->top-1; + } + return t->bot; +} +/* + * Return ul corner of the character with the given index + */ +Point tw_rune2pt(Textwin *t, int i){ + if(itop) return t->r.min; + if(i>t->bot) return t->r.max; + return t->loc[i-t->top]; +} +/* + * Store p at t->loc[l], extending t->loc if necessary + */ +void tw_storeloc(Textwin *t, int l, Point p){ + int nloc; + if(l>=t->eloc-t->loc){ + nloc=l+SLACK; + t->loc=pl_erealloc(t->loc, nloc*sizeof(Point)); + t->eloc=t->loc+nloc; + } + t->loc[l]=p; +} +/* + * Set the locations at which the given runes should appear. + * Returns the index of the first rune not set, which might not + * be last because we reached the bottom of the window. + * + * N.B. this zaps the loc of r[last], so that value should be saved first, + * if it's important. + */ +int tw_setloc(Textwin *t, int first, int last, Point ul){ + Rune *r, *er; + int x, dt, lp; + char buf[UTFmax+1]; + er=t->text+last; + for(r=t->text+first,lp=first-t->top;r!=er && ul.y+t->hgt<=t->r.max.y;r++,lp++){ + tw_storeloc(t, lp, ul); + switch(*r){ + case '\n': + ul.x=t->r.min.x; + ul.y+=t->hgt; + break; + case '\t': + x=ul.x-t->r.min.x+t->mintab+t->tabstop; + x-=x%t->tabstop; + ul.x=x+t->r.min.x; + if(ul.x>t->r.max.x){ + ul.x=t->r.min.x; + ul.y+=t->hgt; + tw_storeloc(t, lp, ul); + if(ul.y+t->hgt>t->r.max.y) return r-t->text; + ul.x+=+t->tabstop; + } + break; + default: + buf[runetochar(buf, r)]='\0'; + dt=stringwidth(t->font, buf); + ul.x+=dt; + if(ul.x>t->r.max.x){ + ul.x=t->r.min.x; + ul.y+=t->hgt; + tw_storeloc(t, lp, ul); + if(ul.y+t->hgt>t->r.max.y) return r-t->text; + ul.x+=dt; + } + break; + } + } + tw_storeloc(t, lp, ul); + return r-t->text; +} +/* + * Draw the given runes at their locations. + * Bug -- saving up multiple characters would + * reduce the number of calls to string, + * and probably make this a lot faster. + */ +void tw_draw(Textwin *t, int first, int last){ + Rune *r, *er; + Point *lp, ul, ur; + char buf[UTFmax+1]; + if(firsttop) first=t->top; + if(last>t->bot) last=t->bot; + if(last<=first) return; + er=t->text+last; + for(r=t->text+first,lp=t->loc+(first-t->top);r!=er;r++,lp++){ + if(lp->y+t->hgt>t->r.max.y){ + fprint(2, "chr %C, index %ld of %d, loc %d %d, off bottom\n", + *r, lp-t->loc, t->bot-t->top, lp->x, lp->y); + return; + } + switch(*r){ + case '\n': + ur=*lp; + break; + case '\t': + ur=*lp; + if(lp[1].y!=lp[0].y) + ul=Pt(t->r.min.x, lp[1].y); + else + ul=*lp; + pl_clr(t->b, Rpt(ul, Pt(lp[1].x, ul.y+t->hgt))); + break; + default: + buf[runetochar(buf, r)]='\0'; + /***/ pl_clr(t->b, Rpt(*lp, addpt(*lp, stringsize(t->font, buf)))); + ur=string(t->b, *lp, display->black, ZP, t->font, buf); + break; + } + if(lp[1].y!=lp[0].y) + /***/ pl_clr(t->b, Rpt(ur, Pt(t->r.max.x, ur.y+t->hgt))); + } +} +/* + * Hilight the characters with tops between ul and ur + */ +void tw_hilitep(Textwin *t, Point ul, Point ur){ + Point swap; + int y; + if(tw_before(t, ur, ul)){ swap=ul; ul=ur; ur=swap;} + y=ul.y+t->hgt; + if(y>t->r.max.y) y=t->r.max.y; + if(ul.y==ur.y) + pl_highlight(t->b, Rpt(ul, Pt(ur.x, y))); + else{ + pl_highlight(t->b, Rpt(ul, Pt(t->r.max.x, y))); + ul=Pt(t->r.min.x, y); + pl_highlight(t->b, Rpt(ul, Pt(t->r.max.x, ur.y))); + ul=Pt(t->r.min.x, ur.y); + y=ur.y+t->hgt; + if(y>t->r.max.y) y=t->r.max.y; + pl_highlight(t->b, Rpt(ul, Pt(ur.x, y))); + } +} +/* + * Hilite/unhilite the given range of characters + */ +void twhilite(Textwin *t, int sel0, int sel1, int on){ + Point ul, ur; + int swap, y; + if(sel1top || t->bottop) sel0=t->top; + if(sel1>t->bot) sel1=t->bot; + if(!on){ + if(sel1==sel0){ + ul=t->loc[sel0-t->top]; + y=ul.y+t->hgt; + if(y>t->r.max.y) y=t->r.max.y; + pl_clr(t->b, Rpt(ul, Pt(ul.x+1, y))); + }else + tw_draw(t, sel0, sel1); + return; + } + ul=t->loc[sel0-t->top]; + if(sel1==sel0) + ur=addpt(ul, Pt(1, 0)); + else + ur=t->loc[sel1-t->top]; + tw_hilitep(t, ul, ur); +} +/* + * Set t->sel[01] from mouse input. + * Also hilites the selection. + * Caller should unhilite the previous + * selection before calling this. + */ +void twselect(Textwin *t, Mouse *m){ + int sel0, sel1, newsel; + Point p0, p1, newp; + sel0=sel1=twpt2rune(t, m->xy); + p0=tw_rune2pt(t, sel0); + p1=addpt(p0, Pt(1, 0)); + twhilite(t, sel0, sel1, 1); + for(;;){ + if(display->bufp > display->buf) + flushimage(display, 1); + *m=emouse(); + if((m->buttons&7)!=1) break; + newsel=twpt2rune(t, m->xy); + newp=tw_rune2pt(t, newsel); + if(eqpt(newp, p0)) newp=addpt(newp, Pt(1, 0)); + if(!eqpt(newp, p1)){ + if((sel0<=sel1 && sel1sel0=sel0; + t->sel1=sel1; + } + else{ + t->sel0=sel1; + t->sel1=sel0; + } +} +/* + * Clear the area following the last displayed character + */ +void tw_clrend(Textwin *t){ + Point ul; + int y; + ul=t->loc[t->bot-t->top]; + y=ul.y+t->hgt; + if(y>t->r.max.y) y=t->r.max.y; + pl_clr(t->b, Rpt(ul, Pt(t->r.max.x, y))); + ul=Pt(t->r.min.x, y); + pl_clr(t->b, Rpt(ul, t->r.max)); +} +/* + * Move part of a line of text, truncating the source or padding + * the destination on the right if necessary. + */ +void tw_moverect(Textwin *t, Point uld, Point urd, Point uls, Point urs){ + int sw, dw, d; + if(urs.y!=uls.y) urs=Pt(t->r.max.x, uls.y); + if(urd.y!=uld.y) urd=Pt(t->r.max.x, uld.y); + sw=uls.x-urs.x; + dw=uld.x-urd.x; + if(dw>sw){ + d=dw-sw; + pl_clr(t->b, Rect(urd.x-d, urd.y, urd.x, urd.y+t->hgt)); + dw=sw; + } + pl_cpy(t->b, uld, Rpt(uls, Pt(uls.x+dw, uls.y+t->hgt))); +} +/* + * Move a block of characters up or to the left: + * Identify contiguous runs of characters whose width doesn't change, and + * move them in one bitblt per run. + * If we get to a point where source and destination are x-aligned, + * they will remain x-aligned for the rest of the block. + * Then, if they are y-aligned, they're already in the right place. + * Otherwise, we can move them in three bitblts; one if all the + * remaining characters are on one line. + */ +void tw_moveup(Textwin *t, Point *dp, Point *sp, Point *esp){ + Point uld, uls; /* upper left of destination/source */ + int y; + while(sp!=esp && sp->x!=dp->x){ + uld=*dp; + uls=*sp; + while(sp!=esp && sp->y==uls.y && dp->y==uld.y && sp->x-uls.x==dp->x-uld.x){ + sp++; + dp++; + } + tw_moverect(t, uld, *dp, uls, *sp); + } + if(sp==esp || esp->y==dp->y) return; + if(esp->y==sp->y){ /* one line only */ + pl_cpy(t->b, *dp, Rpt(*sp, Pt(esp->x, sp->y+t->hgt))); + return; + } + y=sp->y+t->hgt; + pl_cpy(t->b, *dp, Rpt(*sp, Pt(t->r.max.x, y))); + pl_cpy(t->b, Pt(t->r.min.x, dp->y+t->hgt), + Rect(t->r.min.x, y, t->r.max.x, esp->y)); + y=dp->y+esp->y-sp->y; + pl_cpy(t->b, Pt(t->r.min.x, y), + Rect(t->r.min.x, esp->y, esp->x, esp->y+t->hgt)); +} +/* + * Same as above, but moving down and in reverse order, so as not to overwrite stuff + * not moved yet. + */ +void tw_movedn(Textwin *t, Point *dp, Point *bsp, Point *esp){ + Point *sp, urs, urd; + int dy; + dp+=esp-bsp; + sp=esp; + dy=dp->y-sp->y; + while(sp!=bsp && dp[-1].x==sp[-1].x){ + --dp; + --sp; + } + if(dy!=0){ + if(sp->y==esp->y) + pl_cpy(t->b, *dp, Rect(sp->x, sp->y, esp->x, esp->y+t->hgt)); + else{ + pl_cpy(t->b, Pt(t->r.min.x, sp->x+dy), + Rect(t->r.min.x, sp->y, esp->x, esp->y+t->hgt)); + pl_cpy(t->b, Pt(t->r.min.x, dp->y+t->hgt), + Rect(t->r.min.x, sp->y+t->hgt, t->r.max.x, esp->y)); + pl_cpy(t->b, *dp, + Rect(sp->x, sp->y, t->r.max.x, sp->y+t->hgt)); + } + } + while(sp!=bsp){ + urd=*dp; + urs=*sp; + while(sp!=bsp && sp[-1].y==sp[0].y && dp[-1].y==dp[0].y + && sp[-1].x-sp[0].x==dp[-1].x-dp[0].x){ + --sp; + --dp; + } + tw_moverect(t, *dp, urd, *sp, urs); + } +} +/* + * Move the given range of characters, already drawn on + * the given textwin, to the given location. + * Start and end must both index characters that are initially on-screen. + */ +void tw_relocate(Textwin *t, int first, int last, Point dst){ + Point *srcloc; + int nbyte; + if(firsttop || lastbotloc[first-t->top], nbyte); + tw_setloc(t, first, last, dst); + if(tw_before(t, dst, srcloc[0])) + tw_moveup(t, t->loc+first-t->top, srcloc, srcloc+(last-first)); + else + tw_movedn(t, t->loc+first-t->top, srcloc, srcloc+(last-first)); +} +/* + * Replace the runes with indices from r0 to r1-1 with the text + * pointed to by text, and with length ntext. + * Open up a hole in t->text, t->loc. + * Insert new text, calculate their locs (save the extra loc that's overwritten first) + * (swap saved & overwritten locs) + * move tail. + * calc locs and draw new text after tail, if necessary. + * draw new text, if necessary + */ +void twreplace(Textwin *t, int r0, int r1, Rune *ins, int nins){ + int olen, nlen, tlen, dtop; + olen=t->etext-t->text; + nlen=olen+nins-(r1-r0); + tlen=t->eslack-t->text; + if(nlen>tlen){ + tlen=nlen+SLACK; + t->text=pl_erealloc(t->text, tlen*sizeof(Rune)); + t->eslack=t->text+tlen; + } + if(olen!=nlen) + memmove(t->text+r0+nins, t->text+r1, (olen-r1)*sizeof(Rune)); + if(nins!=0) /* ins can be 0 if nins==0 */ + memmove(t->text+r0, ins, nins*sizeof(Rune)); + t->etext=t->text+nlen; + if(r0>t->bot) /* insertion is completely below visible text */ + return; + if(r1top){ /* insertion is completely above visible text */ + dtop=nlen-olen; + t->top+=dtop; + t->bot+=dtop; + return; + } + if(1 || t->bot<=r0+nins){ /* no useful text on screen below r0 */ + if(r0<=t->top) /* no useful text above, either */ + t->top=r0; + t->bot=tw_setloc(t, r0, nlen, t->loc[r0-t->top]); + tw_draw(t, r0, t->bot); + tw_clrend(t); + return; + } + /* + * code for case where there is useful text below is missing (see `1 ||' above) + */ +} +/* + * This works but is stupid. + */ +void twscroll(Textwin *t, int top){ + while(top!=0 && t->text[top-1]!='\n') --top; + t->top=top; + t->bot=tw_setloc(t, top, t->etext-t->text, t->r.min); + tw_draw(t, t->top, t->bot); + tw_clrend(t); +} +void twreshape(Textwin *t, Rectangle r){ + t->r=r; + t->bot=tw_setloc(t, t->top, t->etext-t->text, t->r.min); + tw_draw(t, t->top, t->bot); + tw_clrend(t); +} +Textwin *twnew(Image *b, Font *f, Rune *text, int ntext){ + Textwin *t; + t=pl_emalloc(sizeof(Textwin)); + t->text=pl_emalloc((ntext+SLACK)*sizeof(Rune)); + t->loc=pl_emalloc(SLACK*sizeof(Point)); + t->eloc=t->loc+SLACK; + t->etext=t->text+ntext; + t->eslack=t->etext+SLACK; + if(ntext) memmove(t->text, text, ntext*sizeof(Rune)); + t->top=0; + t->bot=0; + t->sel0=0; + t->sel1=0; + t->b=b; + t->font=f; + t->hgt=f->height; + t->mintab=stringwidth(f, "0"); + t->tabstop=8*t->mintab; + return t; +} +void twfree(Textwin *t){ + free(t->loc); + free(t->text); + free(t); +} +/* + * Correct the character locations in a textwin after the panel is moved. + * This horrid hack would not be necessary if loc values were relative + * to the panel, rather than absolute. + */ +void twmove(Textwin *t, Point d){ + Point *lp; + t->r = rectaddpt(t->r, d); + for(lp=t->loc; lpeloc; lp++) + *lp = addpt(*lp, d); +} diff --git a/libpanel/utf.c b/libpanel/utf.c new file mode 100644 index 000000000..30cb221cb --- /dev/null +++ b/libpanel/utf.c @@ -0,0 +1,30 @@ +#include +#include +#include +#include +#include +#include "pldefs.h" +/* + * This is the same definition that 8½ uses + */ +int pl_idchar(int c){ + if(c<=' ' + || 0x7F<=c && c<=0xA0 + || utfrune("!\"#$%&'()*+,-./:;<=>?@`[\\]^{|}~", c)) + return 0; + return 1; +} +int pl_rune1st(int c){ + return (c&0xc0)!=0x80; +} +char *pl_nextrune(char *s){ + do s++; while(!pl_rune1st(*s)); + return s; +} +int pl_runewidth(Font *f, char *s){ + char r[4], *t; + t=r; + do *t++=*s++; while(!pl_rune1st(*s)); + *t='\0'; + return stringwidth(f, r); +} diff --git a/mkfile b/mkfile new file mode 100644 index 000000000..014bcf78b --- /dev/null +++ b/mkfile @@ -0,0 +1,19 @@ +