ref: 2470b26dd84fc9b5e0021b7ba84f8439e5c97e23
author: glenda <glenda@9front.local>
date: Sat May 7 21:55:03 EDT 2022
initial import.
--- /dev/null
+++ b/forms.c
@@ -1,0 +1,764 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#include "rtext.h"
+#include "mothra.h"
+#include "html.h"
+
+typedef struct Field Field;
+typedef struct Option Option;
+struct Form{
+ int method;
+ char *ctype;
+ char *action;
+ Field *fields, *efields;
+ Form *next;
+};
+struct Field{
+ Field *next;
+ Form *form;
+ char *name;
+ char *value;
+ int checked;
+ int size; /* should be a point, but that feature is deprecated */
+ int maxlength;
+ int type;
+ int rows, cols;
+ Option *options;
+ int multiple;
+ int state; /* is the button marked? */
+ Panel *p;
+ Panel *pulldown;
+ Panel *textwin;
+};
+/*
+ * Field types
+ */
+enum{
+ TYPEIN=1,
+ CHECK,
+ PASSWD,
+ RADIO,
+ SUBMIT,
+ RESET,
+ BUTTON,
+ SELECT,
+ TEXTWIN,
+ HIDDEN,
+ INDEX,
+ FILE,
+};
+struct Option{
+ int selected;
+ int def;
+ char label[NLABEL+1];
+ char *value;
+ Option *next;
+};
+
+#define BOUNDARY "nAboJ9uN6ZXsqoVGzLAdjKq97TWDTGjo"
+
+void h_checkinput(Panel *, int, int);
+void h_radioinput(Panel *, int, int);
+void h_submitinput(Panel *, int);
+void h_buttoninput(Panel *, int);
+void h_fileinput(Panel *, int);
+void h_submittype(Panel *, char *);
+void h_submitindex(Panel *, char *);
+void h_resetinput(Panel *, int);
+void h_select(Panel *, int, int);
+char *selgen(Panel *, int);
+char *nullgen(Panel *, int);
+Field *newfield(Form *form){
+ Field *f;
+ f=emalloc(sizeof(Field));
+ if(form->efields==0)
+ form->fields=f;
+ else
+ form->efields->next=f;
+ form->efields=f;
+ f->next=0;
+ f->form=form;
+ return f;
+}
+/*
+ * Called by rdhtml on seeing a forms-related tag
+ */
+void rdform(Hglob *g){
+ char *s;
+ Field *f;
+ Option *o, **op;
+ Form *form;
+ switch(g->tag){
+ default:
+ fprint(2, "Bad tag <%s> in rdform (Can't happen!)\n", g->token);
+ return;
+ case Tag_form:
+ if(g->form){
+ htmlerror(g->name, g->lineno, "nested forms illegal\n");
+ break;
+ }
+ g->form=emalloc(sizeof(Form));
+ s=pl_getattr(g->attr, "action");
+ g->form->action=strdup((s && *s) ? s : g->dst->url->fullname);
+ s=pl_getattr(g->attr, "method");
+ if(s==0 || *s==0)
+ g->form->method=GET;
+ else if(cistrcmp(s, "post")==0)
+ g->form->method=POST;
+ else{
+ if(cistrcmp(s, "get")!=0)
+ htmlerror(g->name, g->lineno,
+ "unknown form method %s\n", s);
+ g->form->method=GET;
+ }
+ s=pl_getattr(g->attr, "enctype");
+ if(s && cistrcmp(s, "multipart/form-data")==0)
+ g->form->ctype = "multipart/form-data; boundary=" BOUNDARY;
+ g->form->fields=0;
+
+ g->form->next = g->dst->form;
+ g->dst->form = g->form;
+ break;
+ case Tag_input:
+ case Tag_button:
+ if(g->form==0){
+ /* no form, assume link button */
+ form = emalloc(sizeof(Form));
+ form->method = 0;
+ form->fields = 0;
+ form->efields = 0;
+ if(g->state->link)
+ form->action = strdup(g->state->link);
+ form->next = g->dst->form;
+ g->dst->form = form;
+ f=newfield(form);
+ } else
+ f=newfield(g->form);
+ s=pl_getattr(g->attr, "name");
+ if(s==0 || *s == 0)
+ f->name=0;
+ else
+ f->name=strdup(s);
+ s=pl_getattr(g->attr, "value");
+ if(s==0)
+ f->value=strdup("");
+ else
+ f->value=strdup(s);
+ f->checked=pl_hasattr(g->attr, "checked");
+ s=pl_getattr(g->attr, "size");
+ if(s==0 || *s==0)
+ f->size=20;
+ else
+ f->size=atoi(s);
+ s=pl_getattr(g->attr, "maxlength");
+ if(s==0 || *s==0)
+ f->maxlength=0x3fffffff;
+ else
+ f->maxlength=atoi(s);
+ s=pl_getattr(g->attr, "type");
+ if((g->tag == Tag_button) &&
+ (s==0 || cistrcmp(s, "reset") || cistrcmp(s, "button")))
+ s="submit";
+ else if(s==0)
+ s="text";
+ if(cistrcmp(s, "checkbox")==0)
+ f->type=CHECK;
+ else if(cistrcmp(s, "radio")==0)
+ f->type=RADIO;
+ else if(cistrcmp(s, "submit")==0)
+ f->type=SUBMIT;
+ else if(cistrcmp(s, "image")==0){
+ f->type=SUBMIT;
+ s=pl_getattr(g->attr, "src");
+ if(s && *s){
+ free(g->state->image);
+ g->state->image = strdup(s);
+ }
+ s=pl_getattr(g->attr, "width");
+ if(s && *s)
+ g->state->width=strtolength(g, HORIZ, s);
+ s=pl_getattr(g->attr, "height");
+ if(s && *s)
+ g->state->height=strtolength(g, VERT, s);
+ s=pl_getattr(g->attr, "alt");
+ if(s==0 || *s == 0) s = f->value;
+ pl_htmloutput(g, g->nsp, s, f);
+ free(g->state->image);
+ g->state->image = 0;
+ g->state->width=0;
+ g->state->height=0;
+ break;
+ }
+ else if(cistrcmp(s, "button")==0)
+ f->type=BUTTON;
+ else if(cistrcmp(s, "file")==0)
+ f->type=FILE;
+ else if(cistrcmp(s, "reset")==0)
+ f->type=RESET;
+ else if(cistrcmp(s, "hidden")==0)
+ f->type=HIDDEN;
+ else{
+ f->type=TYPEIN;
+ if(cistrcmp(s, "password")==0)
+ f->type=PASSWD;
+ s=f->name;
+ if(s && cistrcmp(s, "isindex")==0)
+ f->type=INDEX;
+
+ /*
+ * If there's exactly one attribute, use its value as the name,
+ * regardless of the attribute name. This makes
+ * http://linus.att.com/ias/puborder.html work.
+ */
+ if(s==0){
+ if(g->attr[0].name && g->attr[1].name==0)
+ f->name=strdup(g->attr[0].value);
+ else
+ f->name=strdup("no-name");
+ }
+ }
+ if((f->type==CHECK || f->type==RADIO) && !pl_hasattr(g->attr, "value")){
+ free(f->value);
+ f->value=strdup("on");
+ }
+ if(f->type!=HIDDEN)
+ pl_htmloutput(g, g->nsp, f->value[0]?f->value:"blank field", f);
+ break;
+ case Tag_select:
+ if(g->form==0){
+ BadTag:
+ htmlerror(g->name, g->lineno, "<%s> not in form, ignored\n",
+ tag[g->tag].name);
+ break;
+ }
+ f=newfield(g->form);
+ s=pl_getattr(g->attr, "name");
+ if(s==0 || *s==0){
+ f->name=strdup("select");
+ htmlerror(g->name, g->lineno, "select has no name=\n");
+ }
+ else
+ f->name=strdup(s);
+ f->multiple=pl_hasattr(g->attr, "multiple");
+ f->type=SELECT;
+ f->size=0;
+ f->options=0;
+ g->text=g->token;
+ g->tp=g->text;
+ g->etext=g->text;
+ break;
+ case Tag_option:
+ if(g->form==0) goto BadTag;
+ if((f=g->form->efields)==0) goto BadTag;
+ if(f->size<8)
+ f->size++;
+ o=emalloc(sizeof(Option));
+ for(op=&f->options;*op;op=&(*op)->next);
+ *op=o;
+ o->next=0;
+ g->text=o->label;
+ g->tp=o->label;
+ g->etext=o->label+NLABEL;
+ memset(o->label, 0, NLABEL+1);
+ *g->tp++=' ';
+ o->def=pl_hasattr(g->attr, "selected");
+ o->selected=o->def;
+ if(pl_hasattr(g->attr, "disabled"))
+ o->selected=0;
+ s=pl_getattr(g->attr, "value");
+ if(s==0)
+ o->value=o->label+1;
+ else
+ o->value=strdup(s);
+ break;
+ case Tag_textarea:
+ if(g->form==0) goto BadTag;
+ f=newfield(g->form);
+ s=pl_getattr(g->attr, "name");
+ if(s==0 || *s==0){
+ f->name=strdup("enter text");
+ htmlerror(g->name, g->lineno, "select has no name=\n");
+ }
+ else
+ f->name=strdup(s);
+ s=pl_getattr(g->attr, "rows");
+ f->rows=(s && *s)?atoi(s):8;
+ s=pl_getattr(g->attr, "cols");
+ f->cols=(s && *s)?atoi(s):30;
+ f->type=TEXTWIN;
+ /* suck up initial text */
+ pl_htmloutput(g, g->nsp, f->name, f);
+ break;
+ case Tag_isindex:
+ /*
+ * Make up a form with one tag, of type INDEX
+ * I have seen a page with <ISINDEX PROMPT="Enter a title here ">,
+ * which is nonstandard and not handled here.
+ */
+ form=emalloc(sizeof(Form));
+ form->fields=0;
+ form->efields=0;
+ s=pl_getattr(g->attr, "action");
+ form->action=strdup((s && *s) ? s : g->dst->url->fullname);
+ form->method=GET;
+ form->fields=0;
+ f=newfield(form);
+ f->name=0;
+ f->value=strdup("");
+ f->size=20;
+ f->maxlength=0x3fffffff;
+ f->type=INDEX;
+ pl_htmloutput(g, g->nsp, f->value[0]?f->value:"blank field", f);
+ break;
+ }
+}
+/*
+ * Called by rdhtml on seeing a forms-related end tag
+ */
+void endform(Hglob *g){
+ Field *f;
+
+ switch(g->tag){
+ case Tag_form:
+ g->form=0;
+ break;
+ case Tag_select:
+ if(g->form==0)
+ htmlerror(g->name, g->lineno, "</select> not in form, ignored\n");
+ else if((f=g->form->efields)==0)
+ htmlerror(g->name, g->lineno, "spurious </select>\n");
+ else
+ pl_htmloutput(g, g->nsp, f->name, f);
+ break;
+ case Tag_textarea:
+ break;
+ }
+}
+char *nullgen(Panel *, int ){
+ return 0;
+}
+char *selgen(Panel *p, int index){
+ Option *a;
+ Field *f;
+ f=p->userp;
+ if(f==0) return 0;
+ for(a=f->options;index!=0 && a!=0;--index,a=a->next);
+ if(a==0) return 0;
+ a->label[0]=a->selected?'*':' ';
+ return a->label;
+}
+char *seloption(Field *f){
+ Option *a;
+ for(a=f->options;a!=0;a=a->next)
+ if(a->selected)
+ return a->label+1;
+ return f->name;
+}
+void mkfieldpanel(Rtext *t){
+ Action *a;
+ Panel *win, *scrl;
+ Field *f;
+
+ if((a = t->user) == nil)
+ return;
+ if((f = a->field) == nil)
+ return;
+
+ f->p=0;
+ switch(f->type){
+ case TYPEIN:
+ f->p=plentry(0, 0, f->size*chrwidth, f->value, h_submittype);
+ break;
+ case PASSWD:
+ f->p=plentry(0, USERFL, f->size*chrwidth, f->value, h_submittype);
+ break;
+ case CHECK:
+ f->p=plcheckbutton(0, 0, "", h_checkinput);
+ f->state=f->checked;
+ plsetbutton(f->p, f->checked);
+ break;
+ case RADIO:
+ f->p=plradiobutton(0, 0, "", h_radioinput);
+ f->state=f->checked;
+ plsetbutton(f->p, f->checked);
+ break;
+ case SUBMIT:
+ f->p=plbutton(0, 0, f->value[0]?f->value:"submit", h_submitinput);
+ break;
+ case RESET:
+ f->p=plbutton(0, 0, f->value[0]?f->value:"reset", h_resetinput);
+ break;
+ case BUTTON:
+ f->p=plbutton(0, 0, f->value[0]?f->value:"button", h_buttoninput);
+ break;
+ case FILE:
+ f->p=plbutton(0, 0, f->value[0]?f->value:"file", h_fileinput);
+ break;
+ case SELECT:
+ if(f->size <= 0)
+ f->size=1;
+ f->pulldown=plgroup(0,0);
+ scrl=plscrollbar(f->pulldown, PACKW|FILLY);
+ win=pllist(f->pulldown, PACKN, nullgen, f->size, h_select);
+ win->userp=f;
+ plinitlist(win, PACKN, selgen, f->size, h_select);
+ plscroll(win, 0, scrl);
+ plpack(f->pulldown, Rect(0,0,1024,1024));
+ f->p=plpulldown(0, FIXEDX, seloption(f), f->pulldown, PACKS);
+ f->p->fixedsize.x=f->pulldown->r.max.x-f->pulldown->r.min.x;
+ break;
+ case TEXTWIN:
+ f->p=plframe(0,0);
+ pllabel(f->p, PACKN|FILLX, f->name);
+ scrl=plscrollbar(f->p, PACKW|FILLY);
+ f->textwin=pledit(f->p, EXPAND, Pt(f->cols*chrwidth, f->rows*font->height), 0, 0, 0);
+ f->textwin->userp=f;
+ plscroll(f->textwin, 0, scrl);
+ break;
+ case INDEX:
+ f->p=plentry(0, 0, f->size*chrwidth, f->value, h_submitindex);
+ break;
+ }
+ if(f->p){
+ f->p->userp=f;
+ free(t->text);
+ t->text=0;
+ t->p=f->p;
+ t->flags|=PL_HOT;
+ }
+}
+void h_checkinput(Panel *p, int, int v){
+ ((Field *)p->userp)->state=v;
+}
+void h_radioinput(Panel *p, int, int v){
+ Field *f, *me;
+ me=p->userp;
+ me->state=v;
+ if(v){
+ for(f=me->form->fields;f;f=f->next)
+ if(f->type==RADIO && f!=me && strcmp(f->name, me->name)==0){
+ plsetbutton(f->p, 0);
+ f->state=0;
+ pldraw(f->p, screen);
+ }
+ }
+}
+void h_select(Panel *p, int, int index){
+ Option *a;
+ Field *f;
+ f=p->userp;
+ if(f==0) return;
+ if(!f->multiple) for(a=f->options;a;a=a->next) a->selected=0;
+ for(a=f->options;index!=0 && a!=0;--index,a=a->next);
+ if(a==0) return;
+ a->selected=!a->selected;
+ plinitpulldown(f->p, FIXEDX, seloption(f), f->pulldown, PACKS);
+ pldraw(f->p, screen);
+}
+void h_resetinput(Panel *p, int){
+ Field *f;
+ Option *o;
+ for(f=((Field *)p->userp)->form->fields;f;f=f->next) switch(f->type){
+ case TYPEIN:
+ plinitentry(f->p, 0, f->size*chrwidth, f->value, 0);
+ break;
+ case PASSWD:
+ plinitentry(f->p, USERFL, f->size*chrwidth, f->value, 0);
+ break;
+ case FILE:
+ free(f->value);
+ f->value=strdup("");
+ if(f->p==nil) break;
+ f->p->state=0;
+ pldraw(f->p, screen);
+ break;
+ case CHECK:
+ case RADIO:
+ f->state=f->checked;
+ plsetbutton(f->p, f->checked);
+ break;
+ case SELECT:
+ for(o=f->options;o;o=o->next)
+ o->selected=o->def;
+ break;
+ }
+ pldraw(text, screen);
+}
+
+void h_buttoninput(Panel *p, int){
+ Field *f;
+
+ f = p->userp;
+ if(f && f->form && f->form->method != POST && f->form->action)
+ geturl(f->form->action, -1, 0, 0);
+}
+
+void h_fileinput(Panel *p, int){
+ char name[NNAME];
+ Field *f;
+
+ f = p->userp;
+ nstrcpy(name, f->value, sizeof(name));
+ for(;;){
+ if(eenter("Upload file", name, sizeof(name), &mouse) <= 0)
+ break;
+ if(access(name, AREAD) == 0)
+ break;
+ }
+ free(f->value);
+ f->value = strdup(name);
+ p->state = name[0] != 0;
+ pldraw(f->p, screen);
+}
+
+/*
+ * If there's exactly one button with type=text, then
+ * a CR in the button is supposed to submit the form.
+ */
+void h_submittype(Panel *p, char *){
+ int ntype;
+ Field *f;
+ ntype=0;
+ for(f=((Field *)p->userp)->form->fields;f;f=f->next)
+ if(f->type==TYPEIN || f->type==PASSWD)
+ ntype++;
+ if(ntype==1) h_submitinput(p, 0);
+}
+void h_submitindex(Panel *p, char *){
+ h_submitinput(p, 0);
+}
+
+void mencodeform(Form *form, int fd){
+ char *b, *p, *sep;
+ int ifd, n, nb;
+ Option *o;
+ Field *f;
+ Rune *rp;
+
+ sep = "--" BOUNDARY;
+ for(f=form->fields;f;f=f->next)switch(f->type){
+ case TYPEIN:
+ case PASSWD:
+ fprint(fd, "%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s",
+ sep, f->name, plentryval(f->p));
+ sep = "\r\n--" BOUNDARY;
+ break;
+ case CHECK:
+ case RADIO:
+ case SUBMIT:
+ if(!f->state) break;
+ case HIDDEN:
+ if(f->name==0 || f->value==0)
+ break;
+ fprint(fd, "%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s",
+ sep, f->name, f->value);
+ sep = "\r\n--" BOUNDARY;
+ break;
+ case SELECT:
+ if(f->name==0) break;
+ for(o=f->options;o;o=o->next)
+ if(o->selected && o->value){
+ fprint(fd, "%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s",
+ sep, f->name, o->value);
+ sep = "\r\n--" BOUNDARY;
+ }
+ break;
+ case TEXTWIN:
+ if(f->name==0) break;
+ n=plelen(f->textwin);
+ rp=pleget(f->textwin);
+ p=b=malloc(UTFmax*n+1);
+ if(b == nil)
+ break;
+ while(n > 0){
+ p += runetochar(p, rp);
+ rp++;
+ n--;
+ }
+ *p = 0;
+ fprint(fd, "%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s",
+ sep, f->name, b);
+ sep = "\r\n--" BOUNDARY;
+ free(b);
+ break;
+ case FILE:
+ if(f->name==0 || f->value[0]==0)
+ break;
+ if(p = strrchr(f->value, '/'))
+ p++;
+ if(p == 0 || *p == 0)
+ p = f->value;
+ if((b = malloc(nb = 8192)) == nil)
+ break;
+ if((ifd = open(f->value, OREAD)) >= 0){
+ if(filetype(ifd, b, nb) < 0)
+ strcpy(b, "application/octet-stream");
+ fprint(fd, "%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\""
+ "\r\nContent-Type: %s\r\n\r\n", sep, f->name, p, b);
+ sep = "\r\n--" BOUNDARY;
+ while((n = read(ifd, b, nb)) > 0)
+ if(write(fd, b, n) != n)
+ break;
+ close(ifd);
+ }
+ free(b);
+ break;
+ }
+ fprint(fd, "%s--\r\n", sep);
+}
+
+void uencodeform(Form *form, int fd){
+ char *b, *p, *sep;
+ Option *o;
+ Field *f;
+ Rune *rp;
+ int n;
+
+ sep = "";
+ for(f=form->fields;f;f=f->next) switch(f->type){
+ case TYPEIN:
+ case PASSWD:
+ fprint(fd, "%s%U=%U", sep, f->name, plentryval(f->p));
+ sep = "&";
+ break;
+ case INDEX:
+ fprint(fd, "%s%U", sep, plentryval(f->p));
+ sep = "&";
+ break;
+ case CHECK:
+ case RADIO:
+ case SUBMIT:
+ if(!f->state) break;
+ case HIDDEN:
+ if(f->name==0 || f->value==0)
+ break;
+ fprint(fd, "%s%U=%U", sep, f->name, f->value);
+ sep = "&";
+ break;
+ case SELECT:
+ if(f->name==0) break;
+ for(o=f->options;o;o=o->next)
+ if(o->selected && o->value){
+ fprint(fd, "%s%U=%U", sep, f->name, o->value);
+ sep = "&";
+ }
+ break;
+ case TEXTWIN:
+ if(f->name==0) break;
+ n=plelen(f->textwin);
+ rp=pleget(f->textwin);
+ p=b=malloc(UTFmax*n+1);
+ if(b == nil)
+ break;
+ while(n > 0){
+ p += runetochar(p, rp);
+ rp++;
+ n--;
+ }
+ *p = 0;
+ fprint(fd, "%s%U=%U", sep, f->name, b);
+ sep = "&";
+ free(b);
+ break;
+ }
+}
+
+void h_submitinput(Panel *p, int){
+ char buf[NNAME];
+ Form *form;
+ Field *f;
+ int n, fd;
+
+ f = p->userp;
+ form=f->form;
+ for(f=form->fields;f;f=f->next)
+ if(f->type==SUBMIT)
+ f->state = (f->p == p);
+
+ switch(form->method){
+ case GET:
+ strcpy(buf, "/tmp/mfXXXXXXXXXXX");
+ fd = create(mktemp(buf), ORDWR|ORCLOSE, 0600);
+ break;
+ case POST:
+ fd = urlpost(selurl(form->action), form->ctype);
+ break;
+ default:
+ return;
+ }
+
+ if(fd < 0){
+ message("submit: %r");
+ return;
+ }
+ if(form->method==GET){
+ fprint(fd, "%s?", form->action);
+ uencodeform(form, fd);
+ seek(fd, 0, 0);
+ n = readn(fd, buf, sizeof(buf));
+ close(fd);
+ if(n < 0 || n >= sizeof(buf)){
+ message("submit: too large");
+ return;
+ }
+ buf[n] = 0;
+ geturl(buf, -1, 0, 0);
+ } else {
+ /* only set for multipart/form-data */
+ if(form->ctype)
+ mencodeform(form, fd);
+ else
+ uencodeform(form, fd);
+ geturl(form->action, fd, 0, 0);
+ }
+}
+
+void freeform(void *p)
+{
+ Form *form;
+ Field *f;
+ Option *o;
+
+ while(form = p){
+ p = form->next;
+ free(form->action);
+ while(f = form->fields){
+ form->fields = f->next;
+
+ if(f->p!=0)
+ plfree(f->p);
+
+ free(f->name);
+ free(f->value);
+
+ while(o = f->options){
+ f->options = o->next;
+ if(o->value != o->label+1)
+ free(o->value);
+ free(o);
+ }
+
+ free(f);
+ }
+ free(form);
+ }
+}
+
+int Ufmt(Fmt *f){
+ char *s = va_arg(f->args, char*);
+ for(; *s; s++){
+ if(strchr("/$-_@.!*'(),", *s)
+ || 'a'<=*s && *s<='z'
+ || 'A'<=*s && *s<='Z'
+ || '0'<=*s && *s<='9')
+ fmtprint(f, "%c", *s);
+ else if(*s==' ')
+ fmtprint(f, "+");
+ else
+ fmtprint(f, "%%%.2X", *s & 0xFF);
+ }
+ return 0;
+}
--- /dev/null
+++ b/getpix.c
@@ -1,0 +1,158 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#include "mothra.h"
+
+typedef struct Pix Pix;
+struct Pix{
+ Pix *next;
+ Image *b;
+ int width;
+ int height;
+ char name[NNAME];
+};
+
+char *pixcmd[]={
+[GIF] "gif -9t",
+[JPEG] "jpg -9t",
+[PNG] "png -9t",
+[BMP] "bmp -9t",
+[ICO] "ico -c",
+};
+
+void getimage(Rtext *t, Www *w){
+ Action *ap;
+ Url *url;
+ Image *b;
+ int fd, typ;
+ char err[512], buf[80], *s;
+ Pix *p;
+
+ ap=t->user;
+ url=emalloc(sizeof(Url));
+ seturl(url, ap->image, w->url->fullname);
+ for(p=w->pix;p!=nil; p=p->next)
+ if(strcmp(ap->image, p->name)==0 && ap->width==p->width && ap->height==p->height){
+ t->b = p->b;
+ w->changed=1;
+ return;
+ }
+ fd=urlget(url, -1);
+ if(fd==-1){
+ Err:
+ snprint(err, sizeof(err), "[img: %s: %r]", urlstr(url));
+ free(t->text);
+ t->text=strdup(err);
+ w->changed=1;
+ close(fd);
+ goto Out;
+ }
+ typ = snooptype(fd);
+ if(typ < 0 || typ >= nelem(pixcmd) || pixcmd[typ] == nil){
+ werrstr("unknown image type");
+ goto Err;
+ }
+ if((fd = pipeline(fd, "exec %s", pixcmd[typ])) < 0)
+ goto Err;
+ if(ap->width>0 || ap->height>0){
+ s = buf;
+ s += sprint(s, "exec resize");
+ if(ap->width>0)
+ s += sprint(s, " -x %d", ap->width);
+ if(ap->height>0)
+ s += sprint(s, " -y %d", ap->height);
+ USED(s);
+ if((fd = pipeline(fd, buf)) < 0)
+ goto Err;
+ }
+ b=readimage(display, fd, 1);
+ if(b==0){
+ werrstr("can't read image");
+ goto Err;
+ }
+ close(fd);
+ p=emalloc(sizeof(Pix));
+ nstrcpy(p->name, ap->image, sizeof(p->name));
+ p->b=b;
+ p->width=ap->width;
+ p->height=ap->height;
+ p->next=w->pix;
+ w->pix=p;
+ t->b=b;
+ w->changed=1;
+Out:
+ freeurl(url);
+}
+
+void getpix(Rtext *t, Www *w){
+ int i, pid, nworker, worker[NXPROC];
+ Action *ap;
+
+ nworker = 0;
+ for(i=0; i<nelem(worker); i++)
+ worker[i] = -1;
+
+ for(;t!=0;t=t->next){
+ ap=t->user;
+ if(ap && ap->image){
+ pid = rfork(RFFDG|RFPROC|RFMEM);
+ switch(pid){
+ case -1:
+ fprint(2, "fork: %r\n");
+ break;
+ case 0:
+ getimage(t, w);
+ exits(0);
+ default:
+ for(i=0; i<nelem(worker); i++)
+ if(worker[i] == -1){
+ worker[i] = pid;
+ nworker++;
+ break;
+ }
+
+ while(nworker == nelem(worker)){
+ if((pid = waitpid()) < 0)
+ break;
+ for(i=0; i<nelem(worker); i++)
+ if(worker[i] == pid){
+ worker[i] = -1;
+ nworker--;
+ break;
+ }
+ }
+ }
+
+ }
+ }
+ while(nworker > 0){
+ if((pid = waitpid()) < 0)
+ break;
+ for(i=0; i<nelem(worker); i++)
+ if(worker[i] == pid){
+ worker[i] = -1;
+ nworker--;
+ break;
+ }
+ }
+}
+
+ulong countpix(void *p){
+ ulong n=0;
+ Pix *x;
+ for(x = p; x; x = x->next)
+ n += Dy(x->b->r)*bytesperline(x->b->r, x->b->depth);
+ return n;
+}
+
+void freepix(void *p){
+ Pix *x, *xx;
+ xx = p;
+ while(x = xx){
+ xx = x->next;
+ freeimage(x->b);
+ free(x);
+ }
+}
--- /dev/null
+++ b/html.h
@@ -1,0 +1,235 @@
+/*
+ * Parameters
+ */
+#define NSTACK 100 /* html grammar is not recursive, so 30 or so should do */
+#define NHBUF 8192 /* Input buffer size */
+#define NPEEKC 3 /* Maximum lookahead */
+#define NTOKEN 65536 /* Maximum token length */
+#define NATTR 512 /* Maximum number of attributes of a tag */
+typedef struct Pair Pair;
+typedef struct Tag Tag;
+typedef struct Stack Stack;
+typedef struct Hglob Hglob;
+typedef struct Form Form;
+typedef struct Entity Entity;
+struct Pair{
+ char *name;
+ char *value;
+};
+struct Entity{
+ char *name;
+ Rune value;
+};
+struct Tag{
+ char *name;
+ int action;
+};
+struct Stack{
+ int tag; /* html tag being processed */
+ int pre; /* in preformatted text? */
+ int font; /* typeface */
+ int size; /* point size of text */
+ int sub; /* < 0 superscript, > 0 subscript */
+ int margin; /* left margin position */
+ int indent; /* extra indent at paragraph start */
+ int number; /* paragraph number */
+ int ismap; /* flag of <img> */
+ int isscript; /* inside <script> */
+ int strike; /* flag of <strike> */
+ int width; /* size of image */
+ int height;
+ char *image; /* arg of <img> */
+ char *link; /* arg of <a href=...> */
+ char *name; /* arg of <a name=...> */
+};
+
+/*
+ * Globals -- these are packed up into a struct that gets passed around
+ * so that multiple parsers can run concurrently
+ */
+struct Hglob{
+ char *tp; /* pointer in text buffer */
+ char *name; /* input file name */
+ int hfd; /* input file descriptor */
+ char hbuf[NHBUF]; /* input buffer */
+ char *hbufp; /* next character in buffer */
+ char *ehbuf; /* end of good characters in buffer */
+ int heof; /* end of file flag */
+ int peekc[NPEEKC]; /* characters to re-read */
+ int npeekc; /* # of characters to re-read */
+ char token[NTOKEN]; /* if token type is TEXT */
+ Pair attr[NATTR]; /* tag attribute/value pairs */
+ int nsp; /* # of white-space characters before TEXT token */
+ int spacc; /* place to accumulate more spaces */
+ /* if negative, won't accumulate! */
+ int tag; /* if token type is TAG or END */
+ Stack stack[NSTACK]; /* parse stack */
+ Stack *state; /* parse stack pointer */
+ int lineno; /* input line number */
+ int linebrk; /* flag set if we require a line-break in output */
+ int para; /* flag set if we need an indent at the break */
+ char *text; /* text buffer */
+ char *etext; /* end of text buffer */
+ Form *form; /* data for form under construction */
+ Www *dst; /* where the text goes */
+};
+
+/*
+ * Token types
+ */
+enum{
+ TAG=1,
+ ENDTAG,
+ TEXT,
+};
+
+/*
+ * Magic characters corresponding to
+ * literal < followed by / ! or alpha,
+ * literal > and
+ * end of file
+ */
+#define STAG 65536
+#define ETAG 65537
+#define EOF -1
+
+/*
+ * fonts
+ */
+enum{
+ ROMAN,
+ ITALIC,
+ BOLD,
+ CWIDTH,
+};
+
+/*
+ * font sizes
+ */
+enum{
+ SMALL,
+ NORMAL,
+ LARGE,
+ ENORMOUS,
+};
+
+/*
+ * length direction
+ */
+enum{
+ HORIZ,
+ VERT,
+};
+int strtolength(Hglob *g, int dir, char *str);
+
+/*
+ * Token names for the html parser.
+ * Tag_end corresponds to </end> tags.
+ * Tag_text tags text not in a tag.
+ * Those two must follow the others.
+ */
+enum{
+ Tag_comment,
+
+ Tag_a,
+ Tag_abbr,
+ Tag_acronym,
+ Tag_address,
+ Tag_applet,
+ Tag_audio,
+ Tag_b,
+ Tag_base,
+ Tag_blockquot,
+ Tag_body,
+ Tag_br,
+ Tag_button,
+ Tag_center,
+ Tag_cite,
+ Tag_code,
+ Tag_dd,
+ Tag_del,
+ Tag_div,
+ Tag_dfn,
+ Tag_dir,
+ Tag_dl,
+ Tag_dt,
+ Tag_em,
+ Tag_embed,
+ Tag_font,
+ Tag_form,
+ Tag_frame, /* rm 5.8.97 */
+ Tag_h1,
+ Tag_h2,
+ Tag_h3,
+ Tag_h4,
+ Tag_h5,
+ Tag_h6,
+ Tag_head,
+ Tag_hr,
+ Tag_html,
+ Tag_i,
+ Tag_iframe,
+ Tag_img,
+ Tag_image,
+ Tag_input,
+ Tag_ins,
+ Tag_isindex,
+ Tag_kbd,
+ Tag_key,
+ Tag_li,
+ Tag_link,
+ Tag_listing,
+ Tag_menu,
+ Tag_meta,
+ Tag_nextid,
+ Tag_object,
+ Tag_ol,
+ Tag_option,
+ Tag_p,
+ Tag_plaintext,
+ Tag_pre,
+ Tag_s,
+ Tag_samp,
+ Tag_script,
+ Tag_select,
+ Tag_span,
+ Tag_strike,
+ Tag_strong,
+ Tag_style,
+ Tag_sub,
+ Tag_sup,
+ Tag_source,
+ Tag_table, /* rm 3.8.00 */
+ Tag_td,
+ Tag_th,
+ Tag_textarea,
+ Tag_title,
+ Tag_tr,
+ Tag_tt,
+ Tag_u,
+ Tag_ul,
+ Tag_var,
+ Tag_video,
+ Tag_wbr,
+ Tag_xmp,
+
+ Tag_end, /* also used to indicate unrecognized start tag */
+ Tag_text,
+};
+enum{
+ NTAG=Tag_end,
+ END=1, /* tag must have a matching end tag */
+ NOEND, /* tag must not have a matching end tag */
+ OPTEND, /* tag may have a matching end tag */
+ ERR, /* tag must not occur */
+};
+Tag tag[];
+void rdform(Hglob *);
+void endform(Hglob *);
+char *pl_getattr(Pair *, char *);
+int pl_hasattr(Pair *, char *);
+void pl_htmloutput(Hglob *, int, char *, Field *);
+
+#pragma incomplete Form
+#pragma incomplete Field
+
--- /dev/null
+++ b/html.syntax.c
@@ -1,0 +1,92 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#include "mothra.h"
+#include "html.h"
+Tag tag[]={
+[Tag_a] "a", END,
+[Tag_abbr] "abbr", END,
+[Tag_acronym] "acronym", END,
+[Tag_address] "address", END,
+[Tag_applet] "applet", NOEND,
+[Tag_audio] "audio", OPTEND,
+[Tag_b] "b", END,
+[Tag_base] "base", NOEND,
+[Tag_blockquot] "blockquote", END,
+[Tag_body] "body", END, /* OPTEND */
+[Tag_br] "br", NOEND,
+[Tag_button] "button", END,
+[Tag_center] "center", END,
+[Tag_cite] "cite", END,
+[Tag_code] "code", END,
+[Tag_comment] "!--", NOEND,
+[Tag_dd] "dd", NOEND, /* OPTEND */
+[Tag_del] "del", END,
+[Tag_dfn] "dfn", END,
+[Tag_dir] "dir", END,
+[Tag_div] "div", END, /* OPTEND */
+[Tag_dl] "dl", END,
+[Tag_dt] "dt", NOEND, /* OPTEND */
+[Tag_em] "em", END,
+[Tag_embed] "embed", NOEND,
+[Tag_end] 0, ERR,
+[Tag_font] "font", END,
+[Tag_form] "form", END,
+[Tag_frame] "frame", NOEND,
+[Tag_h1] "h1", END,
+[Tag_h2] "h2", END,
+[Tag_h3] "h3", END,
+[Tag_h4] "h4", END,
+[Tag_h5] "h5", END,
+[Tag_h6] "h6", END,
+[Tag_head] "head", END, /* OPTEND */
+[Tag_hr] "hr", NOEND,
+[Tag_html] "html", END, /* OPTEND */
+[Tag_i] "i", END,
+[Tag_iframe] "iframe", NOEND,
+[Tag_img] "img", NOEND,
+[Tag_image] "image", NOEND,
+[Tag_input] "input", NOEND,
+[Tag_ins] "ins", END,
+[Tag_isindex] "isindex", NOEND,
+[Tag_kbd] "kbd", END,
+[Tag_key] "key", END,
+[Tag_li] "li", NOEND, /* OPTEND */
+[Tag_link] "link", NOEND,
+[Tag_listing] "listing", END,
+[Tag_menu] "menu", END,
+[Tag_meta] "meta", NOEND,
+[Tag_nextid] "nextid", NOEND,
+[Tag_object] "object", END,
+[Tag_ol] "ol", END,
+[Tag_option] "option", NOEND, /* OPTEND */
+[Tag_p] "p", NOEND, /* OPTEND */
+[Tag_plaintext] "plaintext", NOEND,
+[Tag_pre] "pre", END,
+[Tag_s] "s", END,
+[Tag_samp] "samp", END,
+[Tag_script] "script", END,
+[Tag_select] "select", END,
+[Tag_span] "span", END,
+[Tag_strike] "strike", END,
+[Tag_strong] "strong", END,
+[Tag_style] "style", END,
+[Tag_sub] "sub", END,
+[Tag_sup] "sup", END,
+[Tag_source] "source", NOEND,
+[Tag_table] "table", END,
+[Tag_td] "td", END,
+[Tag_th] "th", END,
+[Tag_textarea] "textarea", END,
+[Tag_title] "title", END,
+[Tag_tr] "tr", END,
+[Tag_tt] "tt", END,
+[Tag_u] "u", END,
+[Tag_ul] "ul", END,
+[Tag_var] "var", END,
+[Tag_video] "video", OPTEND,
+[Tag_wbr] "wbr", NOEND,
+[Tag_xmp] "xmp", END,
+};
--- /dev/null
+++ b/libpanel/button.c
@@ -1,0 +1,205 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#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
+#define MENU 4
+void pl_drawbutton(Panel *p){
+ Rectangle r;
+ Button *bp;
+ bp=p->data;
+ switch(bp->btype){
+ case MENU:
+ r=pl_box(p->b, p->r, p->state);
+ break;
+ case BUTTON:
+ r=pl_box(p->b, p->r, p->state|BORDER);
+ break;
+ case CHECK:
+ r=pl_box(p->b, p->r, p->state|BORDER);
+ r=pl_check(p->b, r, bp->check);
+ break;
+ case RADIO:
+ r=pl_box(p->b, p->r, p->state|BORDER);
+ 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 && bp->btype!=MENU){
+ ckw=pl_ckwid();
+ if(s.y<ckw){
+ s.x+=ckw;
+ s.y=ckw;
+ }
+ else s.x+=s.y;
+ }
+ return pl_boxsize(s, p->state);
+}
+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 MENU: v->kind="button"; break;
+ 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 *pl_menubutton(Panel *parent, int flags, Icon *icon, void (*hit)(Panel *, int)){
+ Panel *p;
+ p=pl_newpanel(parent, sizeof(Button));
+ ((Button *)p->data)->pl_buttonhit=hit;
+ pl_initbtype(p, flags, icon, pl_buttonhit, MENU);
+ return p;
+}
+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=pl_menubutton(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;
+}
--- /dev/null
+++ b/libpanel/canvas.c
@@ -1,0 +1,51 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#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;
+}
--- /dev/null
+++ b/libpanel/draw.c
@@ -1,0 +1,261 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#include "pldefs.h"
+#define PWID 1 /* width of label border */
+#define BWID 1 /* width of button relief */
+#define FWID 1 /* width of frame relief */
+#define SPACE 2 /* 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 Image *pl_light, *pl_dark, *pl_scrl, *pl_tick, *pl_hilit;
+Image *pl_blue, *pl_white, *pl_black;
+int pl_drawinit(void){
+ pl_white=allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x000000FF);
+ pl_light=allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x000000FF);
+ pl_dark=allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x777777FF);
+ pl_scrl=allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x999999FF);
+ pl_black=allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xFFFFFFFF);
+ pl_hilit=allocimage(display, Rect(0,0,1,1), CHAN1(CAlpha,8), 1, 0x80);
+ pl_blue=allocimage(display, Rect(0,0,1,1), RGB24, 1, 0x0A84FFFF);
+ if((pl_tick = allocimage(display, Rect(0, 0, TICKW, font->height), screen->chan, 0, DNofill)) != nil){
+ draw(pl_tick, pl_tick->r, pl_white, nil, ZP);
+ draw(pl_tick, Rect(TICKW/2, 0, TICKW/2+1, font->height), pl_black, nil, ZP);
+ draw(pl_tick, Rect(0, 0, TICKW, TICKW), pl_black, nil, ZP);
+ draw(pl_tick, Rect(0, font->height-TICKW, TICKW, font->height), pl_black, nil, ZP);
+ }
+ if(pl_white==0 || pl_light==0 || pl_black==0 || pl_dark==0 || pl_scrl==0 || pl_blue==0 || pl_tick==0) sysfatal("allocimage: %r");
+ return 1;
+}
+Rectangle pl_boxoutline(Image *b, Rectangle r, int style, int fill){
+ int doborder;
+
+ doborder = (style & BORDER) != 0;
+ switch(style & ~BORDER){
+ case SUP:
+ case TUP:
+ if(fill) draw(b, r, pl_light, 0, ZP);
+ else border(b, r, BWID+SPACE, pl_white, ZP);
+ if(doborder) border(b, r, BWID, pl_black, ZP);
+ r=insetrect(r, BWID);
+ break;
+ case UP:
+ if(fill) draw(b, r, pl_light, 0, ZP);
+ else border(b, r, BWID+SPACE, pl_white, ZP);
+ if(doborder) border(b, r, BWID, pl_black, ZP);
+ r=insetrect(r, BWID);
+ break;
+ case DOWN:
+ case DOWN1:
+ case DOWN2:
+ case DOWN3:
+ if(fill) draw(b, r, pl_dark, 0, ZP);
+ else border(b, r, BWID+SPACE, pl_dark, ZP);
+ if(doborder) border(b, r, BWID, pl_black, ZP);
+ r=insetrect(r, BWID);
+ break;
+ case PASSIVE:
+ if(fill) draw(b, r, pl_light, 0, ZP);
+ else border(b, r, PWID+SPACE, pl_white, ZP);
+ if(doborder) border(b, r, BWID, pl_black, ZP);
+ r=insetrect(r, PWID);
+ break;
+ case FRAME:
+ border(b, r, FWID, pl_black, ZP);
+ r=insetrect(r, FWID);
+ if(fill) draw(b, r, pl_light, 0, ZP);
+ else border(b, r, SPACE, pl_white, ZP);
+ break;
+ }
+ switch(style){
+ case SUP: return insetrect(r, SPACE-SPACE);
+ default: 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);
+}
+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(2*FWID+2*SPACE, 2*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(FWID+SPACE, FWID+SPACE));
+ *size=subpt(*size, Pt(2*FWID+2*SPACE, 2*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);
+ border(b, r, CKWID, pl_white, ZP);
+ r=insetrect(r, CKWID);
+ 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);
+ border(b, r, CKWID, pl_white, ZP);
+ r=insetrect(r, CKWID);
+ 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_scrollupd(Image *b, Rectangle r, int lo, int hi)
+{
+ Rectangle sr;
+ if(lo<0) lo=0;
+ if(hi<=lo) hi=lo+1;
+ sr=r;
+ sr.min.y+=lo;
+ sr.max.x-=1;
+ sr.max.y=sr.min.y+hi;
+ if(sr.max.y>r.max.y) sr.max.y=r.max.y;
+ draw(b, r, pl_scrl, 0, ZP);
+ draw(b, sr, 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_drawtick(Image *b, Rectangle r){
+ draw(b, r, pl_tick, nil, ZP);
+}
+void pl_clr(Image *b, Rectangle r){
+ draw(b, r, pl_white, 0, ZP);
+}
+void pl_fill(Image *b, Rectangle r){
+ draw(b, r, 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);
+}
--- /dev/null
+++ b/libpanel/edit.c
@@ -1,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 <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#include "pldefs.h"
+#include <keyboard.h>
+
+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);
+}
--- /dev/null
+++ b/libpanel/entry.c
@@ -1,0 +1,296 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#include "pldefs.h"
+#include <keyboard.h>
+
+typedef struct Entry Entry;
+struct Entry{
+ Rectangle lastr;
+ Rune *entry;
+ char *sentry;
+ int sz, n;
+ int a, b;
+ Point text;
+ void (*hit)(Panel *, char *);
+ Point minsize;
+};
+#define SLACK 7 /* enough for one extra rune and ◀ and a nul */
+void pl_cutentry(Panel *p){
+ Entry *ep;
+
+ ep=p->data;
+ memmove(ep->entry+ep->a, ep->entry+ep->b, (ep->n-ep->b)*sizeof(Rune));
+ ep->n -= ep->b-ep->a;
+ ep->entry[ep->n]=0;
+ ep->b=ep->a;
+}
+char *pl_snarfentry(Panel *p){
+ Entry *ep;
+ int n;
+
+ if(p->flags&USERFL) /* no snarfing from password entry */
+ return nil;
+ ep=p->data;
+ n=ep->b-ep->a;
+ if(n<1) return nil;
+ return smprint("%.*S", n, ep->entry+ep->a);
+}
+void pl_pasteentry(Panel *p, char *s){
+ Entry *ep;
+ int m;
+
+ ep=p->data;
+ m=utflen(s);
+ ep->sz=ep->n+m+100+SLACK;
+ ep->entry=pl_erealloc(ep->entry,ep->sz*sizeof(Rune));
+ memmove(ep->entry+ep->a+m, ep->entry+ep->b, (ep->n-ep->b)*sizeof(Rune));
+ ep->n+=m-(ep->b-ep->a);
+ while(m-- > 0)
+ s += chartorune(&ep->entry[ep->a++], s);
+ ep->b=ep->a;
+ ep->entry[ep->n]=0;
+ pldraw(p, p->b);
+}
+static void drawentry(Panel *p, Rectangle r, Rune *s){
+ Rectangle save;
+ Point tick;
+ Entry *ep;
+ Image *b;
+ int d;
+
+ ep = p->data;
+ b = p->b;
+
+ ep->text = r.min;
+ ep->lastr = r;
+ tick = ep->text;
+ tick.x += runestringnwidth(font, s, ep->a);
+ if(plkbfocus == p)
+ r.max.x -= TICKW;
+ ep->text.y = r.min.y;
+ if(!ptinrect(tick, r)){
+ d = 0;
+ if(tick.x < r.min.x)
+ d = r.min.x - tick.x;
+ else if(tick.x > r.max.x)
+ d = r.max.x - tick.x;
+ tick.x += d;
+ ep->text.x += d;
+ }
+ if(plkbfocus == p)
+ r.max.x += TICKW;
+
+ save = b->clipr;
+ if(!rectclip(&r, save))
+ return;
+ replclipr(b, b->repl, r);
+ runestring(b, ep->text, pl_black, ZP, font, s);
+ if(plkbfocus == p){
+ r.min = tick;
+ if(ep->a != ep->b){
+ r.max.x = ep->text.x+runestringnwidth(font, s, ep->b);
+ if(r.max.x < r.min.x){
+ d = r.min.x;
+ r.min.x = r.max.x;
+ r.max.x = d;
+ }
+ pl_highlight(b, r);
+ }else
+ pl_drawtick(b, r);
+ }
+ replclipr(b, b->repl, save);
+}
+void pl_drawentry(Panel *p){
+ Rectangle r;
+ Entry *ep;
+ Rune *s;
+
+ ep=p->data;
+ r=pl_box(p->b, p->r, p->state|BORDER);
+ s=ep->entry;
+ if(p->flags & USERFL){
+ Rune *p;
+ s=runestrdup(s);
+ for(p=s; *p; p++)
+ *p='*';
+ }
+ drawentry(p, r, s);
+ if(s != ep->entry)
+ free(s);
+}
+int pl_hitentry(Panel *p, Mouse *m){
+ Entry *ep;
+ int i, n, selecting;
+ if((m->buttons&7)==1){
+ if(plkbfocus != p)
+ p->state=DOWN;
+ plgrabkb(p);
+ ep = p->data;
+ for(i = 1; i <= ep->n; i++)
+ if(runestringnwidth(font, ep->entry, i) > m->xy.x-ep->text.x)
+ break;
+ n = i-1;
+ ep->a = ep->b = n;
+ pldraw(p, p->b);
+ selecting = 1;
+ while(m->buttons&1){
+ int old;
+ old=m->buttons;
+ if(display->bufp > display->buf)
+ flushimage(display, 1);
+ *m=emouse();
+ p->state=UP;
+ if((old&7)==1){
+ if((m->buttons&7)==3){
+ plsnarf(p);
+ pl_cutentry(p);
+ pldraw(p, p->b);
+ ep->b = n = ep->a;
+ }
+ if(selecting && (m->buttons&7)==1){
+ p->state=UP;
+ for(i = 0; i < ep->n; i++)
+ if(runestringnwidth(font, ep->entry, i)+TICKW > m->xy.x-ep->text.x)
+ break;
+ /*
+ * tick is moved towards the mouse pointer dragging the selection
+ * after drawing it has to be set so that (a <= b), since
+ * the rest of the logic assumes that's always the case
+ */
+ ep->a = i;
+ ep->b = n;
+ pldraw(p, p->b);
+ if(ep->a > ep->b){
+ ep->a = n;
+ ep->b = i;
+ }
+ }else
+ selecting = 0;
+ if((m->buttons&7)==5)
+ plpaste(p);
+ }
+ }
+ p->state=UP;
+ pldraw(p, p->b);
+ }
+ return 0;
+}
+void pl_typeentry(Panel *p, Rune c){
+ Entry *ep;
+ ep=p->data;
+ switch(c){
+ case '\n':
+ case '\r':
+ if(ep->hit) ep->hit(p, plentryval(p));
+ return;
+ case Kleft:
+ if(ep->a > 0)
+ ep->a--;
+ ep->b=ep->a;
+ break;
+ case Kright:
+ if(ep->a<ep->n)
+ ep->a++;
+ ep->b = ep->a;
+ break;
+ case Ksoh:
+ ep->a=ep->b=0;
+ break;
+ case Kenq:
+ ep->a=ep->b=ep->n;
+ break;
+ case Kesc:
+ ep->a=0;
+ ep->b=ep->n;
+ plsnarf(p);
+ /* no break */
+ case Kdel: /* clear */
+ ep->a = ep->b = ep->n = 0;
+ *ep->entry = 0;
+ break;
+ case Knack: /* ^U: erase line */
+ ep->a = 0;
+ pl_cutentry(p);
+ break;
+ case Kbs: /* ^H: erase character */
+ if(ep->a > 0 && ep->a == ep->b)
+ ep->a--;
+ /* wet floor */
+ if(0){
+ case Ketb: /* ^W: erase word */
+ while(ep->a>0 && !pl_idchar(ep->entry[ep->a-1]))
+ --ep->a;
+ while(ep->a>0 && pl_idchar(ep->entry[ep->a-1]))
+ --ep->a;
+ }
+ pl_cutentry(p);
+ break;
+ default:
+ if(c < 0x20 || (c & 0xFF00) == KF || (c & 0xFF00) == Spec)
+ break;
+ memmove(ep->entry+ep->a+1, ep->entry+ep->b, (ep->n-ep->b)*sizeof(Rune));
+ ep->n -= ep->b - ep->a - 1;
+ ep->entry[ep->a++] = c;
+ ep->b = ep->a;
+ if(ep->n>ep->sz){
+ ep->sz = ep->n+100;
+ ep->entry=pl_erealloc(ep->entry, (ep->sz+SLACK)*sizeof(Rune));
+ }
+ ep->entry[ep->n]=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);
+ free(ep->sentry);
+ ep->entry = nil;
+ ep->sentry = nil;
+}
+void plinitentry(Panel *v, int flags, int wid, char *str, void (*hit)(Panel *, char *)){
+ 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;
+ ep->a = ep->b = 0;
+ ep->n = str ? utflen(str) : 0;
+ ep->sz = ep->n + 100;
+ ep->entry=pl_erealloc(ep->entry, (ep->sz+SLACK)*sizeof(Rune));
+ runesnprint(ep->entry, ep->sz, "%s", str ? str : "");
+ 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;
+ free(ep->sentry);
+ ep->sentry = smprint("%S", ep->entry);
+ return ep->sentry;
+}
--- /dev/null
+++ b/libpanel/event.c
@@ -1,0 +1,53 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#include "pldefs.h"
+
+void plgrabkb(Panel *g){
+ Panel *o;
+ o=plkbfocus;
+ plkbfocus=nil;
+ if(o && o!=g) /* redraw if lost focus */
+ pldraw(o, o->b);
+ 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;
+ }
+}
--- /dev/null
+++ b/libpanel/frame.c
@@ -1,0 +1,39 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#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;
+}
--- /dev/null
+++ b/libpanel/group.c
@@ -1,0 +1,37 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#include "pldefs.h"
+void pl_drawgroup(Panel *p){
+ pl_outline(p->b, p->r, FRAME);
+}
+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 *, Point children){
+ return pl_boxsize(children, FRAME);
+}
+void pl_childspacegroup(Panel *, Point *ul, Point *size){
+ pl_interior(FRAME, 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;
+}
--- /dev/null
+++ b/libpanel/init.c
@@ -1,0 +1,13 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#include "pldefs.h"
+/*
+ * Just a wrapper for all the initialization routines
+ */
+int plinit(void){
+ if(!pl_drawinit()) return 0;
+ return 1;
+}
--- /dev/null
+++ b/libpanel/label.c
@@ -1,0 +1,50 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#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;
+}
--- /dev/null
+++ b/libpanel/list.c
@@ -1,0 +1,190 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#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 && sel<hi && sel<lp->len){
+ 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->sel<hi) pl_listsel(p, lp->sel, 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->sel<lp->len && 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->lo<oldlo){
+ y=r.min.y+(oldlo-lp->lo)*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;
+}
--- /dev/null
+++ b/libpanel/mem.c
@@ -1,0 +1,123 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#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);
+}
--- /dev/null
+++ b/libpanel/message.c
@@ -1,0 +1,104 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#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, pl_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;
+}
--- /dev/null
+++ b/libpanel/mkfile
@@ -1,0 +1,34 @@
+</$objtype/mkfile
+
+LIB=libpanel.a$O
+OFILES=\
+ button.$O\
+ canvas.$O\
+ draw.$O\
+ edit.$O\
+ entry.$O\
+ event.$O\
+ frame.$O\
+ group.$O\
+# idollist.$O\
+ init.$O\
+ label.$O\
+ list.$O\
+ mem.$O\
+ message.$O\
+ pack.$O\
+ popup.$O\
+ print.$O\
+ pulldown.$O\
+ rtext.$O\
+ scroll.$O\
+ scrollbar.$O\
+ slider.$O\
+ textview.$O\
+ textwin.$O\
+ utf.$O\
+ snarf.$O
+
+HFILES=panel.h pldefs.h rtext.h
+
+</sys/src/cmd/mklib
--- /dev/null
+++ b/libpanel/pack.c
@@ -1,0 +1,161 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#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);
+}
--- /dev/null
+++ b/libpanel/panel.h
@@ -1,0 +1,201 @@
+//#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 */
+/* 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
+
+Panel *plkbfocus; /* the panel in keyboard focus */
+
+int plinit(void); /* 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 */
binary files /dev/null b/libpanel/panel.pdf differ
--- /dev/null
+++ b/libpanel/pldefs.h
@@ -1,0 +1,113 @@
+/*
+ * 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 */
+#define TICKW 3 /* tick width */
+/*
+ * States, also styles
+ */
+enum{
+ SUP, // scrollbar
+ TUP, // textview
+ UP, // deprecated
+ DOWN1,
+ DOWN2,
+ DOWN3,
+ DOWN,
+ PASSIVE,
+ FRAME,
+ BORDER = 1<<8,
+};
+/*
+ * Scroll flags
+ */
+enum{
+ SCROLLUP,
+ SCROLLDOWN,
+ SCROLLABSY,
+ SCROLLLEFT,
+ SCROLLRIGHT,
+ SCROLLABSX,
+};
+
+extern Image *pl_blue, *pl_white, *pl_black;
+
+/*
+ * 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(void);
+Rectangle pl_box(Image *, Rectangle, int);
+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_scrollupd(Image *, Rectangle, int, int);
+void pl_invis(Panel *, int);
+Point pl_iconsize(int, Icon *);
+void pl_highlight(Image *, Rectangle);
+void pl_drawtick(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);
--- /dev/null
+++ b/libpanel/popup.c
@@ -1,0 +1,116 @@
+/*
+ * popup
+ * looks like a group, except diverts hits on certain buttons to
+ * panels that it temporarily pops up.
+ */
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#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.x<g->r.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.y<g->r.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;
+}
--- /dev/null
+++ b/libpanel/print.c
@@ -1,0 +1,56 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#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);
+}
--- /dev/null
+++ b/libpanel/pulldown.c
@@ -1,0 +1,160 @@
+/*
+ * pulldown
+ * makes a button that pops up a panel when hit
+ */
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#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.y<g->r.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.x<g->r.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;
+}
--- /dev/null
+++ b/libpanel/rtext.c
@@ -1,0 +1,365 @@
+/*
+ * Rich text with images.
+ * Should there be an offset field, to do subscripts & kerning?
+ */
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#include "pldefs.h"
+#include "rtext.h"
+
+#define LEAD 4 /* extra space between lines */
+#define BORD 2 /* extra border for images */
+
+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.y<r.max.y
+ && dr.max.x>r.min.x
+ && dr.min.x<r.max.x){
+ if(t->b){
+ draw(b, insetrect(dr, BORD), t->b, 0, t->b->r.min);
+ if(t->flags&PL_STR) {
+ line(b, Pt(dr.min.x, dr.min.y), Pt(dr.max.x, dr.max.y),
+ Endsquare, Endsquare, 0,
+ pl_black, ZP);
+ line(b, Pt(dr.min.x, dr.max.y), Pt(dr.max.x, dr.min.y),
+ Endsquare, Endsquare, 0,
+ pl_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_HOT)
+ string(b, dr.min, pl_blue, ZP, t->font, t->text);
+ else
+ string(b, dr.min, pl_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,
+ pl_black, ZP);
+ sp = Pt(dr.max.x, y);
+ } else
+ sp = ZP;
+ 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;
+}
--- /dev/null
+++ b/libpanel/rtext.h
@@ -1,0 +1,11 @@
+/*
+ * Rtext definitions
+ */
+#define PL_NOPBIT 4
+#define PL_NARGBIT 12
+#define PL_ARGMASK ((1<<PL_NARGBIT)-1)
+#define PL_SPECIAL(op) (((-1<<PL_NOPBIT)|op)<<PL_NARGBIT)
+#define PL_OP(t) ((t)&~PL_ARGMASK)
+#define PL_ARG(t) ((t)&PL_ARGMASK)
+#define PL_TAB PL_SPECIAL(0) /* # of tab stops before text */
+void pltabsize(int, int); /* set min tab and tab size */
--- /dev/null
+++ b/libpanel/scrltest.c
@@ -1,0 +1,65 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+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);
+}
--- /dev/null
+++ b/libpanel/scroll.c
@@ -1,0 +1,21 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#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);
+ }
+}
--- /dev/null
+++ b/libpanel/scrollbar.c
@@ -1,0 +1,143 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#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, SUP); /* SUP was p->state */
+ pl_scrollupd(p->b, sp->interior, 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_scrollupd(g->b, sp->interior, 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_scrollupd(g->b, sp->interior, 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);
+ 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;
+}
--- /dev/null
+++ b/libpanel/slider.c
@@ -1,0 +1,97 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#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;
+}
--- /dev/null
+++ b/libpanel/snarf.c
@@ -1,0 +1,58 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#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);
+ }
+}
--- /dev/null
+++ b/libpanel/textview.c
@@ -1,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 <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#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, TUP);
+ 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=PASSIVE;
+ 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=PASSIVE;
+ }
+ 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=PASSIVE;
+ 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);
+}
--- /dev/null
+++ b/libpanel/textwin.c
@@ -1,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 <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#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.y<b.y || a.y<b.y+t->hgt && a.x<b.x;
+}
+/*
+ * Return the character index indicated by point p, or -1
+ * if its off-screen. The screen must be up-to-date.
+ *
+ * Linear search should be binary search.
+ */
+int twpt2rune(Textwin *t, Point p){
+ Point *el, *lp;
+ el=t->loc+(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(i<t->top) 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(first<t->top) 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 %zd 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(sel1<sel0){ swap=sel0; sel0=sel1; sel1=swap; }
+ if(sel1<t->top || t->bot<sel0) return;
+ if(sel0<t->top) 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 && sel1<newsel) || (newsel<sel1 && sel1<sel0))
+ tw_hilitep(t, p1, newp);
+ else if((sel0<=newsel && newsel<sel1) || (sel1<newsel && newsel<=sel0)){
+ twhilite(t, sel1, newsel, 0);
+ if(newsel==sel0)
+ tw_hilitep(t, p0, newp);
+ }else if((newsel<sel0 && sel0<=sel1) || (sel1<sel0 && sel0<=newsel)){
+ twhilite(t, sel0, sel1, 0);
+ tw_hilitep(t, p0, newp);
+ }
+ sel1=newsel;
+ p1=newp;
+ }
+ }
+ if(sel0<=sel1){
+ t->sel0=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(first<t->top || last<first || t->bot<last) return;
+ nbyte=(last-first+1)*sizeof(Point);
+ srcloc=pl_emalloc(nbyte);
+ memmove(srcloc, &t->loc[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(r1<t->top){ /* 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; lp<t->eloc; lp++)
+ *lp = addpt(*lp, d);
+}
--- /dev/null
+++ b/libpanel/utf.c
@@ -1,0 +1,30 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#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);
+}
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,27 @@
+</$objtype/mkfile
+
+TARG=bmothra
+LIB=libpanel/libpanel.a$O
+CFILES= \
+ snoop.c \
+ forms.c \
+ getpix.c \
+ html.syntax.c \
+ mothra.c \
+ rdhtml.c \
+ url.c \
+
+OFILES=${CFILES:%.c=%.$O}
+HFILES=mothra.h html.h libpanel/panel.h libpanel/rtext.h
+BIN=/$objtype/bin
+</sys/src/cmd/mkone
+
+CFLAGS=-FTVw -Ilibpanel
+
+$LIB:V:
+ cd libpanel
+ mk
+
+clean nuke:V:
+ @{ cd libpanel; mk $target }
+ rm -f *.[$OS] [$OS].out $TARG
--- /dev/null
+++ b/mothra.c
@@ -1,0 +1,1283 @@
+/*
+ * Trivial web browser
+ */
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <keyboard.h>
+#include <plumb.h>
+#include <cursor.h>
+#include <panel.h>
+#include <regexp.h>
+#include "mothra.h"
+#include "rtext.h"
+int debug=0;
+int verbose=0; /* -v flag causes html errors to be written to file-descriptor 2 */
+int killimgs=0; /* should mothra kill images? */
+int defdisplay=1; /* is the default (initial) display visible? */
+int visxbar=0; /* horizontal scrollbar visible? */
+int topxbar=0; /* horizontal scrollbar at top? */
+Panel *root; /* the whole display */
+Panel *alt; /* the alternate display */
+Panel *alttext; /* the alternate text window */
+Panel *cmd; /* command entry */
+Panel *cururl; /* label giving the url of the visible text */
+Panel *list; /* list of previously acquired www pages */
+Panel *msg; /* message display */
+Panel *menu3; /* button 3 menu */
+char mothra[] = "mothra!";
+Cursor patientcurs={
+ 0, 0,
+ 0x01, 0x80, 0x03, 0xC0, 0x07, 0xE0, 0x07, 0xe0,
+ 0x07, 0xe0, 0x07, 0xe0, 0x03, 0xc0, 0x0F, 0xF0,
+ 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8, 0x1F, 0xF8,
+ 0x0F, 0xF0, 0x1F, 0xF8, 0x3F, 0xFC, 0x3F, 0xFC,
+
+ 0x01, 0x80, 0x03, 0xC0, 0x07, 0xE0, 0x04, 0x20,
+ 0x04, 0x20, 0x06, 0x60, 0x02, 0x40, 0x0C, 0x30,
+ 0x10, 0x08, 0x14, 0x08, 0x14, 0x28, 0x12, 0x28,
+ 0x0A, 0x50, 0x16, 0x68, 0x20, 0x04, 0x3F, 0xFC,
+};
+Cursor confirmcursor={
+ 0, 0,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+
+ 0x00, 0x0E, 0x07, 0x1F, 0x03, 0x17, 0x73, 0x6F,
+ 0xFB, 0xCE, 0xDB, 0x8C, 0xDB, 0xC0, 0xFB, 0x6C,
+ 0x77, 0xFC, 0x00, 0x00, 0x00, 0x01, 0x00, 0x03,
+ 0x94, 0xA6, 0x63, 0x3C, 0x63, 0x18, 0x94, 0x90,
+};
+Cursor readingcurs={
+ -10, -3,
+ 0x00, 0x00, 0x00, 0x00, 0x0F, 0xF0, 0x0F, 0xF0,
+ 0x0F, 0xF0, 0x0F, 0xF0, 0x0F, 0xF0, 0x1F, 0xF0,
+ 0x3F, 0xF0, 0x7F, 0xF0, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFB, 0xFF, 0xF3, 0xFF, 0x00, 0x00, 0x00, 0x00,
+
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, 0xE0,
+ 0x07, 0xE0, 0x01, 0xE0, 0x03, 0xE0, 0x07, 0x60,
+ 0x0E, 0x60, 0x1C, 0x00, 0x38, 0x00, 0x71, 0xB6,
+ 0x61, 0xB6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+};
+Cursor mothcurs={
+ {-7, -7},
+ {0x00, 0x00, 0x60, 0x06, 0xf8, 0x1f, 0xfc, 0x3f,
+ 0xfe, 0x7f, 0xff, 0xff, 0x7f, 0xfe, 0x7f, 0xfe,
+ 0x7f, 0xfe, 0x3f, 0xfc, 0x3f, 0xfc, 0x1f, 0xf8,
+ 0x1f, 0xf8, 0x0e, 0x70, 0x0c, 0x30, 0x00, 0x00, },
+ {0x00, 0x00, 0x00, 0x00, 0x60, 0x06, 0x58, 0x1a,
+ 0x5c, 0x3a, 0x64, 0x26, 0x27, 0xe4, 0x37, 0xec,
+ 0x37, 0xec, 0x17, 0xe8, 0x1b, 0xd8, 0x0e, 0x70,
+ 0x0c, 0x30, 0x04, 0x20, 0x00, 0x00, 0x00, 0x00, }
+};
+
+Www *current=0;
+Url *selection=0;
+int mothmode;
+int kickpipe[2];
+
+void docmd(Panel *, char *);
+void doprev(Panel *, int, int);
+char *urlstr(Url *);
+void setcurrent(int, char *);
+char *genwww(Panel *, int);
+void updtext(Www *);
+void dolink(Panel *, int, Rtext *);
+void hit3(int, int);
+void mothon(Www *, int);
+void killpix(Www *w);
+char *buttons[]={
+ "alt display",
+ "moth mode",
+ "snarf",
+ "paste",
+ "plumb",
+ "search",
+ "save hit",
+ "hit list",
+ "exit",
+ 0
+};
+
+int wwwtop=0;
+Www *www(int index){
+ static Www a[NWWW];
+ return &a[index % NWWW];
+}
+int nwww(void){
+ return wwwtop<NWWW ? wwwtop : NWWW;
+}
+
+int subpanel(Panel *obj, Panel *subj){
+ if(obj==0) return 0;
+ if(obj==subj) return 1;
+ for(obj=obj->child;obj;obj=obj->next)
+ if(subpanel(obj, subj)) return 1;
+ return 0;
+}
+/*
+ * Make sure that the keyboard focus is on-screen, by adjusting it to
+ * be the cmd entry if necessary.
+ */
+int adjkb(void){
+ Rtext *t;
+ int yoffs;
+ if(current){
+ yoffs=text->r.min.y-plgetpostextview(text);
+ for(t=current->text;t;t=t->next) if(!eqrect(t->r, Rect(0,0,0,0))){
+ if(t->r.max.y+yoffs>=text->r.min.y
+ && t->r.min.y+yoffs<text->r.max.y
+ && t->b==0
+ && subpanel(t->p, plkbfocus))
+ return 1;
+ }
+ }
+ plgrabkb(cmd);
+ return 0;
+}
+
+void scrollpanel(Panel *p, int dy, int whence)
+{
+ Scroll s;
+
+ s = plgetscroll(p);
+ 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(p, s);
+}
+
+void sidescroll(int dx, int whence)
+{
+ Scroll s;
+
+ s = plgetscroll(text);
+ switch(whence){
+ case 0:
+ s.pos.x = dx;
+ break;
+ case 1:
+ s.pos.x += dx;
+ break;
+ case 2:
+ s.pos.x = s.size.x+dx;
+ break;
+ }
+ if(s.pos.x > s.size.x - text->size.x + 5)
+ s.pos.x = s.size.x - text->size.x + 5;
+ if(s.pos.x < 0)
+ s.pos.x = 0;
+ plsetscroll(text, s);
+}
+
+void mkpanels(void){
+ Panel *p, *xbar, *ybar, *swap;
+ int xflags;
+
+ if(topxbar)
+ xflags=PACKN|USERFL;
+ else
+ xflags=PACKS|USERFL;
+ if(!visxbar)
+ xflags|=IGNORE;
+ menu3=plmenu(0, 0, buttons, PACKN|FILLX, hit3);
+ root=plpopup(root, EXPAND, 0, 0, menu3);
+ p=plgroup(root, PACKN|FILLX);
+ msg=pllabel(p, PACKN|FILLX, mothra);
+ plplacelabel(msg, PLACEW);
+ pllabel(p, PACKW, "Go:");
+ cmd=plentry(p, PACKN|FILLX, 0, "", docmd);
+ p=plgroup(root, PACKN|FILLX);
+ ybar=plscrollbar(p, PACKW);
+ list=pllist(p, PACKN|FILLX, genwww, 8, doprev);
+ plscroll(list, 0, ybar);
+ p=plgroup(root, PACKN|FILLX);
+ pllabel(p, PACKW, "Url:");
+ cururl=pllabel(p, PACKE|EXPAND, "---");
+ plplacelabel(cururl, PLACEW);
+ p=plgroup(root, PACKN|EXPAND);
+ ybar=plscrollbar(p, PACKW|USERFL);
+ xbar=plscrollbar(p, xflags);
+ text=pltextview(p, PACKE|EXPAND, Pt(0, 0), 0, dolink);
+ plscroll(text, xbar, ybar);
+ plgrabkb(cmd);
+ alt=plpopup(0, PACKE|EXPAND, 0, 0, menu3);
+ ybar=plscrollbar(alt, PACKW|USERFL);
+ xbar=plscrollbar(alt, xflags);
+ alttext=pltextview(alt, PACKE|EXPAND, Pt(0, 0), 0, dolink);
+ plscroll(alttext, xbar, ybar);
+ if(!defdisplay){
+ swap=root;
+ root=alt;
+ alt=swap;
+ swap=text;
+ text=alttext;
+ alttext=swap;
+ }
+}
+int cohort = -1;
+void killcohort(void){
+ int i;
+ for(i=0;i!=3;i++){ /* It's a long way to the kitchen */
+ postnote(PNGROUP, cohort, "kill\n");
+ sleep(1);
+ }
+}
+void catch(void*, char*){
+ noted(NCONT);
+}
+void dienow(void*, char*){
+ noted(NDFLT);
+}
+
+char* mkhome(void){
+ static char *home; /* where to put files */
+ char *henv, *tmp;
+ int f;
+
+ if(home == nil){
+ henv=getenv("home");
+ if(henv){
+ tmp = smprint("%s/lib", henv);
+ f=create(tmp, OREAD, DMDIR|0777);
+ if(f!=-1) close(f);
+ free(tmp);
+
+ home = smprint("%s/lib/mothra", henv);
+ f=create(home, OREAD, DMDIR|0777);
+ if(f!=-1) close(f);
+ free(henv);
+ }
+ else
+ home = strdup("/tmp");
+ }
+ return home;
+}
+
+void donecurs(void){
+ if(current && current->alldone==0)
+ esetcursor(&readingcurs);
+ else if(mothmode)
+ esetcursor(&mothcurs);
+ else
+ esetcursor(0);
+}
+
+void drawlock(int dolock){
+ static int ref = 0;
+ if(dolock){
+ if(ref++ == 0)
+ lockdisplay(display);
+ } else {
+ if(--ref == 0)
+ unlockdisplay(display);
+ }
+}
+
+void scrollto(char *tag);
+void search(void);
+
+extern char *mtpt; /* url */
+
+void main(int argc, char *argv[]){
+ Event e;
+ enum { Eplumb = 128, Ekick = 256 };
+ Plumbmsg *pm;
+ char *url;
+ int i;
+
+ quotefmtinstall();
+ fmtinstall('U', Ufmt);
+
+ ARGBEGIN{
+ case 'd': debug=1; break;
+ case 'v': verbose=1; break;
+ case 'k': killimgs=1; break;
+ case 'm':
+ if(mtpt = ARGF())
+ break;
+ case 'a': defdisplay=0; break;
+ default: goto Usage;
+ }ARGEND
+
+ /*
+ * so that we can stop all subprocesses with a note,
+ * and to isolate rendezvous from other processes
+ */
+ if(cohort=rfork(RFPROC|RFNOTEG|RFNAMEG|RFREND)){
+ atexit(killcohort);
+ notify(catch);
+ waitpid();
+ exits(0);
+ }
+ cohort = getpid();
+ atexit(killcohort);
+
+ switch(argc){
+ default:
+ Usage:
+ fprint(2, "usage: %s [-dvak] [-m mtpt] [url]\n", argv0);
+ exits("usage");
+ case 0:
+ url=getenv("url");
+ break;
+ case 1: url=argv[0]; break;
+ }
+ if(initdraw(0, 0, mothra) < 0)
+ sysfatal("initdraw: %r");
+ display->locking = 1;
+ chrwidth=stringwidth(font, "0");
+ pltabsize(chrwidth, 8*chrwidth);
+ einit(Emouse|Ekeyboard);
+ eplumb(Eplumb, "web");
+ if(pipe(kickpipe) < 0)
+ sysfatal("pipe: %r");
+ estart(Ekick, kickpipe[0], 256);
+ plinit();
+ if(debug) notify(dienow);
+ getfonts();
+ hrule=allocimage(display, Rect(0, 0, 1, 5), screen->chan, 1, DBlack);
+ if(hrule==0)
+ sysfatal("can't allocimage!");
+ draw(hrule, Rect(0,1,1,3), display->white, 0, ZP);
+ linespace=allocimage(display, Rect(0, 0, 1, 5), screen->chan, 1, DBlack);
+ if(linespace==0)
+ sysfatal("can't allocimage!");
+ bullet=allocimage(display, Rect(0,0,25, 8), screen->chan, 0, DBlack);
+ fillellipse(bullet, Pt(4,4), 3, 3, display->white, ZP);
+ mkpanels();
+ unlockdisplay(display);
+ eresized(0);
+ drawlock(1);
+
+ if(url && url[0])
+ geturl(url, -1, 1, 0);
+
+ mouse.buttons=0;
+ for(;;){
+ if(mouse.buttons==0 && current){
+ if(current->finished){
+ updtext(current);
+ if(current->url->tag[0])
+ scrollto(current->url->tag);
+ current->finished=0;
+ current->changed=0;
+ current->alldone=1;
+ message(mothra);
+ donecurs();
+ }
+ }
+
+ drawlock(0);
+ i=event(&e);
+ drawlock(1);
+
+ switch(i){
+ case Ekick:
+ if(mouse.buttons==0 && current && current->changed){
+ if(!current->finished)
+ updtext(current);
+ current->changed=0;
+ }
+ break;
+ case Ekeyboard:
+ switch(e.kbdc){
+ default:
+Plkey:
+ adjkb();
+ plkeyboard(e.kbdc);
+ break;
+ case Khome:
+ scrollpanel(text, 0, 0);
+ break;
+ case Kup:
+ scrollpanel(text, -text->size.y/4, 1);
+ break;
+ case Kpgup:
+ scrollpanel(text, -text->size.y/2, 1);
+ break;
+ case Kdown:
+ scrollpanel(text, text->size.y/4, 1);
+ break;
+ case Kpgdown:
+ scrollpanel(text, text->size.y/2, 1);
+ break;
+ case Kend:
+ scrollpanel(text, -text->size.y, 2);
+ break;
+ case Kack:
+ search();
+ break;
+ case Kright:
+ if(plkbfocus)
+ goto Plkey;
+ sidescroll(text->size.x/4, 1);
+ break;
+ case Kleft:
+ if(plkbfocus)
+ goto Plkey;
+ sidescroll(-text->size.x/4, 1);
+ break;
+ }
+ break;
+ case Emouse:
+ mouse=e.mouse;
+ if(mouse.buttons & (8|16) && ptinrect(mouse.xy, list->r) && defdisplay){
+ if(mouse.buttons & 8)
+ scrollpanel(list, list->r.min.y - mouse.xy.y, 1);
+ else
+ scrollpanel(list, mouse.xy.y - list->r.min.y, 1);
+ break;
+ }
+ if(mouse.buttons & (8|16) && ptinrect(mouse.xy, text->r)){
+ if(mouse.buttons & 8)
+ scrollpanel(text, text->r.min.y - mouse.xy.y, 1);
+ else
+ scrollpanel(text, mouse.xy.y - text->r.min.y, 1);
+ break;
+ }
+ plmouse(root, &mouse);
+ if(mouse.buttons == 1 && root->lastmouse == root)
+ plgrabkb(nil);
+ break;
+ case Eplumb:
+ pm=e.v;
+ if(pm->ndata > 0)
+ geturl(pm->data, -1, 1, 0);
+ plumbfree(pm);
+ break;
+ }
+ }
+}
+int confirm(int b){
+ Mouse down, up;
+ esetcursor(&confirmcursor);
+ do down=emouse(); while(!down.buttons);
+ do up=emouse(); while(up.buttons);
+ donecurs();
+ return down.buttons==(1<<(b-1));
+}
+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(msg, PACKN|FILLX, buf);
+ if(defdisplay) pldraw(msg, screen);
+}
+void htmlerror(char *name, int line, char *m, ...){
+ static char buf[1024];
+ char *out;
+ va_list args;
+ if(verbose){
+ va_start(args, m);
+ out=buf+snprint(buf, sizeof(buf), "%s: line %d: ", name, line);
+ out+=vsnprint(out, sizeof(buf)-(out-buf)-1, m, args);
+ va_end(args);
+ *out='\0';
+ fprint(2, "%s\n", buf);
+ }
+}
+void eresized(int new){
+ Rectangle r;
+
+ drawlock(1);
+ if(new && getwindow(display, Refnone) == -1) {
+ fprint(2, "getwindow: %r\n");
+ exits("getwindow");
+ }
+ r=screen->r;
+ plpack(root, r);
+ plpack(alt, r);
+ pldraw(cmd, screen); /* put cmd box on screen for alt display */
+ pldraw(root, screen);
+ flushimage(display, 1);
+ drawlock(0);
+}
+void *emalloc(int n){
+ void *v;
+ v=malloc(n);
+ if(v==0)
+ sysfatal("out of memory");
+ memset(v, 0, n);
+ setmalloctag(v, getcallerpc(&n));
+ return v;
+}
+void nstrcpy(char *to, char *from, int len){
+ strncpy(to, from, len);
+ to[len-1] = 0;
+}
+
+char *genwww(Panel *, int index){
+ static char buf[1024];
+ Www *w;
+ int i;
+
+ if(index >= nwww())
+ return 0;
+ i = wwwtop-index-1;
+ w = www(i);
+ if(!w->url)
+ return 0;
+ if(w->title[0]!='\0'){
+ w->gottitle=1;
+ snprint(buf, sizeof(buf), "%2d %s", i+1, w->title);
+ } else
+ snprint(buf, sizeof(buf), "%2d %s", i+1, urlstr(w->url));
+ return buf;
+}
+
+void scrollto(char *tag){
+ Rtext *tp;
+ Action *ap;
+ if(current == nil || text == nil)
+ return;
+ if(tag && tag[0]){
+ for(tp=current->text;tp;tp=tp->next){
+ ap=tp->user;
+ if(ap && ap->name && strcmp(ap->name, tag)==0){
+ current->yoffs=tp->topy;
+ break;
+ }
+ }
+ }
+ plsetpostextview(text, current->yoffs);
+}
+
+/*
+ * selected text should be a url.
+ */
+void setcurrent(int index, char *tag){
+ Www *new;
+ int i;
+ new=www(index);
+ if(new==current && (tag==0 || tag[0]==0)) return;
+ if(current)
+ current->yoffs=plgetpostextview(text);
+ current=new;
+ plinitlabel(cururl, PACKE|EXPAND, current->url->fullname);
+ if(defdisplay) pldraw(cururl, screen);
+ plinittextview(text, PACKE|EXPAND, Pt(0, 0), current->text, dolink);
+ scrollto(tag);
+ if((i = open("/dev/label", OWRITE)) >= 0){
+ fprint(i, "%s %s", mothra, current->url->fullname);
+ close(i);
+ }
+ donecurs();
+}
+char *arg(char *s){
+ do ++s; while(*s==' ' || *s=='\t');
+ return s;
+}
+void save(int ifd, char *name){
+ char buf[NNAME+64];
+ int ofd;
+ if(ifd < 0){
+ message("save: %s: %r", name);
+ return;
+ }
+ 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);
+ donecurs();
+}
+void screendump(char *name, int full){
+ Image *b;
+ int fd;
+ fd=create(name, OWRITE, 0666);
+ if(fd==-1){
+ message("can't create %s", name);
+ return;
+ }
+ if(full){
+ writeimage(fd, screen, 0);
+ } else {
+ if((b=allocimage(display, text->r, screen->chan, 0, DNofill)) == nil){
+ message("can't allocate image");
+ close(fd);
+ return;
+ }
+ draw(b, b->r, screen, 0, b->r.min);
+ writeimage(fd, b, 0);
+ freeimage(b);
+ }
+ close(fd);
+}
+
+/*
+ * convert a url into a local file name.
+ */
+char *urltofile(Url *url){
+ char *name, *slash;
+ if(url == nil)
+ return nil;
+ name = urlstr(url);
+ if(name == nil || name[0] == 0)
+ name = "/";
+ if(slash = strrchr(name, '/'))
+ name = slash+1;
+ if(name[0] == 0)
+ name = "index";
+ return name;
+}
+
+/*
+ * user typed a command.
+ */
+void docmd(Panel *p, char *s){
+ char buf[NNAME];
+ int c;
+
+ USED(p);
+ while(*s==' ' || *s=='\t') s++;
+ /*
+ * Non-command does a get on the url
+ */
+ if(s[0]!='\0' && s[1]!='\0' && s[1]!=' ')
+ geturl(s, -1, 0, 0);
+ else switch(c = s[0]){
+ default:
+ message("Unknown command %s", s);
+ break;
+ case 'a':
+ s = arg(s);
+ if(*s=='\0' && selection)
+ hit3(3, 0);
+ break;
+ case 'd':
+ s = arg(s);
+ if(*s){
+ s = smprint("https://lite.duckduckgo.com/lite/?q=%U&kd=-1", s);
+ if(s != nil)
+ geturl(s, -1, 0, 0);
+ free(s);
+ }else
+ message("Usage: d text");
+ break;
+ case 'g':
+ s = arg(s);
+ if(*s=='\0'){
+ case 'r':
+ if(selection)
+ s = urlstr(selection);
+ else
+ message("no url selected");
+ }
+ geturl(s, -1, 0, 0);
+ break;
+ case 'j':
+ s = arg(s);
+ if(*s)
+ doprev(nil, 1, wwwtop-atoi(s));
+ else
+ message("Usage: j index");
+ break;
+ case 'm':
+ mothon(current, !mothmode);
+ break;
+ case 'k':
+ killimgs = !killimgs;
+ if (killimgs)
+ killpix(current);
+ break;
+ case 'w':
+ case 'W':
+ s = arg(s);
+ if(s==0 || *s=='\0'){
+ snprint(buf, sizeof(buf), "dump.bit");
+ if(eenter("Screendump to", buf, sizeof(buf), &mouse) <= 0)
+ break;
+ s = buf;
+ }
+ screendump(s, c == 'W');
+ break;
+ case 's':
+ s = arg(s);
+ if(!selection){
+ message("no url selected");
+ break;
+ }
+ if(s==0 || *s=='\0'){
+ snprint(buf, sizeof(buf), "%s", urltofile(selection));
+ if(eenter("Save to", buf, sizeof(buf), &mouse) <= 0)
+ break;
+ s = buf;
+ }
+ save(urlget(selection, -1), s);
+ break;
+ case 'q':
+ exits(0);
+ }
+ plinitentry(cmd, EXPAND, 0, "", docmd);
+ pldraw(root, screen);
+}
+
+void regerror(char *msg)
+{
+ werrstr("regerror: %s", msg);
+}
+
+void search(void){
+ static char last[256];
+ char buf[256];
+ Reprog *re;
+ Rtext *tp;
+
+ for(;;){
+ if(current == nil || current->text == nil || 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=current->text;tp;tp=tp->next)
+ if(tp->flags & PL_SEL)
+ break;
+ if(tp == nil)
+ tp = current->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(text, tp->topy);
+ break;
+ }
+ tp = tp->next;
+ }
+ free(re);
+ updtext(current);
+ }
+}
+
+void hiturl(int buttons, char *url, int map){
+ switch(buttons){
+ case 1: geturl(url, -1, 0, map); break;
+ case 2: urlresolve(selurl(url)); break;
+ case 4: message("Button 3 hit on url can't happen!"); break;
+ }
+}
+
+/*
+ * user selected from the list of available pages
+ */
+void doprev(Panel *p, int buttons, int index){
+ int i;
+ USED(p);
+ if(index < 0 || index >= nwww())
+ return;
+ i = wwwtop-index-1;
+ switch(buttons){
+ case 1: setcurrent(i, 0); /* no break ... */
+ case 2: selurl(www(i)->url->fullname); break;
+ case 4: message("Button 3 hit on page can't happen!"); break;
+ }
+}
+
+/*
+ * Follow an html link
+ */
+void dolink(Panel *p, int buttons, Rtext *word){
+ Action *a;
+
+ a=word->user;
+ if(a == nil || (a->link == nil && a->image == nil))
+ return;
+ if(mothmode)
+ hiturl(buttons, a->image ? a->image : a->link, 0);
+ else if(a->link){
+ if(a->ismap){
+ char mapurl[NNAME];
+ Point coord;
+ int yoffs;
+
+ yoffs=plgetpostextview(p);
+ coord=subpt(subpt(mouse.xy, word->r.min), p->r.min);
+ snprint(mapurl, sizeof(mapurl), "%s?%d,%d", a->link, coord.x, coord.y+yoffs);
+ hiturl(buttons, mapurl, 1);
+ } else
+ hiturl(buttons, a->link, 0);
+ }
+}
+
+void filter(int fd, char *cmd){
+ switch(rfork(RFFDG|RFPROC|RFMEM|RFREND|RFNOWAIT|RFNOTEG)){
+ case -1:
+ message("Can't fork!");
+ break;
+ case 0:
+ dupfds(fd, 1, 2, -1);
+ execl("/bin/rc", "rc", "-c", cmd, nil);
+ _exits(0);
+ }
+ close(fd);
+}
+void gettext(Www *w, int fd, int type){
+ switch(rfork(RFFDG|RFPROC|RFMEM|RFNOWAIT)){
+ case -1:
+ message("Can't fork, please wait");
+ break;
+ case 0:
+ if(type==HTML)
+ plrdhtml(w->url->fullname, fd, w, killimgs);
+ else
+ plrdplain(w->url->fullname, fd, w);
+ _exits(0);
+ }
+ close(fd);
+}
+
+void freetext(Rtext *t){
+ Rtext *tt;
+ Action *a;
+
+ tt = t;
+ for(; t!=0; t = t->next){
+ t->b=0;
+ free(t->text);
+ t->text=0;
+ if(a = t->user){
+ t->user=0;
+ free(a->image);
+ free(a->link);
+ free(a->name);
+ free(a);
+ }
+ }
+ plrtfree(tt);
+}
+
+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<n; i++){
+ if(strstr(dir[i].name, "ctl"))
+ continue;
+ fd = atoi(dir[i].name);
+ if(fd >= mfd)
+ close(fd);
+ }
+ free(dir);
+}
+
+int pipeline(int fd, char *fmt, ...)
+{
+ char buf[80], *argv[4];
+ va_list arg;
+ int pfd[2];
+
+ va_start(arg, fmt);
+ vsnprint(buf, sizeof buf, fmt, arg);
+ va_end(arg);
+
+ if(pipe(pfd) < 0){
+ Err:
+ close(fd);
+ werrstr("pipeline for %s failed: %r", buf);
+ return -1;
+ }
+ switch(rfork(RFPROC|RFMEM|RFFDG|RFREND|RFNOWAIT)){
+ case -1:
+ close(pfd[0]);
+ close(pfd[1]);
+ goto Err;
+ case 0:
+ dupfds(fd, pfd[1], 2, -1);
+ argv[0] = "rc";
+ argv[1] = "-c";
+ argv[2] = buf;
+ argv[3] = nil;
+ exec("/bin/rc", argv);
+ _exits(0);
+ }
+ close(fd);
+ close(pfd[1]);
+ return pfd[0];
+}
+
+char*
+urlstr(Url *url){
+ if(url->fullname[0])
+ return url->fullname;
+ return url->reltext;
+}
+
+Url *copyurl(Url *u){
+ Url *v;
+ v=emalloc(sizeof(Url));
+ *v=*u;
+ v->reltext = strdup(u->reltext);
+ v->basename = strdup(u->basename);
+ return v;
+}
+
+void freeurl(Url *u){
+ free(u->reltext);
+ free(u->basename);
+ free(u);
+}
+
+void seturl(Url *url, char *urlname, char *base){
+ url->reltext = strdup(urlname);
+ url->basename = strdup(base);
+ url->fullname[0] = 0;
+ url->tag[0] = 0;
+ url->map = 0;
+}
+
+Url* selurl(char *urlname){
+ Url *last;
+
+ last=selection;
+ selection=emalloc(sizeof(Url));
+ seturl(selection, urlname, current ? current->url->fullname : "");
+ if(last) freeurl(last);
+ message("selected: %s", urlstr(selection));
+ plgrabkb(cmd); /* for snarf */
+ return selection;
+}
+
+/*
+ * get the file at the given url
+ */
+void geturl(char *urlname, int post, int plumb, int map){
+ int i, fd, typ;
+ char cmd[NNAME];
+ ulong n;
+ Www *w;
+
+ if(*urlname == '#' && post < 0){
+ scrollto(urlname+1);
+ return;
+ }
+
+ selurl(urlname);
+ selection->map=map;
+
+ message("getting %s", urlstr(selection));
+ esetcursor(&patientcurs);
+ for(;;){
+ if((fd=urlget(selection, post)) < 0){
+ message("%r");
+ break;
+ }
+ message("getting %s", selection->fullname);
+ if(mothmode && !plumb)
+ typ = -1;
+ else if((typ = mimetotype(selection->contenttype)) < 0)
+ typ = snooptype(fd);
+
+ switch(typ){
+ default:
+ if(plumb){
+ message("unknown file type");
+ close(fd);
+ break;
+ }
+ snprint(cmd, sizeof(cmd), "%s", urltofile(selection));
+ if(eenter("Save to", cmd, sizeof(cmd), &mouse) <= 0){
+ close(fd);
+ break;
+ }
+ save(fd, cmd);
+ break;
+ case HTML:
+ fd = pipeline(fd, "exec uhtml");
+ case PLAIN:
+ n=0;
+ for(i=wwwtop-1; i>=0 && i!=(wwwtop-NWWW-1); i--){
+ w = www(i);
+ n += countpix(w->pix);
+ if(n >= NPIXMB*1024*1024)
+ killpix(w);
+ }
+ w = www(i = wwwtop++);
+ if(i >= NWWW){
+ /* wait for the reader to finish the document */
+ while(!w->finished && !w->alldone){
+ drawlock(0);
+ sleep(10);
+ drawlock(1);
+ }
+ freetext(w->text);
+ freeform(w->form);
+ freepix(w->pix);
+ freeurl(w->url);
+ memset(w, 0, sizeof(*w));
+ }
+ if(selection->map)
+ w->url=copyurl(current->url);
+ else
+ w->url=copyurl(selection);
+ w->finished = 0;
+ w->alldone = 0;
+ gettext(w, fd, typ);
+ if(rfork(RFPROC|RFMEM|RFNOWAIT) == 0){
+ for(;;){
+ sleep(1000);
+ if(w->finished || w->alldone)
+ break;
+ if(w->changed)
+ write(kickpipe[1], "C", 1);
+ }
+ _exits(0);
+ }
+ plinitlist(list, PACKN|FILLX, genwww, 8, doprev);
+ if(defdisplay) pldraw(list, screen);
+ setcurrent(i, selection->tag);
+ break;
+ case GIF:
+ case JPEG:
+ case PNG:
+ case BMP:
+ case PAGE:
+ filter(fd, "exec page -w");
+ break;
+ }
+ break;
+ }
+ donecurs();
+}
+void updtext(Www *w){
+ Rtext *t;
+ Action *a;
+ if(defdisplay && w->gottitle==0 && w->title[0]!='\0')
+ pldraw(list, screen);
+ for(t=w->text;t;t=t->next){
+ a=t->user;
+ if(a){
+ if(a->field)
+ mkfieldpanel(t);
+ a->field=0;
+ }
+ }
+ if(w != current)
+ return;
+ w->yoffs=plgetpostextview(text);
+ plinittextview(text, PACKE|EXPAND, Pt(0, 0), w->text, dolink);
+ plsetpostextview(text, w->yoffs);
+ pldraw(text, screen);
+}
+
+void finish(Www *w){
+ w->finished = 1;
+ write(kickpipe[1], "F", 1);
+}
+
+void
+mothon(Www *w, int on)
+{
+ Rtext *t, *x;
+ Action *a, *ap;
+
+ if(w==0 || mothmode==on)
+ return;
+ if(mothmode = on)
+ message("moth mode!");
+ else
+ message(mothra);
+ /*
+ * insert or remove artificial links to the href for
+ * images that are also links
+ */
+ for(t=w->text;t;t=t->next){
+ a=t->user;
+ if(a == nil || a->image == nil)
+ continue;
+ if(a->link == nil){
+ if(on)
+ t->flags |= PL_HOT;
+ else
+ t->flags &= ~PL_HOT;
+ continue;
+ }
+ x = t->next;
+ if(on){
+ t->next = nil;
+ ap=emalloc(sizeof(Action));
+ ap->link = strdup(a->link);
+ plrtstr(&t->next, 0, 0, 0, t->font, strdup("->"), PL_HOT, ap);
+ t->next->next = x;
+ } else {
+ if(x) {
+ t->next = x->next;
+ x->next = nil;
+ freetext(x);
+ }
+ }
+ }
+ updtext(w);
+ donecurs();
+}
+
+void killpix(Www *w){
+ Rtext *t;
+
+ if(w==0 || !w->finished && !w->alldone)
+ return;
+ for(t=w->text; t; t=t->next)
+ if(t->b && t->user)
+ t->b=0;
+ freepix(w->pix);
+ w->pix=0;
+ updtext(w);
+}
+void snarf(Panel *p){
+ if(p==0 || p==cmd){
+ if(selection){
+ plputsnarf(urlstr(selection));
+ plsnarf(text);
+ }else
+ message("no url selected");
+ }else
+ plsnarf(p);
+}
+void paste(Panel *p){
+ if(p==0) p=cmd;
+ plpaste(p);
+}
+void hit3(int button, int item){
+ char buf[1024];
+ char name[NNAME];
+ char *s;
+ Panel *swap;
+ int fd;
+ USED(button);
+ switch(item){
+ case 0:
+ swap=root;
+ root=alt;
+ alt=swap;
+ if(current)
+ current->yoffs=plgetpostextview(text);
+ swap=text;
+ text=alttext;
+ alttext=swap;
+ defdisplay=!defdisplay;
+ plpack(root, screen->r);
+ if(current){
+ plinittextview(text, PACKE|EXPAND, Pt(0, 0), current->text, dolink);
+ plsetpostextview(text, current->yoffs);
+ }
+ pldraw(root, screen);
+ break;
+ case 1:
+ mothon(current, !mothmode);
+ break;
+ case 2:
+ snarf(plkbfocus);
+ break;
+ case 3:
+ paste(plkbfocus);
+ break;
+ case 4:
+ if(plkbfocus==nil || plkbfocus==cmd){
+ if(text==nil || text->snarf==nil || selection==nil)
+ return;
+ if((s=text->snarf(text))==nil)
+ s=smprint("%s", urlstr(selection));
+ }else
+ if((s=plkbfocus->snarf(plkbfocus))==nil)
+ return;
+ if((fd=plumbopen("send", OWRITE))<0){
+ message("can't plumb");
+ free(s);
+ return;
+ }
+ plumbsendtext(fd, "mothra", nil, getwd(buf, sizeof buf), s);
+ close(fd);
+ free(s);
+ break;
+ case 5:
+ search();
+ break;
+ case 6:
+ if(!selection){
+ message("no url selected");
+ break;
+ }
+ snprint(name, sizeof(name), "%s/hit.html", mkhome());
+ fd=open(name, OWRITE);
+ if(fd==-1){
+ fd=create(name, OWRITE, 0666);
+ if(fd==-1){
+ message("can't open %s", name);
+ return;
+ }
+ fprint(fd, "<html><head><title>Hit List</title></head>\n");
+ fprint(fd, "<body><h1>Hit list</h1>\n");
+ }
+ seek(fd, 0, 2);
+ fprint(fd, "<p><a href=\"%s\">%s</a>\n", urlstr(selection), urlstr(selection));
+ close(fd);
+ break;
+ case 7:
+ snprint(name, sizeof(name), "file:%s/hit.html", mkhome());
+ geturl(name, -1, 1, 0);
+ break;
+ case 8:
+ if(confirm(3))
+ exits(0);
+ break;
+ }
+}
binary files /dev/null b/mothra.gif differ
--- /dev/null
+++ b/mothra.h
@@ -1,0 +1,109 @@
+enum{
+ NWWW=64, /* # of pages we hold in the log */
+ NXPROC=5, /* # of parallel procs loading the pix */
+ NPIXMB=8, /* megabytes of image data to keep arround */
+ NNAME=512,
+ NLINE=256,
+ NAUTH=128,
+ NTITLE=81, /* length of title (including nul at end) */
+ NLABEL=50, /* length of option name in forms */
+ NREDIR=10, /* # of redirections we'll tolerate before declaring a loop */
+};
+
+typedef struct Action Action;
+typedef struct Url Url;
+typedef struct Www Www;
+typedef struct Field Field;
+struct Action{
+ char *image;
+ Field *field;
+ char *link;
+ char *name;
+ int ismap;
+ int width;
+ int height;
+};
+struct Url{
+ char *basename;
+ char *reltext;
+ char fullname[NNAME];
+ char tag[NNAME];
+ char contenttype[NNAME];
+ int map; /* is this an image map? */
+};
+struct Www{
+ Url *url;
+ void *pix;
+ void *form;
+ char title[NTITLE];
+ Rtext *text;
+ int yoffs;
+ int gottitle; /* title got drawn */
+ int changed; /* reader sets this every time it updates page */
+ int finished; /* reader sets this when done */
+ int alldone; /* page will not change further -- used to adjust cursor */
+};
+
+enum{
+ PLAIN,
+ HTML,
+
+ GIF,
+ JPEG,
+ PNG,
+ BMP,
+ ICO,
+
+ PAGE,
+};
+
+/*
+ * authentication types
+ */
+enum{
+ ANONE,
+ ABASIC,
+};
+
+Image *hrule, *bullet, *linespace;
+int chrwidth; /* nominal width of characters in font */
+Panel *text; /* Panel displaying the current www page */
+int debug; /* command line flag */
+
+/*
+ * HTTP methods
+ */
+enum{
+ GET=1,
+ POST,
+};
+
+void finish(Www *w);
+void plrdhtml(char *, int, Www *, int);
+void plrdplain(char *, int, Www *);
+void htmlerror(char *, int, char *, ...); /* user-supplied routine */
+void seturl(Url *, char *, char *);
+void freeurl(Url *);
+Url *selurl(char *);
+void getpix(Rtext *, Www *);
+ulong countpix(void *p);
+void freepix(void *p);
+void dupfds(int fd, ...);
+int pipeline(int fd, char *fmt, ...);
+void getfonts(void);
+void *emalloc(int);
+void nstrcpy(char *to, char *from, int len);
+void freeform(void *p);
+int Ufmt(Fmt *f);
+#pragma varargck type "U" char*
+void message(char *, ...);
+int filetype(int, char *, int);
+int mimetotype(char *);
+int snooptype(int);
+void mkfieldpanel(Rtext *);
+void geturl(char *, int, int, int);
+char *urlstr(Url *);
+int urlpost(Url*, char*);
+int urlget(Url*, int);
+int urlresolve(Url *);
+Mouse mouse;
binary files /dev/null b/mothracompat.gif differ
binary files /dev/null b/mothraenhanced.gif differ
--- /dev/null
+++ b/rdhtml.c
@@ -1,0 +1,1242 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#include "mothra.h"
+#include "html.h"
+#include "rtext.h"
+
+typedef struct Fontdata Fontdata;
+struct Fontdata{
+ char *name;
+ Font *font;
+ int space;
+}fontlist[4][4]={
+/* original */
+ "lucidasans/unicode.7", 0, 0,
+ "lucidasans/unicode.8", 0, 0,
+ "lucidasans/unicode.10", 0, 0,
+ "lucidasans/unicode.13", 0, 0,
+
+ "lucidasans/italicunicode.7", 0, 0,
+ "lucidasans/italicunicode.8", 0, 0,
+ "lucidasans/italicunicode.10", 0, 0,
+ "lucidasans/italicunicode.13", 0, 0,
+
+ "lucidasans/boldunicode.7", 0, 0,
+ "lucidasans/boldunicode.8", 0, 0,
+ "lucidasans/boldunicode.10", 0, 0,
+ "lucidasans/boldunicode.13", 0, 0,
+
+ "lucidasans/typeunicode.7", 0, 0,
+ "pelm/unicode.8", 0, 0,
+ "lucidasans/typeunicode.12", 0, 0,
+ "lucidasans/typeunicode.16", 0, 0,
+};
+
+static struct{
+ char *prefix;
+ int len;
+}links[]={
+ {"http://", 7},
+ {"https://", 8},
+ {"gemini://", 9},
+ {"ftp://", 6},
+};
+
+Font *pl_whichfont(int f, int s, int *space){
+ char name[NNAME];
+
+ assert(f >= 0 && f < 4);
+ assert(s >= 0 && s < 4);
+
+ if(fontlist[f][s].font==0){
+ snprint(name, sizeof(name), "/lib/font/bit/%s.font", fontlist[f][s].name);
+ fontlist[f][s].font=openfont(display, name);
+ if(fontlist[f][s].font==0) fontlist[f][s].font=font;
+ fontlist[f][s].space=stringwidth(fontlist[f][s].font, "0");
+ }
+ if(space)
+ *space = fontlist[f][s].space;
+ return fontlist[f][s].font;
+}
+
+void getfonts(void){
+ int f, s;
+ for(f=0;f!=4;f++)
+ for(s=0;s!=4;s++)
+ pl_whichfont(f, s, nil);
+}
+void pl_pushstate(Hglob *g, int t){
+ ++g->state;
+ if(g->state==&g->stack[NSTACK]){
+ htmlerror(g->name, g->lineno, "stack overflow at <%s>", tag[t].name);
+ --g->state;
+ }
+ g->state[0]=g->state[-1];
+ g->state->tag=t;
+
+ if(g->state->name)
+ g->state->name = strdup(g->state->name);
+ if(g->state->link)
+ g->state->link = strdup(g->state->link);
+ if(g->state->image)
+ g->state->image = strdup(g->state->image);
+}
+void pl_popstate(Stack *state){
+ free(state->name);
+ state->name=0;
+ free(state->link);
+ state->link=0;
+ free(state->image);
+ state->image=0;
+}
+
+void pl_linespace(Hglob *g){
+ plrtbitmap(&g->dst->text, 1000000, 0, 0, linespace, 0, 0);
+ g->para=0;
+ g->linebrk=0;
+}
+
+int strtolength(Hglob *g, int dir, char *str){
+ double f;
+ Point p;
+
+ f = atof(str);
+ if(cistrstr(str, "%"))
+ return 0;
+ if(cistrstr(str, "em")){
+ p=stringsize(pl_whichfont(g->state->font, g->state->size, nil), "M");
+ return floor(f*((dir==HORIZ) ? p.x : p.y));
+ }
+ return floor(f);
+}
+
+void pl_htmloutput(Hglob *g, int nsp, char *s, Field *field){
+ Font *f;
+ int space, indent, flags, voff;
+ Action *ap;
+ if(g->state->tag==Tag_title
+/* || g->state->tag==Tag_textarea */
+ || g->state->tag==Tag_select){
+ if(s){
+ if(g->tp!=g->text && g->tp!=g->etext && g->tp[-1]!=' ')
+ *g->tp++=' ';
+ while(g->tp!=g->etext && *s) *g->tp++=*s++;
+ if(g->state->tag==Tag_title) g->dst->changed=1;
+ *g->tp='\0';
+ }
+ return;
+ }
+ voff = 0;
+ f=pl_whichfont(g->state->font, g->state->size, &space);
+ if(g->state->sub){
+ voff = g->state->sub * f->ascent / 2;
+ g->state->size = SMALL;
+ f=pl_whichfont(g->state->font, g->state->size, &space);
+ }
+ indent=g->state->margin;
+ if(g->para){
+ space=1000000;
+ indent+=g->state->indent;
+ }
+ else if(g->linebrk)
+ space=1000000;
+ else if(nsp<=0)
+ space=0;
+ if(g->state->image==0 && g->state->link==0 && g->state->name==0 && field==0)
+ ap=0;
+ else{
+ ap=emalloc(sizeof(Action));
+ if(g->state->image)
+ ap->image = strdup(g->state->image);
+ if(g->state->link)
+ ap->link = strdup(g->state->link);
+ if(g->state->name)
+ ap->name = strdup(g->state->name);
+ ap->ismap=g->state->ismap;
+ ap->width=g->state->width;
+ ap->height=g->state->height;
+ ap->field=field;
+ }
+ if(space<0) space=0;
+ if(indent<0) indent=0;
+ if(g->state->pre && s[0]=='\t'){
+ space=0;
+ while(s[0]=='\t'){
+ space++;
+ s++;
+ }
+ space=PL_TAB|space;
+ if(g->linebrk){
+ indent=space;
+ space=1000000;
+ }
+ }
+ flags = 0;
+ if(g->state->link)
+ flags |= PL_HOT;
+ if(g->state->strike)
+ flags |= PL_STR;
+ plrtstr(&g->dst->text, space, indent, voff, f, strdup(s), flags, ap);
+ g->para=0;
+ g->linebrk=0;
+ g->dst->changed=1;
+}
+
+/*
+ * Buffered read, no translation
+ * Save in cache.
+ */
+int pl_bread(Hglob *g){
+ int n, c;
+ char err[1024];
+ if(g->hbufp==g->ehbuf){
+ n=read(g->hfd, g->hbuf, NHBUF);
+ if(n<=0){
+ if(n<0){
+ snprint(err, sizeof(err), "%r reading %s", g->name);
+ pl_htmloutput(g, 1, err, 0);
+ }
+ g->heof=1;
+ return EOF;
+ }
+ g->hbufp=g->hbuf;
+ g->ehbuf=g->hbuf+n;
+ }
+ c=*g->hbufp++&255;
+ if(c=='\n') g->lineno++;
+ return c;
+}
+/*
+ * Read a character, translating \r\n, \n\r, \r and \n into \n
+ * convert to runes.
+ */
+int pl_readc(Hglob *g){
+ static int peek=-1;
+ char crune[UTFmax+1];
+ int c, n;
+ Rune r;
+
+ if(peek!=-1){
+ c=peek;
+ peek=-1;
+ }
+ else
+ c=pl_bread(g);
+ if(c=='\r'){
+ c=pl_bread(g);
+ if(c!='\n') peek=c;
+ return '\n';
+ }
+ if(c=='\n'){
+ c=pl_bread(g);
+ if(c!='\r') peek=c;
+ return '\n';
+ }
+
+ if(c < Runeself)
+ return c;
+
+ crune[0]=c;
+ for (n=1; n<=sizeof(crune); n++){
+ if(fullrune(crune, n)){
+ chartorune(&r, crune);
+ return r;
+ }
+ c=pl_bread(g);
+ if(c==EOF)
+ return EOF;
+ crune[n]=c;
+ }
+ return c;
+}
+void pl_putback(Hglob *g, int c){
+ if(g->npeekc==NPEEKC) htmlerror(g->name, g->lineno, "too much putback!");
+ else if(c!=EOF) g->peekc[g->npeekc++]=c;
+}
+int pl_nextc(Hglob *g){
+ int c;
+
+ if(g->heof) return EOF;
+ if(g->npeekc!=0) return g->peekc[--g->npeekc];
+ c=pl_readc(g);
+ if(c=='<'){
+ c=pl_readc(g);
+ if(c=='/'){
+ c=pl_readc(g);
+ pl_putback(g, c);
+ pl_putback(g, '/');
+ if('a'<=c && c<='z' || 'A'<=c && c<='Z') return STAG;
+ return '<';
+ }
+ pl_putback(g, c);
+ if(c=='!' || 'a'<=c && c<='z' || 'A'<=c && c<='Z' || c=='?') return STAG;
+ return '<';
+ }
+ if(c=='>') return ETAG;
+ return c;
+}
+
+char *unquot(char *src){
+ char *e, *dst;
+ int len;
+
+ e=0;
+ while(*src && strchr(" \t\r\n", *src))
+ src++;
+ if(*src=='\'' || *src=='"'){
+ e=strrchr(src+1, *src);
+ src++;
+ }
+ if(e==0) e=strchr(src, 0);
+ len=e-src;
+ dst = emalloc(len+1);
+ memmove(dst, src, len);
+ dst[len]=0;
+ return dst;
+}
+int alnumchar(int c){
+ return 'a'<=c && c<='z' || 'A'<=c && c<='Z' || '0'<=c && c<='9';
+}
+int entchar(int c){
+ return c=='#' || alnumchar(c);
+}
+
+/* return url if text token looks like a hyperlink */
+char *linkify(char *s){
+ int i;
+ if(s == 0 && s[0] == 0)
+ return 0;
+ for(i = 0; i < nelem(links); i++)
+ if(!cistrncmp(s, links[i].prefix, links[i].len))
+ return strdup(s);
+ if(!cistrncmp(s, "www.", 4)){
+ int d, i;
+
+ d = 1;
+ for(i=4; s[i]; i++){
+ if(s[i] == '.'){
+ if(s[i-1] == '.')
+ return 0;
+ d++;
+ } else if(!alnumchar(s[i]))
+ break;
+ }
+ if(d >= 2)
+ return smprint("http://%s", s);
+ }
+ return 0;
+}
+
+/*
+ * remove entity references, in place.
+ * Potential bug:
+ * This doesn't work if removing an entity reference can lengthen the string!
+ * Fortunately, this doesn't happen.
+ */
+void pl_rmentities(Hglob *, char *s){
+ char *t, *u, c, svc;
+ t=s;
+ do{
+ c=*s++;
+ if(c=='&'
+ && ((*s=='#' && strchr("0123456789Xx", s[1]))
+ || 'a'<=*s && *s<='z'
+ || 'A'<=*s && *s<='Z')){
+ u=s;
+ while(entchar(*s)) s++;
+ svc=*s;
+ *s = 0;
+ if(svc==';') s++;
+ if(strcmp(u, "lt") == 0)
+ *t++='<';
+ else if(strcmp(u, "gt") == 0)
+ *t++='>';
+ else if(strcmp(u, "quot") == 0)
+ *t++='"';
+ else if(strcmp(u, "apos") == 0)
+ *t++='\'';
+ else if(strcmp(u, "amp") == 0)
+ *t++='&';
+ else {
+ if(svc==';') s--;
+ *s=svc;
+ *t++='&';
+ while(u<s)
+ *t++=*u++;
+ }
+ }
+ else *t++=c;
+ }while(c);
+}
+/*
+ * Skip over white space
+ */
+char *pl_whitespace(char *s){
+ while(*s==' ' || *s=='\t' || *s=='\n' || *s=='\r') s++;
+ return s;
+}
+/*
+ * Skip over HTML word
+ */
+char *pl_word(char *s){
+ if ('a'<=*s && *s<='z' || 'A'<=*s && *s<='Z') {
+ s++;
+ while('a'<=*s && *s<='z' || 'A'<=*s && *s<='Z' || '0'<=*s && *s<='9' ||
+ *s=='-' || *s=='.' || *s==':') s++;
+ }
+ return s;
+}
+/*
+ * Skip to matching quote
+ */
+char *pl_quote(char *s){
+ char q;
+ q=*s++;
+ while(*s!=q && *s!='\0') s++;
+ return s;
+}
+void pl_dnl(char *s){
+ char *t;
+ for(t=s;*s;s++) if(*s!='\r' && *s!='\n') *t++=*s;
+ *t='\0';
+}
+void pl_tagparse(Hglob *g, char *str){
+ char *s, *t, *name, c;
+ Pair *ap;
+ Tag *tagp;
+ g->tag=Tag_end;
+ ap=g->attr;
+ if(str[0]=='!'){ /* test should be strncmp(str, "!--", 3)==0 */
+ g->tag=Tag_comment;
+ ap->name=0;
+ return;
+ }
+ if(str[0]=='/') str++;
+ name=str;
+ s=pl_word(str);
+ if(*s!='/' && *s!=' ' && *s!='\n' && *s!='\t' && *s!='\0'){
+ htmlerror(g->name, g->lineno, "bad tag name in %s", str);
+ ap->name=0;
+ return;
+ }
+ if(*s!='\0') *s++='\0';
+ for(t=name;t!=s;t++) if('A'<=*t && *t<='Z') *t+='a'-'A';
+ /*
+ * Binary search would be faster here
+ */
+ for(tagp=tag;tagp->name;tagp++) if(strcmp(name, tagp->name)==0) break;
+ g->tag=tagp-tag;
+ if(g->tag==Tag_end) htmlerror(g->name, g->lineno, "no tag %s", name);
+ for(;;){
+ s=pl_whitespace(s);
+ if(*s=='\0'){
+ ap->name=0;
+ return;
+ }
+ ap->name=s;
+ s=pl_word(s);
+ t=pl_whitespace(s);
+ c=*t;
+ *s='\0';
+ for(s=ap->name;*s;s++) if('A'<=*s && *s<='Z') *s+='a'-'A';
+ if(c=='='){
+ s=pl_whitespace(t+1);
+ if(*s=='\'' || *s=='"'){
+ ap->value=s+1;
+ s=pl_quote(s);
+ if(*s=='\0'){
+ htmlerror(g->name, g->lineno,
+ "No terminating quote in rhs of attribute %s",
+ ap->name);
+ ap->name=0;
+ return;
+ }
+ *s++='\0';
+ pl_dnl(ap->value);
+ }
+ else{
+ /* read up to white space or > */
+ ap->value=s;
+ while(*s!=' ' && *s!='\t' && *s!='\n' && *s!='\0') s++;
+ if(*s!='\0') *s++='\0';
+ }
+ pl_rmentities(g, ap->value);
+ }
+ else{
+ if(c!='\0') s++;
+ ap->value="";
+ }
+ if(ap==&g->attr[NATTR-1])
+ htmlerror(g->name, g->lineno, "too many attributes!");
+ else ap++;
+ }
+}
+int pl_getcomment(Hglob *g){
+ int c;
+ if((c=pl_nextc(g))=='-' && (c=pl_nextc(g))=='-'){
+ /* <!-- eats everything until --> or EOF */
+ for(;;){
+ while((c=pl_nextc(g))!='-' && c!=EOF)
+ ;
+ if(c==EOF)
+ break;
+ if(pl_nextc(g)=='-'){
+ while((c=pl_nextc(g))=='-')
+ ;
+ if(c==ETAG || c==EOF)
+ break;
+ }
+ }
+ } else {
+ /* <! eats everything until > or EOF */
+ while(c!=ETAG && c!=EOF)
+ c=pl_nextc(g);
+ }
+ if(c==EOF)
+ htmlerror(g->name, g->lineno, "EOF in comment");
+ g->tag=Tag_comment;
+ g->attr->name=0;
+ g->token[0]='\0';
+ return TAG;
+}
+
+int lrunetochar(char *p, int v)
+{
+ Rune r;
+
+ r=v;
+ return runetochar(p, &r);
+}
+
+int pl_getscript(Hglob *g){
+ char *tokp, *t;
+ int c;
+ tokp = g->token;
+ *tokp++ = '<';
+ while((c=pl_nextc(g)) != EOF){
+ if(c==STAG || c==' ' || c=='\t' || c=='\n'){
+ pl_putback(g, c);
+ break;
+ }
+ if(c==ETAG) c='>';
+ tokp += lrunetochar(tokp, c);
+ if(c==0 || c=='>' || tokp >= &g->token[NTOKEN-UTFmax-1])
+ break;
+ }
+ *tokp = '\0';
+ t = tag[g->state->tag].name;
+ if(g->token[1] == '/' && cistrncmp(g->token+2, t, strlen(t)) == 0){
+ g->tag=g->state->tag;
+ g->attr->name=0;
+ return ENDTAG;
+ }
+ pl_rmentities(g, g->token);
+ g->nsp=g->spacc;
+ g->spacc=0;
+ return TEXT;
+}
+
+/*
+ * Read a start or end tag -- the caller has read the initial <
+ */
+int pl_gettag(Hglob *g){
+ char *tokp;
+ int c, q;
+ if(g->state->isscript)
+ return pl_getscript(g);
+ if((c=pl_nextc(g))=='!' || c=='?')
+ return pl_getcomment(g);
+ pl_putback(g, c);
+ q = 0;
+ tokp=g->token;
+ while((c=pl_nextc(g))!=EOF){
+ if(c == '=' && q == 0)
+ q = '=';
+ else if(c == '\'' || c == '"'){
+ if(q == '=')
+ q = c;
+ else if(q == c)
+ q = 0;
+ }
+ else if(c == ETAG && q != '\'' && q != '"')
+ break;
+ else if(q == '=' && c != ' ' && c != '\t' && c != '\n')
+ q = 0;
+ if(tokp < &g->token[NTOKEN-UTFmax-1])
+ tokp += lrunetochar(tokp, c);
+ }
+ *tokp='\0';
+ if(c==EOF) htmlerror(g->name, g->lineno, "EOF in tag");
+ pl_tagparse(g, g->token);
+ if(g->token[0]!='/') return TAG;
+ if(g->attr[0].name!=0)
+ htmlerror(g->name, g->lineno, "end tag should not have attributes");
+ return ENDTAG;
+}
+/*
+ * The next token is a tag, an end tag or a sequence of non-white
+ * characters. If inside <pre>, single newlines are converted to <br>,
+ * double newlines are converted to <p> and spaces are preserved.
+ * Otherwise, spaces and newlines are noted and discarded.
+ */
+int pl_gettoken(Hglob *g){
+ char *tokp;
+ int c;
+ if(g->state->pre) switch(c=pl_nextc(g)){
+ case STAG: return pl_gettag(g);
+ case EOF: return EOF;
+ case '\n':
+ switch(c=pl_nextc(g)){
+ case '\n':
+ pl_tagparse(g, "p");
+ return TAG;
+ default:
+ pl_tagparse(g, "br");
+ pl_putback(g, c);
+ return TAG;
+ }
+ default:
+ tokp=g->token;
+ while(c=='\t'){
+ if(tokp < &g->token[NTOKEN-UTFmax-1]) tokp += lrunetochar(tokp, c);
+ c=pl_nextc(g);
+ }
+ while(c!='\t' && c!='\n' && c!=STAG && c!=EOF){
+ if(c==ETAG) c='>';
+ if(tokp < &g->token[NTOKEN-UTFmax-1]) tokp += lrunetochar(tokp, c);
+ c=pl_nextc(g);
+ }
+ *tokp='\0';
+ pl_rmentities(g, g->token);
+ pl_putback(g, c);
+ g->nsp=0;
+ g->spacc=0;
+ return TEXT;
+ }
+ while((c=pl_nextc(g))==' ' || c=='\t' || c=='\n')
+ if(g->spacc!=-1)
+ g->spacc++;
+ switch(c){
+ case STAG: return pl_gettag(g);
+ case EOF: return EOF;
+ default:
+ tokp=g->token;
+ do{
+ if(c==ETAG) c='>';
+ if(tokp < &g->token[NTOKEN-UTFmax-1]) tokp += lrunetochar(tokp, c);
+ c=pl_nextc(g);
+ }while(c!=' ' && c!='\t' && c!='\n' && c!=STAG && c!=EOF);
+ *tokp='\0';
+ pl_rmentities(g, g->token);
+ pl_putback(g, c);
+ g->nsp=g->spacc;
+ g->spacc=0;
+ return TEXT;
+ }
+}
+char *pl_getattr(Pair *attr, char *name){
+ for(;attr->name;attr++)
+ if(strcmp(attr->name, name)==0)
+ return attr->value;
+ return 0;
+}
+int pl_hasattr(Pair *attr, char *name){
+ for(;attr->name;attr++)
+ if(strcmp(attr->name, name)==0)
+ return 1;
+ return 0;
+}
+void plaintext(Hglob *g){
+ char line[NLINE];
+ char *lp, *elp;
+ int c;
+ g->state->font=CWIDTH;
+ g->state->size=NORMAL;
+ g->state->sub = 0;
+ elp=&line[NLINE-UTFmax-1];
+ lp=line;
+ for(;;){
+ c=pl_readc(g);
+ if(c==EOF) break;
+ if(c=='\n' || lp>=elp){
+ *lp='\0';
+ g->linebrk=1;
+ pl_htmloutput(g, 0, line, 0);
+ lp=line;
+ }
+ if(c=='\t'){
+ do *lp++=' '; while(lp<elp && utfnlen(line, lp-line)%8!=0);
+ }
+ else if(c!='\n')
+ lp += lrunetochar(lp, c);
+ }
+ if(lp!=line){
+ *lp='\0';
+ g->linebrk=1;
+ pl_htmloutput(g, 0, line, 0);
+ }
+}
+void plrdplain(char *name, int fd, Www *dst){
+ Hglob g;
+ g.state=g.stack;
+ g.state->tag=Tag_html;
+ g.state->font=CWIDTH;
+ g.state->size=NORMAL;
+ g.state->sub=0;
+ g.state->pre=0;
+ g.state->image=0;
+ g.state->link=0;
+ g.state->name=0;
+ g.state->margin=0;
+ g.state->indent=20;
+ g.state->ismap=0;
+ g.state->isscript=0;
+ g.state->strike=0;
+ g.state->width=0;
+ g.state->height=0;
+ g.dst=dst;
+ g.hfd=fd;
+ g.name=name;
+ g.ehbuf=g.hbufp=g.hbuf;
+ g.npeekc=0;
+ g.heof=0;
+ g.lineno=1;
+ g.linebrk=1;
+ g.para=0;
+ g.text=dst->title;
+ g.tp=g.text;
+ g.etext=g.text+NTITLE-1;
+ g.spacc=0;
+ g.form=0;
+ nstrcpy(g.text, name, NTITLE);
+ plaintext(&g);
+ finish(dst);
+}
+void plrdhtml(char *name, int fd, Www *dst, int killimgs){
+ int tagerr;
+ Stack *sp;
+ char buf[20];
+ char *str;
+ Hglob g;
+
+ g.state=g.stack;
+ g.state->tag=Tag_html;
+ g.state->font=ROMAN;
+ g.state->size=NORMAL;
+ g.state->sub=0;
+ g.state->pre=0;
+ g.state->image=0;
+ g.state->link=0;
+ g.state->name=0;
+ g.state->margin=0;
+ g.state->indent=25;
+ g.state->ismap=0;
+ g.state->isscript=0;
+ g.state->strike=0;
+ g.state->width=0;
+ g.state->height=0;
+ g.dst=dst;
+ g.hfd=fd;
+ g.name=name;
+ g.ehbuf=g.hbufp=g.hbuf;
+ g.npeekc=0;
+ g.heof=0;
+ g.lineno=1;
+ g.linebrk=1;
+ g.para=0;
+ g.text=dst->title;
+ g.tp=g.text;
+ g.etext=g.text+NTITLE-1;
+ dst->title[0]='\0';
+ g.spacc=0;
+ g.form=0;
+
+ for(;;) switch(pl_gettoken(&g)){
+ case TAG:
+ switch(tag[g.tag].action){
+ case OPTEND:
+ for(sp=g.state;sp!=g.stack && sp->tag!=g.tag;--sp);
+ if(sp->tag!=g.tag)
+ pl_pushstate(&g, g.tag);
+ else
+ for(;g.state!=sp;--g.state){
+ if(tag[g.state->tag].action!=OPTEND)
+ htmlerror(g.name, g.lineno,
+ "end tag </%s> missing",
+ tag[g.state->tag].name);
+ pl_popstate(g.state);
+ }
+ break;
+ case END:
+ pl_pushstate(&g, g.tag);
+ break;
+ }
+ str=pl_getattr(g.attr, "id");
+ if(str && *str){
+ char *swap;
+
+ swap = g.state->name;
+ g.state->name = str;
+ pl_htmloutput(&g, 0, "", 0);
+ g.state->name = swap;
+ }
+ switch(g.tag){
+ default:
+ htmlerror(g.name, g.lineno,
+ "unimplemented tag <%s>", tag[g.tag].name);
+ break;
+ case Tag_end: /* unrecognized start tag */
+ break;
+ case Tag_img:
+ case Tag_image:
+ str=pl_getattr(g.attr, "src");
+ if(str && *str){
+ free(g.state->image);
+ g.state->image = strdup(str);
+ } else {
+ Pair *a;
+
+ /*
+ * hack to emulate javascript that rewrites some attribute
+ * into src= after page got loaded. just look for some
+ * attribute that looks like a url.
+ */
+ for(a = g.attr; a->name; a++){
+ if(strcmp(a->name, "longdesc") == 0)
+ continue;
+ if(str = linkify(a->value)){
+ free(g.state->image);
+ g.state->image = str;
+ break;
+ }
+ }
+ }
+ g.state->ismap=pl_hasattr(g.attr, "ismap");
+ str=pl_getattr(g.attr, "width");
+ if(str && *str)
+ g.state->width=strtolength(&g, HORIZ, str);
+ str=pl_getattr(g.attr, "height");
+ if(str && *str)
+ g.state->height=strtolength(&g, VERT, str);
+ str=pl_getattr(g.attr, "alt");
+ if(str==0 || *str == 0){
+ if(g.state->image)
+ str=g.state->image;
+ else
+ str="[[image]]";
+ }
+ pl_htmloutput(&g, 0, str, 0);
+ free(g.state->image);
+ g.state->image=0;
+ g.state->ismap=0;
+ g.state->width=0;
+ g.state->height=0;
+ break;
+ case Tag_plaintext:
+ g.spacc=0;
+ plaintext(&g);
+ break;
+ case Tag_comment:
+ case Tag_html:
+ case Tag_link:
+ case Tag_nextid:
+ case Tag_table:
+ break;
+ case Tag_tr:
+ g.spacc=0;
+ g.linebrk=1;
+ break;
+ case Tag_th:
+ g.state->font=BOLD;
+ case Tag_td:
+ g.spacc++;
+ break;
+ case Tag_base:
+ str=pl_getattr(g.attr, "href");
+ if(str && *str){
+ seturl(g.dst->url, str, g.dst->url->fullname);
+ nstrcpy(g.dst->url->fullname, str, sizeof(g.dst->url->fullname));
+ /* base should be a full url, but it often isnt so have to resolve */
+ urlresolve(g.dst->url);
+ }
+ break;
+ case Tag_a:
+ str=pl_getattr(g.attr, "name");
+ if(str && *str){
+ free(g.state->name);
+ g.state->name = strdup(str);
+ }
+ pl_htmloutput(&g, 0, "", 0);
+ str=pl_getattr(g.attr, "href");
+ if(str && *str){
+ free(g.state->link);
+ g.state->link = strdup(str);
+ }
+ break;
+ case Tag_meta:
+ if((str=pl_getattr(g.attr, "http-equiv"))==0)
+ break;
+ if(cistrcmp(str, "refresh"))
+ break;
+ if((str=pl_getattr(g.attr, "content"))==0)
+ break;
+ if((str=strchr(str, '='))==0)
+ break;
+ str++;
+ pl_htmloutput(&g, 0, "[refresh: ", 0);
+ free(g.state->link);
+ g.state->link=unquot(str);
+ pl_htmloutput(&g, 0, g.state->link, 0);
+ free(g.state->link);
+ g.state->link=0;
+ pl_htmloutput(&g, 0, "]", 0);
+ g.linebrk=1;
+ g.spacc=0;
+ break;
+ case Tag_source:
+ case Tag_video:
+ case Tag_audio:
+ case Tag_embed:
+ case Tag_frame:
+ case Tag_iframe:
+ snprint(buf, sizeof(buf), "[%s: ", tag[g.tag].name);
+ pl_htmloutput(&g, 0, buf, 0);
+ str=pl_getattr(g.attr, "src");
+ if(str && *str){
+ free(g.state->link);
+ g.state->link = strdup(str);
+ }
+ str=pl_getattr(g.attr, "name");
+ if(str && *str){
+ free(g.state->name);
+ g.state->name = strdup(str);
+ } else if(g.state->link)
+ str = g.state->link;
+ else
+ str = "";
+ pl_htmloutput(&g, 0, str, 0);
+ free(g.state->link);
+ g.state->link=0;
+ free(g.state->name);
+ g.state->name=0;
+ pl_htmloutput(&g, 0, "]", 0);
+ g.linebrk=1;
+ g.spacc=0;
+ break;
+ case Tag_address:
+ g.spacc=0;
+ g.linebrk=1;
+ g.state->font=ROMAN;
+ g.state->size=NORMAL;
+ g.state->margin=300;
+ g.state->indent=50;
+ break;
+ case Tag_b:
+ case Tag_strong:
+ g.state->font=BOLD;
+ break;
+ case Tag_s:
+ case Tag_strike:
+ case Tag_del:
+ g.state->strike=1;
+ break;
+ case Tag_sub:
+ g.state->sub++;
+ break;
+ case Tag_sup:
+ g.state->sub--;
+ break;
+ case Tag_blockquot:
+ g.spacc=0;
+ g.linebrk=1;
+ g.state->margin+=50;
+ g.state->indent=20;
+ break;
+ case Tag_body:
+ break;
+ case Tag_head:
+ g.state->font=ROMAN;
+ g.state->size=NORMAL;
+ g.state->margin=0;
+ g.state->indent=20;
+ g.spacc=0;
+ break;
+ case Tag_div:
+ g.spacc=0;
+ break;
+ case Tag_br:
+ case Tag_wbr:
+ g.spacc=0;
+ g.linebrk=1;
+ break;
+ case Tag_span:
+ case Tag_center:
+ /* more to come */
+ break;
+ case Tag_cite:
+ case Tag_acronym:
+ g.state->font=ITALIC;
+ g.state->size=NORMAL;
+ break;
+ case Tag_code:
+ case Tag_samp:
+ g.state->font=CWIDTH;
+ g.state->size=NORMAL;
+ break;
+ case Tag_dd:
+ g.linebrk=1;
+ g.state->indent=0;
+ g.state->font=ROMAN;
+ g.spacc=0;
+ break;
+ case Tag_dfn:
+ htmlerror(g.name, g.lineno, "<dfn> deprecated");
+ case Tag_abbr:
+ g.state->font=BOLD;
+ g.state->size=NORMAL;
+ break;
+ case Tag_dl:
+ g.state->font=BOLD;
+ g.state->size=NORMAL;
+ g.state->margin+=40;
+ g.spacc=0;
+ break;
+ case Tag_dt:
+ g.para=1;
+ g.state->indent=-40;
+ g.state->font=BOLD;
+ g.spacc=0;
+ break;
+ case Tag_font:
+ /* more to come */
+ break;
+ case Tag_u:
+ htmlerror(g.name, g.lineno, "<u> deprecated");
+ case Tag_ins:
+ case Tag_em:
+ case Tag_i:
+ case Tag_var:
+ g.state->font=ITALIC;
+ break;
+ case Tag_h1:
+ g.linebrk=1;
+ g.state->font=BOLD;
+ g.state->size=ENORMOUS;
+ g.state->margin+=100;
+ g.spacc=0;
+ break;
+ case Tag_h2:
+ pl_linespace(&g);
+ g.state->font=BOLD;
+ g.state->size=ENORMOUS;
+ g.spacc=0;
+ break;
+ case Tag_h3:
+ g.linebrk=1;
+ pl_linespace(&g);
+ g.state->font=ITALIC;
+ g.state->size=ENORMOUS;
+ g.state->margin+=20;
+ g.spacc=0;
+ break;
+ case Tag_h4:
+ pl_linespace(&g);
+ g.state->font=BOLD;
+ g.state->size=LARGE;
+ g.state->margin+=10;
+ g.spacc=0;
+ break;
+ case Tag_h5:
+ pl_linespace(&g);
+ g.state->font=ITALIC;
+ g.state->size=LARGE;
+ g.state->margin+=10;
+ g.spacc=0;
+ break;
+ case Tag_h6:
+ pl_linespace(&g);
+ g.state->font=BOLD;
+ g.state->size=LARGE;
+ g.spacc=0;
+ break;
+ case Tag_hr:
+ g.spacc=0;
+ plrtbitmap(&g.dst->text, 1000000, g.state->margin, 0, hrule, 0, 0);
+ break;
+ case Tag_key:
+ htmlerror(g.name, g.lineno, "<key> deprecated");
+ case Tag_kbd:
+ g.state->font=CWIDTH;
+ break;
+ case Tag_dir:
+ case Tag_menu:
+ case Tag_ol:
+ case Tag_ul:
+ g.state->number=0;
+ g.linebrk=1;
+ g.state->margin+=25;
+ g.state->indent=-25;
+ g.spacc=0;
+ break;
+ case Tag_li:
+ g.spacc=0;
+ switch(g.state->tag){
+ default:
+ htmlerror(g.name, g.lineno, "can't have <li> in <%s>",
+ tag[g.state->tag].name);
+ case Tag_dir: /* supposed to be multi-columns, can't do! */
+ case Tag_menu:
+ g.linebrk=1;
+ break;
+ case Tag_ol:
+ g.para=1;
+ snprint(buf, sizeof(buf), "%2d ", ++g.state->number);
+ pl_htmloutput(&g, 0, buf, 0);
+ break;
+ case Tag_ul:
+ g.para=0;
+ g.linebrk=0;
+ g.spacc=-1;
+ plrtbitmap(&g.dst->text, 100000,
+ g.state->margin+g.state->indent, 0, bullet, 0, 0);
+ break;
+ }
+ break;
+ case Tag_p:
+ pl_linespace(&g);
+ g.linebrk=1;
+ g.spacc=0;
+ break;
+ case Tag_listing:
+ case Tag_xmp:
+ htmlerror(g.name, g.lineno, "<%s> deprecated", tag[g.tag].name);
+ case Tag_pre:
+ g.state->indent=0;
+ g.state->pre=1;
+ g.state->font=CWIDTH;
+ g.state->size=NORMAL;
+ pl_linespace(&g);
+ break;
+ case Tag_tt:
+ g.state->font=CWIDTH;
+ g.state->size=NORMAL;
+ break;
+ case Tag_title:
+ g.text=dst->title+strlen(dst->title);
+ g.tp=g.text;
+ g.etext=dst->title+NTITLE-1;
+ break;
+ case Tag_form:
+ case Tag_input:
+ case Tag_button:
+ case Tag_select:
+ case Tag_option:
+ case Tag_textarea:
+ case Tag_isindex:
+ rdform(&g);
+ break;
+ case Tag_script:
+ case Tag_style:
+ g.state->isscript=1;
+ break;
+ }
+ break;
+
+ case ENDTAG:
+ /*
+ * If the end tag doesn't match the top, we try to uncover a match
+ * on the stack.
+ */
+ if(g.state->tag!=g.tag){
+ tagerr=0;
+ for(sp=g.state;sp!=g.stack;--sp){
+ if(sp->tag==g.tag)
+ break;
+ if(tag[g.state->tag].action!=OPTEND) tagerr++;
+ }
+ if(sp==g.stack){
+ if(tagerr)
+ htmlerror(g.name, g.lineno,
+ "end tag mismatch <%s>...</%s>, ignored",
+ tag[g.state->tag].name, tag[g.tag].name);
+ }
+ else{
+ if(tagerr)
+ htmlerror(g.name, g.lineno,
+ "end tag mismatch <%s>...</%s>, "
+ "intervening tags popped",
+ tag[g.state->tag].name, tag[g.tag].name);
+
+ for(--sp; g.state!=sp; --g.state)
+ pl_popstate(g.state);
+ }
+ }
+ else if(g.state==g.stack)
+ htmlerror(g.name, g.lineno, "end tag </%s> at stack bottom",
+ tag[g.tag].name);
+ else
+ pl_popstate(g.state--);
+ switch(g.tag){
+ case Tag_select:
+ case Tag_form:
+ case Tag_textarea:
+ endform(&g);
+ break;
+ case Tag_h1:
+ case Tag_h2:
+ case Tag_h3:
+ case Tag_h4:
+ pl_linespace(&g);
+ break;
+ case Tag_div:
+ case Tag_address:
+ case Tag_blockquot:
+ case Tag_body:
+ case Tag_dir:
+ case Tag_dl:
+ case Tag_dt:
+ case Tag_h5:
+ case Tag_h6:
+ case Tag_listing:
+ case Tag_menu:
+ case Tag_ol:
+ case Tag_title:
+ case Tag_ul:
+ case Tag_xmp:
+ case Tag_table:
+ g.linebrk=1;
+ break;
+ case Tag_pre:
+ pl_linespace(&g);
+ break;
+ }
+ break;
+ case TEXT:
+ if(g.state->isscript)
+ continue;
+ if(g.state->link==0 && (str = linkify(g.token))){
+ g.state->link=str;
+ pl_htmloutput(&g, g.nsp, g.token, 0);
+ free(g.state->link);
+ g.state->link=0;
+ } else
+ pl_htmloutput(&g, g.nsp, g.token, 0);
+ break;
+ case EOF:
+ for(;g.state!=g.stack;--g.state){
+ if(tag[g.state->tag].action!=OPTEND)
+ htmlerror(g.name, g.lineno,
+ "missing </%s> at EOF", tag[g.state->tag].name);
+ pl_popstate(g.state);
+ }
+ pl_popstate(g.state);
+ *g.tp='\0';
+ if (!killimgs)
+ getpix(dst->text, dst);
+ finish(dst);
+ return;
+ }
+}
--- /dev/null
+++ b/snoop.c
@@ -1,0 +1,133 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#include <ctype.h>
+#include "mothra.h"
+
+int
+filetype(int fd, char *typ, int ntyp)
+{
+ int ifd[2], ofd[2], xfd[2], n;
+ char *argv[3], buf[4096];
+
+ typ[0] = 0;
+ if((n = readn(fd, buf, sizeof(buf))) < 0)
+ return -1;
+ if(n == 0)
+ return 0;
+ if(pipe(ifd) < 0)
+ return -1;
+ if(pipe(ofd) < 0){
+Err1:
+ close(ifd[0]);
+ close(ifd[1]);
+ return -1;
+ }
+ switch(rfork(RFFDG|RFPROC|RFNOWAIT)){
+ case -1:
+ close(ofd[0]);
+ close(ofd[1]);
+ goto Err1;
+ case 0:
+ dup(ifd[1], 0);
+ dup(ofd[1], 1);
+
+ close(ifd[1]);
+ close(ifd[0]);
+ close(ofd[1]);
+ close(ofd[0]);
+ close(fd);
+
+ argv[0] = "file";
+ argv[1] = "-m";
+ argv[2] = 0;
+ exec("/bin/file", argv);
+ }
+ close(ifd[1]);
+ close(ofd[1]);
+
+ if(rfork(RFFDG|RFPROC|RFNOWAIT) == 0){
+ close(fd);
+ close(ofd[0]);
+ write(ifd[0], buf, n);
+ exits(nil);
+ }
+ close(ifd[0]);
+
+ if(pipe(xfd) < 0){
+ close(ofd[0]);
+ return -1;
+ }
+ switch(rfork(RFFDG|RFPROC|RFNOWAIT)){
+ case -1:
+ break;
+ case 0:
+ close(ofd[0]);
+ close(xfd[0]);
+ do {
+ if(write(xfd[1], buf, n) != n)
+ break;
+ } while((n = read(fd, buf, sizeof(buf))) > 0);
+ exits(nil);
+ default:
+ dup(xfd[0], fd);
+ }
+ close(xfd[0]);
+ close(xfd[1]);
+
+ if((n = readn(ofd[0], typ, ntyp-1)) < 0)
+ n = 0;
+ close(ofd[0]);
+ while(n > 0 && typ[n-1] == '\n')
+ n--;
+ typ[n] = 0;
+ return 0;
+}
+
+int
+mimetotype(char *mime)
+{
+ int i;
+ static struct {
+ char *typ;
+ int val;
+ } tab[] = {
+ "text/plain", PLAIN,
+ "text/html", HTML,
+
+ "image/jpeg", JPEG,
+ "image/gif", GIF,
+ "image/png", PNG,
+ "image/bmp", BMP,
+ "image/x-icon", ICO,
+
+ "application/pdf", PAGE,
+ "application/postscript", PAGE,
+ "application/ghostscript", PAGE,
+ "application/troff", PAGE,
+
+ "image/", PAGE,
+ "text/", PLAIN,
+ "message/rfc822", PLAIN,
+ };
+
+ for(i=0; i<nelem(tab); i++)
+ if(strncmp(mime, tab[i].typ, strlen(tab[i].typ)) == 0)
+ return tab[i].val;
+
+ return -1;
+}
+
+int
+snooptype(int fd)
+{
+ char buf[128];
+ int i;
+
+ if(filetype(fd, buf, sizeof(buf)) < 0)
+ return -1;
+
+ return mimetotype(buf);
+}
--- /dev/null
+++ b/url.c
@@ -1,0 +1,250 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <event.h>
+#include <panel.h>
+#include "mothra.h"
+
+static int
+hexdigit(int c)
+{
+ if(c >= '0' && c <= '9')
+ return c-'0';
+ if(c >= 'a' && c <= 'f')
+ return c-'a'+10;
+ if(c >= 'A' && c <= 'F')
+ return c-'A'+10;
+ return -1;
+}
+
+static int
+dechex(uchar *out, int lim, char *in, int n)
+{
+ uchar *start, *end;
+ int c;
+
+ start = out;
+ end = start + lim;
+ while(n-- > 0 && out < end){
+ c = *in++;
+ if(c == 0)
+ break;
+ if(c & 0x80)
+ return -1;
+ if(c == '%'){
+ n -= 2;
+ if(n < 0 || (c = hexdigit(*in++)) == -1)
+ return -1;
+ if((c = (c << 4) | hexdigit(*in++)) == -1)
+ return -1;
+ }
+ *out++ = c;
+ }
+ return out - start;
+}
+
+static int
+dataget(Url *url)
+{
+ int (*decfun)(uchar *, int, char *, int) = dechex;
+ char *s, *p;
+ int fd, n, m;
+
+ s = url->reltext;
+ if(cistrncmp(s, "data:", 5) != 0)
+ return -1;
+ s += 5;
+ if((p = strchr(s, ',')) != nil){
+ *p = 0;
+ if(strstr(s, "base64") != nil)
+ decfun = dec64;
+ *p = ',';
+ s = p+1;
+ } else
+ s = strchr(s, 0);
+ n = strlen(s);
+ m = n+64;
+ p = malloc(m);
+ strcpy(p, "/tmp/duXXXXXXXXXXX");
+ if((fd = create(mktemp(p), ORDWR|ORCLOSE, 0600)) < 0){
+ free(p);
+ return -1;
+ }
+ if((m = (*decfun)((uchar*)p, m, s, n)) < 0 || write(fd, p, m) != m){
+ free(p);
+ close(fd);
+ return -1;
+ }
+ free(p);
+ seek(fd, 0, 0);
+ return fd;
+}
+
+static int
+fileget(Url *url)
+{
+ char *rel, *base, *x;
+
+ rel = base = nil;
+ if(cistrncmp(url->basename, "file:", 5) == 0)
+ base = url->basename+5;
+ if(cistrncmp(url->reltext, "file:", 5) == 0)
+ rel = url->reltext+5;
+ if(rel == nil && base == nil)
+ return -1;
+ if(rel == nil)
+ rel = url->reltext;
+ if(base && base[0] == '/' && rel[0] != '/'){
+ if(x = strrchr(base, '/'))
+ *x = 0;
+ snprint(url->fullname, sizeof(url->fullname), "%s/%s", base, rel);
+ if(x) *x = '/';
+ }else
+ snprint(url->fullname, sizeof(url->fullname), "%s", rel);
+ url->tag[0] = 0;
+ if(x = strrchr(url->fullname, '#')){
+ *x++ = 0;
+ nstrcpy(url->tag, x, sizeof(url->tag));
+ }
+ base = cleanname(url->fullname);
+ x = base + strlen(base)+1;
+ if((x - base) > sizeof(url->fullname)-5)
+ return -1;
+ memmove(url->fullname+5, base, x - base);
+ memmove(url->fullname, "file:", 5);
+ return open(url->fullname+5, OREAD);
+}
+
+char *mtpt="/mnt/web";
+
+static int
+webclone(Url *url, char *buf, int nbuf)
+{
+ int n, conn, fd;
+
+ snprint(buf, nbuf, "%s/clone", mtpt);
+ if((fd = open(buf, ORDWR)) < 0)
+ return -1;
+ if((n = read(fd, buf, nbuf-1)) <= 0){
+ close(fd);
+ return -1;
+ }
+ buf[n] = 0;
+ conn = atoi(buf);
+ if(url && url->reltext[0]){
+ if(url->basename[0]){
+ n = snprint(buf, nbuf, "baseurl %s", url->basename);
+ write(fd, buf, n);
+ }
+ n = snprint(buf, nbuf, "url %s", url->reltext);
+ if(write(fd, buf, n) < 0){
+ close(fd);
+ return -1;
+ }
+ }
+ snprint(buf, nbuf, "%s/%d", mtpt, conn);
+ return fd;
+}
+
+static int
+readstr(char *path, char *buf, int nbuf){
+ int n, fd;
+
+ n = 0;
+ if((fd = open(path, OREAD)) >= 0){
+ if((n = read(fd, buf, nbuf-1)) < 0)
+ n = 0;
+ close(fd);
+ }
+ buf[n] = 0;
+ return n;
+}
+
+int
+urlpost(Url *url, char *ctype)
+{
+ char buf[1024];
+ int n, fd;
+
+ if((fd = webclone(url, buf, sizeof(buf))) < 0)
+ return -1;
+ if(ctype && *ctype)
+ fprint(fd, "contenttype %s", ctype);
+ n = strlen(buf);
+ snprint(buf+n, sizeof(buf)-n, "/postbody");
+ n = open(buf, OWRITE);
+ close(fd);
+ return n;
+}
+
+int
+urlget(Url *url, int body)
+{
+ char buf[1024];
+ int n, fd;
+
+ if(body < 0){
+ if((fd = dataget(url)) >= 0)
+ return fd;
+ if((fd = fileget(url)) >= 0)
+ return fd;
+ if((fd = webclone(url, buf, sizeof(buf))) < 0)
+ return -1;
+ }else{
+ char *x;
+
+ if(fd2path(body, buf, sizeof(buf))){
+ close(body);
+ return -1;
+ }
+ if(x = strrchr(buf, '/'))
+ *x = 0;
+ fd = open(buf, OREAD);
+ close(body);
+ }
+ n = strlen(buf);
+ snprint(buf+n, sizeof(buf)-n, "/body");
+ body = open(buf, OREAD);
+ close(fd);
+ fd = body;
+ if(fd < 0)
+ return -1;
+
+ snprint(buf+n, sizeof(buf)-n, "/parsed/url");
+ readstr(buf, url->fullname, sizeof(url->fullname));
+
+ snprint(buf+n, sizeof(buf)-n, "/parsed/fragment");
+ readstr(buf, url->tag, sizeof(url->tag));
+
+ snprint(buf+n, sizeof(buf)-n, "/contenttype");
+ readstr(buf, url->contenttype, sizeof(url->contenttype));
+
+ snprint(buf+n, sizeof(buf)-n, "/contentencoding");
+ readstr(buf, buf, sizeof(buf));
+
+ if(!cistrcmp(buf, "compress"))
+ fd = pipeline(fd, "exec uncompress");
+ else if(!cistrcmp(buf, "gzip"))
+ fd = pipeline(fd, "exec gunzip");
+ else if(!cistrcmp(buf, "bzip2"))
+ fd = pipeline(fd, "exec bunzip2");
+
+ return fd;
+}
+
+int
+urlresolve(Url *url)
+{
+ char buf[1024];
+ int n, fd;
+
+ if((fd = webclone(url, buf, sizeof(buf))) < 0)
+ return -1;
+ n = strlen(buf);
+ snprint(buf+n, sizeof(buf)-n, "/parsed/url");
+ readstr(buf, url->fullname, sizeof(url->fullname));
+ snprint(buf+n, sizeof(buf)-n, "/parsed/fragment");
+ readstr(buf, url->tag, sizeof(url->tag));
+ close(fd);
+ return 0;
+}