ref: 0281f3466bd385123ea7608832e8bc6deb54dd78
author: phil9 <telephil9@gmail.com>
date: Tue Mar 22 16:26:46 EDT 2022
initial import
--- /dev/null
+++ b/LICENSE
@@ -1,0 +1,21 @@
+MIT License
+
+Copyright (c) 2022 phil9 <telephil9@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
--- /dev/null
+++ b/README.md
@@ -1,0 +1,21 @@
+# vdict
+A visual DICT client for plan9
+
+![vdict](vdict.png)
+
+Enter the word you want to search in the entry and press `Enter`.
+Coloured words are links that can be clicked to jump to their definition.
+
+## Usage
+```sh
+% mk install
+% vdict -h <host> [-p <port>]
+```
+You can use `dict.org` for instance.
+
+## License
+MIT
+
+## Bugs
+You tell me!
+
--- /dev/null
+++ b/a.h
@@ -1,0 +1,86 @@
+typedef struct Dictc Dictc;
+typedef struct Dvec Dvec;
+typedef struct Element Element;
+typedef struct Definition Definition;
+typedef struct Entry Entry;
+typedef struct Cols Cols;
+
+#pragma incomplete Dvec;
+
+struct Dictc
+{
+ int fd;
+ Biobuf* bin;
+ Dvec* db;
+ Dvec* strat;
+};
+
+struct Element
+{
+ char *name;
+ char *desc;
+};
+
+struct Definition
+{
+ char *db;
+ char *text;
+};
+
+struct Cols
+{
+ Image *back;
+ Image *text;
+ Image *focus;
+ Image *sel;
+ Image *scrl;
+};
+
+struct Entry
+{
+ Rectangle r;
+ ushort state;
+ int tickx;
+ int p0, p1;
+ int len;
+ int size;
+ int buttons;
+ char *text;
+ Channel *c;
+ Cols *cols;
+};
+
+/* DICT client */
+#define Dfirstmatch "!"
+#define Dallmatches "*"
+
+Dictc* dictdial(const char*, int);
+void dictquit(Dictc*);
+Dvec* dictdefine(Dictc*, char*, char*);
+
+usize dvlen(Dvec*);
+void* dvref(Dvec*, usize);
+
+/* dview */
+void dviewinit(Channel*, Cols*);
+void dviewresize(Rectangle);
+void dviewredraw(void);
+void dviewmouse(Mouse);
+void dviewkey(Rune);
+void dviewset(Dvec*);
+
+/* entry */
+void entryinit(Entry*, Cols*);
+void entryresize(Entry*, Rectangle);
+void entryredraw(Entry*);
+int entrymouse(Entry*, Mouse);
+void entrykey(Entry*, Rune);
+int entryhasfocus(Entry*);
+void entryfocus(Entry*, int);
+void entrysettext(Entry*, char*);
+
+/* utils */
+void *emalloc(ulong);
+void *erealloc(void*, ulong);
+
+
--- /dev/null
+++ b/dictc.c
@@ -1,0 +1,349 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <String.h>
+#include <draw.h>
+#include <thread.h>
+#include "a.h"
+
+typedef struct Response Response;
+
+struct Response
+{
+ int code;
+ char *msg;
+};
+
+struct Dvec
+{
+ void **elts;
+ usize len;
+ usize sz;
+};
+
+enum
+{
+ Eok,
+ Eeof,
+ Eunexpected,
+ Ebadformat,
+ Enodb,
+ Enostrat,
+};
+
+const char* Errors[] = {
+ [Eeof] = "no response from server",
+ [Eunexpected] = "unexpected response from server",
+ [Ebadformat] = "bad response format",
+ [Enodb] = "server does not have any database",
+ [Enostrat] = "server doest not have any strategy",
+};
+
+Dvec*
+mkdvec(usize size)
+{
+ Dvec *v;
+
+ v = emalloc(sizeof *v);
+ v->len = 0;
+ v->sz = size;
+ v->elts = emalloc(size * sizeof(void*));
+ return v;
+}
+
+void
+dvadd(Dvec *v, void *p)
+{
+ if(v->len == v->sz){
+ v->sz *= 1.5;
+ v->elts = erealloc(v->elts, v->sz * sizeof(void*));
+ }
+ v->elts[v->len++] = p;
+}
+
+usize
+dvlen(Dvec *v)
+{
+ return v->len;
+}
+
+void*
+dvref(Dvec *v, usize i)
+{
+ return v->elts[i];
+}
+
+int
+readstatus(char *s)
+{
+ char *e;
+ long l;
+
+ l = strtol(s, &e, 10);
+ if(l == 0 || e == s)
+ return -1;
+ return l;
+}
+
+int
+expectline(Dictc *c, char *l)
+{
+ char *s;
+
+ s = Brdstr(c->bin, '\n', 1);
+ if(s == nil)
+ return Eeof;
+ if(strncmp(s, l, strlen(l)) == 0)
+ return Eok;
+ return Ebadformat;
+}
+
+int
+sendcmd(Dictc *c, const char *cmd, Response *r)
+{
+ char *s;
+
+ write(c->fd, cmd, strlen(cmd));
+ write(c->fd, "\n", 1);
+ s = Brdstr(c->bin, '\n', 1);
+ if(s == nil)
+ return Eeof;
+ if(r != nil){
+ r->code = readstatus(s);
+ if(r->code == -1){
+ free(s);
+ return Eunexpected;
+ }
+ r->msg = strdup(s+4);
+ }
+ free(s);
+ return Eok;
+}
+
+int
+showdb(Dictc *c)
+{
+ Response r;
+ Element *e;
+ char *s, *p;
+ int rc, n, i;
+
+ rc = sendcmd(c, "SHOW DB", &r);
+ if(rc != Eok)
+ return rc;
+ if(r.code == 554)
+ return Enodb;
+ else if(r.code != 110)
+ return Eunexpected;
+ n = readstatus(r.msg);
+ free(r.msg);
+ c->db = mkdvec(n);
+ for(i = 0; i < n; i++){
+ s = Brdstr(c->bin, '\n', 1);
+ if(s == nil)
+ return Eeof;
+ p = strchr(s, ' ');
+ if(p == nil)
+ return Ebadformat;
+ e = emalloc(sizeof(Element));
+ p += 2; /* skip <space>" */
+ p[strlen(p) - 2] = 0; /* remove "\r */
+ e->desc = strdup(p);
+ *p = '\0';
+ e->name = strdup(s);
+ dvadd(c->db, e);
+ free(s);
+ }
+ if((n = expectline(c, ".")) != Eok)
+ return n;
+ if((n = expectline(c, "250 ok")) != Eok)
+ return n;
+ return Eok;
+}
+
+int
+showstrat(Dictc *c)
+{
+ Response r;
+ Element *e;
+ char *s, *p;
+ int rc, n, i;
+
+ rc = sendcmd(c, "SHOW STRAT", &r);
+ if(rc != Eok)
+ return rc;
+ if(r.code == 555)
+ return Enostrat;
+ else if(r.code != 111)
+ return Eunexpected;
+ n = readstatus(r.msg);
+ free(r.msg);
+ c->strat = mkdvec(n);
+ for(i = 0; i < n; i++){
+ s = Brdstr(c->bin, '\n', 1);
+ if(s == nil)
+ return Eeof;
+ p = strchr(s, ' ');
+ if(p == nil)
+ return Ebadformat;
+ e = emalloc(sizeof(Element));
+ p += 2; /* skip <space>" */
+ p[strlen(p) - 2] = 0; /* remove "\r */
+ e->desc = strdup(p);
+ *p = '\0';
+ e->name = strdup(s);
+ dvadd(c->strat, e);
+ free(s);
+ }
+ if((n = expectline(c, ".")) != Eok)
+ return n;
+ if((n = expectline(c, "250 ok")) != Eok)
+ return n;
+ return Eok;
+}
+
+void
+freedictc(Dictc *c)
+{
+ Element *e;
+ int i;
+
+ Bterm(c->bin);
+ close(c->fd);
+ if(c->db != nil){
+ for(i = 0; i < dvlen(c->db); i++){
+ e = dvref(c->db, i);
+ free(e->name);
+ free(e->desc);
+ free(e);
+ }
+ free(c->db);
+ }
+ free(c);
+}
+
+Dictc*
+dictdial(const char *addr, int port)
+{
+ Dictc *c;
+ char *s, buf[255];
+ int n;
+
+ if(port == 0)
+ port = 2628;
+ snprint(buf, sizeof buf, "tcp!%s!%d", addr, port);
+ c = malloc(sizeof *c);
+ if(c == nil)
+ sysfatal("malloc: %r");
+ c->fd = dial(buf, nil, nil, nil);
+ if(c->fd <= 0)
+ sysfatal("dial: %r");
+ c->bin = Bfdopen(c->fd, OREAD);
+ if(c->bin == nil)
+ sysfatal("Bfdopen: %r");
+ s = Brdstr(c->bin, '\n', 1);
+ if(s == nil){
+ werrstr("no status sent by server");
+ freedictc(c);
+ return nil;
+ }
+ n = showdb(c);
+ if(n != Eok){
+ werrstr(Errors[n]);
+ freedictc(c);
+ return nil;
+ }
+ n = showstrat(c);
+ if(n != Eok){
+ werrstr(Errors[n]);
+ freedictc(c);
+ return nil;
+ }
+ return c;
+}
+
+void
+dictquit(Dictc *c)
+{
+ sendcmd(c, "QUIT", nil);
+ freedictc(c);
+}
+
+Definition*
+parsedefinition(Dictc *c)
+{
+ Definition *d;
+ char *s;
+ String *sb;
+ int n;
+
+ s = Brdstr(c->bin, '\n', 1);
+ if(s == nil){
+ werrstr(Errors[Eeof]);
+ return nil;
+ }
+ n = readstatus(s);
+ free(s);
+ if(n != 151){
+ werrstr(Errors[Eunexpected]);
+ return nil;
+ }
+ sb = s_newalloc(255);
+ for(;;){
+ s = Brdstr(c->bin, '\n', 1);
+ if(s == nil){
+ s_free(sb);
+ werrstr(Errors[Eeof]);
+ return nil;
+ }
+ if(*s == '.'){
+ free(s);
+ break;
+ }
+ s[Blinelen(c->bin) - 1] = '\n'; /* replace \r with \n */
+ s_append(sb, s);
+ free(s);
+ }
+ s_terminate(sb);
+ d = emalloc(sizeof *d);
+ d->text = strdup(s_to_c(sb));
+ s_free(sb);
+ return d;
+}
+
+Dvec*
+dictdefine(Dictc* c, char *db, char *word)
+{
+ Dvec *v;
+ Response r;
+ Definition *d;
+ char buf[1024];
+ int rc, n, i;
+
+ snprint(buf, sizeof buf, "DEFINE %s \"%s\"", db, word);
+ rc = sendcmd(c, buf, &r);
+ if(rc != Eok){
+ werrstr(Errors[rc]);
+ return nil;
+ }
+ if(r.code == 552)
+ return mkdvec(1);
+ if(r.code != 150){
+ werrstr(Errors[Eunexpected]);
+ return nil;
+ }
+ n = readstatus(r.msg);
+ v = mkdvec(n);
+ for(i = 0; i < n; i++){
+ d = parsedefinition(c);
+ if(d == nil)
+ return nil; /* FIXME: cleanup vec */
+ dvadd(v, d);
+ }
+ if((n = expectline(c, "250 ok")) != Eok){
+ werrstr(Errors[n]);
+ return nil;
+ }
+ return v;
+}
+
--- /dev/null
+++ b/dview.c
@@ -1,0 +1,286 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <thread.h>
+#include <bio.h>
+#include "a.h"
+
+typedef struct Box Box;
+typedef struct Link Link;
+
+struct Box
+{
+ Rectangle r;
+ Rectangle sr;
+ Image *b;
+};
+
+struct Link
+{
+ Rectangle r;
+ char text[255];
+};
+
+enum
+{
+ Padding = 4,
+ Scrollwidth = 12,
+};
+
+Channel *chan;
+Dvec *defs;
+Box **boxes;
+usize nboxes;
+Link links[1024];
+usize nlinks;
+Cols *cols;
+Rectangle sr;
+Rectangle scrollr;
+Rectangle boxr;
+int totalh;
+int viewh;
+int offset;
+int scrollsize;
+
+Box*
+renderbox(Definition *d)
+{
+ Box *b;
+ int i, l, n, w, mw, inlink, cl;
+ Point p, lp;
+ Image *c;
+
+ n = 0;
+ w = 0;
+ mw = 0;
+ l = strlen(d->text);
+ for(i = 0; i < l; i++){
+ if(d->text[i] == '\n'){
+ ++n;
+ if(w > mw)
+ mw = w;
+ w = 0;
+ }else{
+ w += stringnwidth(font, d->text+i, 1);
+ }
+ }
+ b = emalloc(sizeof *b);
+ b->r = Rect(0, 0, Padding + mw + Padding, Padding+(n+1)*font->height+Padding);
+ b->b = allocimage(display, b->r, screen->chan, 0, DNofill);
+ draw(b->b, b->r, cols->back, nil, ZP);
+ p = Pt(Padding, Padding);
+ inlink = 0;
+ cl = 0;
+ for(i = 0; i < l; i++){
+ switch(d->text[i]){
+ case '\n':
+ p.x = Padding;
+ p.y += font->height;
+ break;
+ case '{':
+ cl = 0;
+ lp = p;
+ inlink = 1;
+ break;
+ case '}':
+ links[nlinks].r = Rpt(lp, addpt(p, Pt(0, font->height)));
+ links[nlinks].text[cl] = '\0';
+ nlinks += 1;
+ cl = 0;
+ inlink = 0;
+ break;
+ default:
+ c = cols->text;
+ if(inlink){
+ c = cols->focus;
+ links[nlinks].text[cl++] = d->text[i];
+ }
+ p = stringn(b->b, p, c, ZP, font, d->text + i, 1);
+ break;
+ }
+ }
+ return b;
+}
+
+void
+layout(void)
+{
+ Box *b;
+ int i;
+ Point p;
+
+ totalh = 0;
+ p = addpt(boxr.min, Pt(Padding, Padding));
+ for(i = 0; i < nboxes; i++){
+ b = boxes[i];
+ b->sr = rectaddpt(b->r, p);
+ p.y += Dy(b->r) + Padding;
+ totalh += Dy(b->r) + Padding;
+ }
+ scrollsize = 10*totalh/100.0;
+}
+
+void
+dviewset(Dvec *d)
+{
+ Definition *def;
+ int i;
+
+ if(defs != nil){
+ for(i = 0; i < nboxes; i++){
+ freeimage(boxes[i]->b);
+ free(boxes[i]);
+ }
+ nboxes = 0;
+ for(i = 0; i < dvlen(defs); i++){
+ def = dvref(defs, i);
+ free(def->text);
+ free(def);
+ }
+ free(defs);
+ }
+ nlinks = 0;
+ defs = d;
+ nboxes = dvlen(defs);
+ boxes = emalloc(nboxes * sizeof(Box*));
+ for(i = 0; i < nboxes; i++){
+ def = dvref(defs, i);
+ boxes[i] = renderbox(def);
+ }
+ layout();
+}
+
+void
+dviewredraw(void)
+{
+ Box *b;
+ Rectangle clipr, scrposr;
+ int i, h, y, ye, vmin, vmax;
+
+ clipr = screen->clipr;
+ draw(screen, sr, cols->back, nil, ZP);
+ draw(screen, scrollr, cols->scrl, nil, ZP);
+ border(screen, scrollr, 1, cols->text, ZP);
+ if(viewh < totalh){
+ h = ((double)viewh/totalh) * Dy(scrollr);
+ y = ((double)offset/totalh) * Dy(scrollr);
+ ye = scrollr.min.y + y + h - 1;
+ if(ye >= scrollr.max.y)
+ ye = scrollr.max.y - 1;
+ scrposr = Rect(scrollr.min.x + 1, scrollr.min.y + y + 1, scrollr.max.x - 1, ye);
+ }else
+ scrposr = insetrect(scrollr, -1);
+ draw(screen, scrposr, cols->back, nil, ZP);
+ replclipr(screen, 0, boxr);
+ vmin = boxr.min.y + offset;
+ vmax = boxr.max.y + offset;
+ if(boxes != nil){
+ for(i = 0; i < nboxes; i++){
+ b = boxes[i];
+ if(b->sr.min.y <= vmax && b->sr.max.y >= vmin)
+ draw(screen, rectaddpt(b->sr, Pt(0, -offset)), b->b, nil, ZP);
+ }
+ }
+ replclipr(screen, 0, clipr);
+}
+
+void
+dviewresize(Rectangle r)
+{
+ sr = r;
+ scrollr = sr;
+ scrollr.min.x += Padding;
+ scrollr.max.x = scrollr.min.x + Padding + Scrollwidth;
+ scrollr.max.y -= Padding;
+ boxr = sr;
+ boxr.min.x = scrollr.max.x + Padding;
+ boxr.max.x -= Padding;
+ boxr.max.y -= Padding;
+ viewh = Dy(boxr);
+ if(boxes != nil)
+ layout();
+}
+
+void
+scroll(int d)
+{
+ if(d < 0 && offset <= 0)
+ return;
+ if(d > 0 && offset + viewh > totalh)
+ return;
+ offset += d;
+ if(offset < 0)
+ offset = 0;
+ if((offset + viewh ) > totalh)
+ offset = totalh - viewh;
+ dviewredraw();
+ flushimage(display, 1);
+}
+
+void
+clicklink(Point p)
+{
+ int i;
+
+ p = subpt(p, addpt(boxr.min, Pt(0, offset)));
+ for(i = 0; i < nlinks; i++){
+ if(ptinrect(p, links[i].r)){
+ nbsendp(chan, strdup(links[i].text));
+ return;
+ }
+ }
+}
+
+void
+dviewmouse(Mouse m)
+{
+ if(!ptinrect(m.xy, sr))
+ return;
+ if(m.buttons == 1)
+ clicklink(m.xy);
+ else if(m.buttons == 8)
+ scroll(-scrollsize);
+ else if(m.buttons == 16)
+ scroll(scrollsize);
+}
+
+void
+dviewkey(Rune k)
+{
+ switch(k){
+ case Kup:
+ scroll(-scrollsize);
+ break;
+ case Kdown:
+ scroll(scrollsize);
+ break;
+ case Kpgup:
+ scroll(-viewh);
+ break;
+ case Kpgdown:
+ scroll(viewh);
+ break;
+ case Khome:
+ scroll(-totalh);
+ break;
+ case Kend:
+ scroll(totalh);
+ break;
+ }
+}
+
+
+void
+dviewinit(Channel *ch, Cols *c)
+{
+ chan = ch;
+ defs = nil;
+ boxes = nil;
+ nboxes = 0;
+ nlinks = 0;
+ totalh = -1;
+ offset = 0;
+ cols = c;
+}
--- /dev/null
+++ b/entry.c
@@ -1,0 +1,419 @@
+#include <u.h>
+#include <libc.h>
+#include <draw.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <thread.h>
+#include <ctype.h>
+#include <bio.h>
+#include "a.h"
+
+enum
+{
+ Senabled = 1 << 0,
+ Sfocused = 1 << 1,
+};
+
+enum
+{
+ Padding = 4,
+};
+
+static void einsert(Entry *entry, char *s);
+static void edelete(Entry *entry, int bs);
+
+static char *menu2str[] =
+{
+ "cut",
+ "paste",
+ "snarf",
+ 0
+};
+
+enum
+{
+ Mcut,
+ Mpaste,
+ Msnarf,
+};
+static Menu menu2 = { menu2str };
+static Image *tick = nil;
+
+int
+min(int x, int y)
+{
+ return x <= y ? x : y;
+}
+
+int
+max(int x, int y)
+{
+ return x >= y ? x : y;
+}
+
+static Image*
+createtick(Image *bg, Image *fg)
+{
+ enum { Tickw = 3 };
+ Image *t;
+
+ t = allocimage(display, Rect(0, 0, Tickw, font->height), screen->chan, 0, DWhite);
+ if(t == nil)
+ return 0;
+ /* background color */
+ draw(t, t->r, bg, nil, ZP);
+ /* vertical line */
+ draw(t, Rect(Tickw/2, 0, Tickw/2+1, font->height), fg, nil, ZP);
+ /* box on each end */
+ draw(t, Rect(0, 0, Tickw, Tickw), fg, nil, ZP);
+ draw(t, Rect(0, font->height-Tickw, Tickw, font->height), fg, nil, ZP);
+ return t;
+}
+
+void
+entryinit(Entry *e, Cols *cols)
+{
+ if(tick == nil)
+ tick = createtick(cols->back, cols->text);
+ e->state = Senabled;
+ e->buttons = 0;
+ e->tickx = 0;
+ e->size = 255;
+ e->text = emalloc(e->size * sizeof(char));
+ e->text[0] = 0;
+ e->p0 = 0;
+ e->p1 = 0;
+ e->len = 0;
+ e->c = chancreate(sizeof(char*), 1);
+ e->cols = cols;
+}
+
+int
+entryhasfocus(Entry *e)
+{
+ return (e->state & Sfocused);
+}
+
+static void
+entryunfocus(Entry *e)
+{
+ if(!entryhasfocus(e))
+ return;
+ e->state ^= Sfocused;
+ e->buttons = 0;
+ e->p0 = e->len;
+ e->p1 = e->len;
+ entryredraw(e);
+}
+
+void
+entryfocus(Entry *e, int sel)
+{
+ if(entryhasfocus(e))
+ return;
+ e->state |= Sfocused;
+ if(sel){
+ e->p0 = 0;
+ e->p1 = e->len;
+ }
+ entryredraw(e);
+}
+
+void
+entrysettext(Entry *e, const char *text)
+{
+ int l;
+
+ l = strlen(text);
+ if(l >= e->size) {
+ e->size = l;
+ e->text = erealloc(e->text, e->size * sizeof(char));
+ }
+ strncpy(e->text, text, l);
+ e->text[l] = 0;
+ e->len = l;
+ e->p0 = e->len;
+ e->p1 = e->p0;
+ e->tickx = stringnwidth(font, e->text, e->len);
+}
+
+void
+entryresize(Entry *e, Rectangle r)
+{
+ e->r = r;
+}
+
+void
+entryredraw(Entry *e)
+{
+ Rectangle r, clipr;
+ Point p;
+ int y, sels, sele;
+
+ clipr = screen->clipr;
+ replclipr(screen, 0, e->r);
+ draw(screen, e->r, e->cols->back, nil, ZP);
+ if(entryhasfocus(e))
+ border(screen, e->r, 1, e->cols->focus, ZP);
+ else
+ border(screen, e->r, 1, e->cols->text, ZP);
+ y = (Dy(e->r) - font->height) / 2;
+ p = Pt(e->r.min.x + Padding, e->r.min.y + y);
+ stringn(screen, p, e->cols->text, ZP, font, e->text, e->len);
+ if (e->p0 != e->p1) {
+ sels = min(e->p0, e->p1);
+ sele = max(e->p0, e->p1);
+ p.x += stringnwidth(font, e->text, sels);
+ stringnbg(screen, p, e->cols->text, ZP, font, e->text+sels, sele-sels, e->cols->sel, ZP);
+ } else if (e->state & Sfocused) {
+ e->tickx = stringnwidth(font, e->text, e->p0);
+ p.x += e->tickx;
+ r = Rect(p.x, p.y, p.x + Dx(tick->r), p.y + Dy(tick->r));
+ draw(screen, r, tick, nil, ZP);
+ }
+ flushimage(display, 1);
+ replclipr(screen, 0, clipr);
+}
+
+static int
+ptpos(Entry *e, Mouse m)
+{
+ int i, x, prev, cur;
+
+ x = m.xy.x - e->r.min.x - Padding;
+ prev = 0;
+ for(i = 0; i < e->len; i++){
+ cur = stringnwidth(font, e->text, i);
+ if ((prev+cur)/2 >= x){
+ i--;
+ break;
+ }else if (prev <= x && cur >= x)
+ break;
+ prev = cur;
+ }
+ return i;
+}
+
+static int
+issep(char c)
+{
+ return c == 0 || c == '/' || (!isalnum(c) && c != '-');
+}
+
+static void
+entryclicksel(Entry *e)
+{
+ int s, t;
+
+ if(e->p0 == 0)
+ e->p1 = e->len;
+ else if(e->p0 == e->len)
+ e->p0 = 0;
+ else{
+ s = e->p0;
+ t = e->p0;
+ while((s - 1) >= 0 && !issep(e->text[s - 1]))
+ --s;
+ while(t < e->len && !issep(e->text[t]))
+ ++t;
+ e->p0 = s;
+ e->p1 = t;
+ }
+}
+
+int
+entrymouse(Entry *e, Mouse m)
+{
+ static int lastn = -1;
+ static ulong lastms = 0;
+ int in, n, sels, sele;
+ char *s;
+ usize len;
+
+ s = nil;
+ len = 0;
+ in = ptinrect(m.xy, e->r);
+ if(in && !e->buttons && m.buttons)
+ e->state |= Sfocused;
+ if(e->state & Sfocused){
+ n = ptpos(e, m);
+ if(!in && !e->buttons && m.buttons){
+ entryunfocus(e);
+ return -1;
+ }
+ if(m.buttons & 1){ /* holding left button */
+ sels = min(e->p0, e->p1);
+ sele = max(e->p0, e->p1);
+ if(m.buttons == (1|2) && e->buttons == 1){
+ if(sels != sele){
+ /* TODO: snarf */
+ edelete(e, 0);
+ }
+ }else if(m.buttons == (1|4) && e->buttons == 1){
+ /* TODO: paste */
+ //plan9_paste(&s, &len);
+ if(len > 0 && s != nil)
+ einsert(e, s);
+ free(s);
+ }else if(m.buttons == 1 && e->buttons <= 1){
+ e->p0 = n;
+ if (e->buttons == 0){
+ e->p1 = n;
+ if(n == lastn && lastms > 0 && (m.msec - lastms)<=250)
+ entryclicksel(e);
+ }
+ }
+ entryredraw(e);
+ lastn = n;
+ lastms = m.msec;
+ } else if (m.buttons & 2) {
+ //sels = min(e->p0, e->p1);
+ //sele = max(e->p0, e->p1);
+ /* TODO
+ //n = emenuhit(2, &e.mouse, &menu2);
+ n = -1;
+ switch(n) {
+ case Mcut:
+ if (sels != sele) {
+ plan9_snarf(entry->text+sels, sele-sels);
+ text_delete(entry, 0);
+ }
+ break;
+ case Mpaste:
+ plan9_paste(&s, &len);
+ if (len >= 0 && s != NULL)
+ text_insert(entry, s);
+ free(s);
+ break;
+ case Msnarf:
+ if (sels != sele) {
+ plan9_snarf(entry->text+sels, sele-sels);
+ }
+ break;
+ }
+ entryredraw(e);
+ */
+ }
+ e->buttons = m.buttons;
+ return 0;
+ }
+ return -1;
+}
+
+void
+entrykey(Entry *e, Rune k)
+{
+ int sels, sele, n;
+ char s[UTFmax+1];
+
+ if(!entryhasfocus(e))
+ return;
+ sels = min(e->p0, e->p1);
+ sele = max(e->p0, e->p1);
+ switch (k) {
+ case Keof:
+ case '\n':
+ e->p0 = e->p1 = e->len;
+ nbsendp(e->c, strdup(e->text));
+ break;
+ case Knack: /* ^U: delete selection, if any, and everything before that */
+ memmove(e->text, e->text + sele, e->len - sele);
+ e->len = e->len - sele;
+ e->p0 = 0;
+ e->text[e->len] = 0;
+ break;
+ case Kleft:
+ e->p0 = max(0, sels-1);
+ break;
+ case Kright:
+ e->p0 = min(e->len, sele+1);
+ break;
+ case Ksoh: /* ^A: start of line */
+ case Khome:
+ e->p0 = 0;
+ break;
+ case Kenq: /* ^E: end of line */
+ case Kend:
+ e->p0 = e->len;
+ break;
+ case Kdel:
+ edelete(e, 0);
+ break;
+ case Kbs:
+ edelete(e, 1);
+ break;
+ case Ketb:
+ while(sels > 0 && !isalnum(e->text[sels-1]))
+ sels--;
+ while(sels > 0 && isalnum(e->text[sels-1]))
+ sels--;
+ e->p0 = sels;
+ e->p1 = sele;
+ edelete(e, 0);
+ break;
+ case Kesc:
+ if (sels == sele) {
+ sels = e->p0 = 0;
+ sele = e->p1 = e->len;
+ }
+ /* TODO */
+ //plan9_snarf(e->text+sels, sele-sels);
+ edelete(e, 0);
+ break;
+ case 0x7: /* ^G: remove focus */
+ entryunfocus(e);
+ return;
+ default:
+ if(k < 0x20 || (k & 0xFF00) == KF || (k & 0xFF00) == Spec || (n = runetochar(s, &k)) < 1)
+ return;
+ s[n] = 0;
+ einsert(e, s);
+ }
+ e->p1 = e->p0;
+ entryredraw(e);
+}
+
+static void
+einsert(Entry *e, char *s)
+{
+ int sels, sele, n;
+ char *p;
+
+ n = strlen(s);
+ if(e->size <= e->len + n){
+ e->size = (e->len + n)*2 + 1;
+ if((p = realloc(e->text, e->size)) == nil)
+ return;
+ e->text = p;
+ }
+ sels = min(e->p0, e->p1);
+ sele = max(e->p0, e->p1);
+ if(sels != sele){
+ memmove(e->text + sels + n, e->text + sele, e->len - sele);
+ e->len -= sele - sels;
+ e->p0 = sels;
+ }else if (e->p0 != e->len)
+ memmove(e->text + e->p0 + n, e->text + e->p0, e->len - e->p0);
+ memmove(e->text + sels, s, n);
+ e->len += n;
+ e->p1 = sels;
+ e->p0 = sels + n;
+ e->text[e->len] = 0;
+}
+
+static void
+edelete(Entry *e, int bs)
+{
+ int sels, sele;
+
+ sels = min(e->p0, e->p1);
+ sele = max(e->p0, e->p1);
+ if(sels == sele && sels == 0)
+ return;
+ memmove(e->text + sels - bs, e->text + sele, e->len - sele);
+ e->p0 = sels - bs;
+ e->len -= sele - sels + bs;
+ e->p1 = e->p0;
+ e->text[e->len] = 0;
+}
+
--- /dev/null
+++ b/mkfile
@@ -1,0 +1,9 @@
+</$objtype/mkfile
+
+BIN=/$objtype/bin
+TARG=vdict
+OFILES=vdict.$O dictc.$O dview.$O entry.$O theme.$O utils.$O
+HFILES=a.h
+
+</sys/src/cmd/mkone
+
--- /dev/null
+++ b/theme.c
@@ -1,0 +1,84 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <draw.h>
+#include "theme.h"
+
+Image*
+ereadcol(char *s)
+{
+ Image *i;
+ char *e;
+ ulong c;
+
+ c = strtoul(s, &e, 16);
+ if(e == nil || e == s)
+ return nil;
+ c = (c << 8) | 0xff;
+ i = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, c);
+ if(i == nil)
+ sysfatal("allocimage: %r");
+ return i;
+}
+
+Theme*
+loadtheme(void)
+{
+ Theme *theme;
+ Biobuf *bp;
+ char *s;
+
+ if(access("/dev/theme", AREAD) < 0)
+ return 0;
+ bp = Bopen("/dev/theme", OREAD);
+ if(bp == nil)
+ return 0;
+ theme = malloc(sizeof *theme);
+ if(theme == nil){
+ Bterm(bp);
+ return nil;
+ }
+ for(;;){
+ s = Brdstr(bp, '\n', 1);
+ if(s == nil)
+ break;
+ if(strncmp(s, "back", 4) == 0)
+ theme->back = ereadcol(s+5);
+ else if(strncmp(s, "high", 4) == 0)
+ theme->high = ereadcol(s+5);
+ else if(strncmp(s, "border", 6) == 0)
+ theme->border = ereadcol(s+7);
+ else if(strncmp(s, "text", 4) == 0)
+ theme->text = ereadcol(s+5);
+ else if(strncmp(s, "htext", 5) == 0)
+ theme->htext = ereadcol(s+6);
+ else if(strncmp(s, "title", 5) == 0)
+ theme->title = ereadcol(s+6);
+ else if(strncmp(s, "ltitle", 6) == 0)
+ theme->ltitle = ereadcol(s+7);
+ else if(strncmp(s, "hold", 4) == 0)
+ theme->hold = ereadcol(s+5);
+ else if(strncmp(s, "lhold", 5) == 0)
+ theme->lhold = ereadcol(s+6);
+ else if(strncmp(s, "palehold", 8) == 0)
+ theme->palehold = ereadcol(s+9);
+ else if(strncmp(s, "paletext", 8) == 0)
+ theme->paletext = ereadcol(s+9);
+ else if(strncmp(s, "size", 4) == 0)
+ theme->size = ereadcol(s+5);
+ else if(strncmp(s, "menuback", 8) == 0)
+ theme->menuback = ereadcol(s+9);
+ else if(strncmp(s, "menuhigh", 8) == 0)
+ theme->menuhigh = ereadcol(s+9);
+ else if(strncmp(s, "menubord", 8) == 0)
+ theme->menubord = ereadcol(s+9);
+ else if(strncmp(s, "menutext", 8) == 0)
+ theme->menutext = ereadcol(s+9);
+ else if(strncmp(s, "menuhtext", 5) == 0)
+ theme->menuhtext = ereadcol(s+6);
+ free(s);
+ }
+ Bterm(bp);
+ return theme;
+}
+
--- /dev/null
+++ b/theme.h
@@ -1,0 +1,25 @@
+typedef struct Theme Theme;
+
+struct Theme
+{
+ Image *back;
+ Image *high;
+ Image *border;
+ Image *text;
+ Image *htext;
+ Image *title;
+ Image *ltitle;
+ Image *hold;
+ Image *lhold;
+ Image *palehold;
+ Image *paletext;
+ Image *size;
+ Image *menubar;
+ Image *menuback;
+ Image *menuhigh;
+ Image *menubord;
+ Image *menutext;
+ Image *menuhtext;
+};
+
+Theme* loadtheme(void);
--- /dev/null
+++ b/utils.c
@@ -1,0 +1,29 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <draw.h>
+#include <thread.h>
+#include "a.h"
+
+void*
+emalloc(ulong size)
+{
+ void *p;
+
+ p = malloc(size);
+ if(p == nil)
+ sysfatal("malloc: %r");
+ return p;
+}
+
+void*
+erealloc(void *p, ulong size)
+{
+ void *q;
+
+ q = realloc(p, size);
+ if(q == nil)
+ sysfatal("realloc: %r");
+ return q;
+}
+
--- /dev/null
+++ b/vdict.c
@@ -1,0 +1,200 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <draw.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include "a.h"
+#include "theme.h"
+
+Dictc *dict;
+Mousectl *mc;
+Keyboardctl *kc;
+Entry *entry;
+Cols *cols;
+Rectangle searchr;
+Rectangle entryr;
+Rectangle viewr;
+
+enum
+{
+ Padding = 4,
+};
+
+void
+redraw(void)
+{
+ draw(screen, screen->r, cols->back, nil, ZP);
+ string(screen, addpt(searchr.min, Pt(Padding, 2*Padding+2)), cols->text, ZP, font, "Search:");
+ entryredraw(entry);
+ dviewredraw();
+ flushimage(display, 1);
+}
+
+void
+emouse(Mouse m)
+{
+ entrymouse(entry, m);
+ dviewmouse(m);
+}
+
+void
+ekeyboard(Rune k)
+{
+ switch(k){
+ case Kdel:
+ threadexitsall(nil);
+ break;
+ case Kstx:
+ if(!entryhasfocus(entry))
+ entryfocus(entry, 1);
+ break;
+ default:
+ entrykey(entry, k);
+ if(!entryhasfocus(entry))
+ dviewkey(k);
+ break;
+ }
+}
+
+void
+eresize(void)
+{
+ searchr = screen->r;
+ searchr.max.y = searchr.min.y + 2*Padding + 2 + font->height + 2 + Padding;
+ entryr = searchr;
+ entryr.min.x += Padding + stringwidth(font, "Search:") + Padding;
+ entryr.max.x = searchr.max.x - Padding;
+ entryr.min.y += 2*Padding;
+ entryr.max.y -= Padding;
+ entryresize(entry, entryr);
+ viewr = screen->r;
+ viewr.min.y = searchr.max.y + 1;
+ dviewresize(viewr);
+ redraw();
+}
+
+void
+esearch(char *s)
+{
+ Dvec *v;
+
+ v = dictdefine(dict, Dfirstmatch, s);
+ dviewset(v);
+ dviewredraw();
+ flushimage(display, 1);
+}
+
+void
+elink(char *s)
+{
+ entrysettext(entry, s);
+ entryredraw(entry);
+ esearch(s);
+}
+
+void
+initcolors(void)
+{
+ Theme *theme;
+
+ cols = emalloc(sizeof *cols);
+ theme = loadtheme();
+ if(theme == nil){
+ cols->back = theme->back;
+ cols->text = theme->text;
+ cols->focus = theme->title;
+ cols->sel = theme->border;
+ cols->scrl = theme->border;
+ }else{
+ cols->back = display->white;
+ cols->text = display->black;
+ cols->focus = allocimage(display, Rect(0, 0, 1, 1), RGBA32, 1, DGreygreen);
+ cols->sel = allocimage(display, Rect(0, 0, 1, 1), RGBA32, 1, 0xCCCCCCFF);
+ cols->scrl = allocimage(display, Rect(0, 0, 1, 1), RGBA32, 1, 0x999999FF);
+ }
+}
+
+void
+usage(void)
+{
+ fprint(2, "usage: %s -h <host> [-p <port>]\n", argv0);
+ exits("usage");
+}
+
+void
+threadmain(int argc, char *argv[])
+{
+ enum { Emouse, Eresize, Ekeyboard, Eentry, Elink };
+ Mouse m;
+ Rune k;
+ char *s, *l, *host;
+ int port;
+ Channel *lchan;
+ Alt a[] = {
+ { nil, &m, CHANRCV },
+ { nil, nil, CHANRCV },
+ { nil, &k, CHANRCV },
+ { nil, &s, CHANRCV },
+ { nil, &l, CHANRCV },
+ { nil, nil, CHANEND },
+ };
+
+ host = nil;
+ port = 2628;
+ ARGBEGIN{
+ case 'h':
+ host = EARGF(usage());
+ break;
+ case 'p':
+ port = atoi(EARGF(usage()));
+ break;
+ }ARGEND;
+ if(host == nil)
+ usage();
+ dict = dictdial(host, port);
+ if(dict == nil)
+ sysfatal("initdict: %r");
+ if(initdraw(nil, nil, argv0) < 0)
+ sysfatal("initdraw: %r");
+ display->locking = 0;
+ if((mc = initmouse(nil, screen)) == nil)
+ sysfatal("initmouse: %r");
+ if((kc = initkeyboard(nil)) == nil)
+ sysfatal("initkeyboard: %r");
+ initcolors();
+ entry = emalloc(sizeof *entry);
+ entryinit(entry, cols);
+ lchan = chancreate(sizeof(char*), 1);
+ dviewinit(lchan, cols);
+ a[Emouse].c = mc->c;
+ a[Eresize].c = mc->resizec;
+ a[Ekeyboard].c = kc->c;
+ a[Eentry].c = entry->c;
+ a[Elink].c = lchan;
+ eresize();
+ entryfocus(entry, 0);
+ for(;;){
+ switch(alt(a)){
+ case Emouse:
+ emouse(m);
+ break;
+ case Eresize:
+ if(getwindow(display, Refnone) < 0)
+ sysfatal("getwindow: %r");
+ eresize();
+ break;
+ case Ekeyboard:
+ ekeyboard(k);
+ break;
+ case Eentry:
+ esearch(s);
+ break;
+ case Elink:
+ elink(l);
+ break;
+ }
+ }
+}
+
binary files /dev/null b/vdict.png differ