#include #include #include #include #include #include #include typedef struct Text Text; struct Text { Control; int border; int topline; int scroll; int nvis; CFont *font; CImage *image; CImage *textcolor; CImage *bordercolor; CImage *selectcolor; Rune **line; int selectmode; uchar *selected; int nline; int align; }; enum { Selsingle, Selmulti, }; enum{ EAccumulate, EAdd, EAlign, EBorder, EBordercolor, EClear, EDelete, EFocus, EFont, EImage, ERect, EReplace, EScroll, ESelect, ESelectcolor, ESelectmode, EShow, ETextcolor, ETopline, EValue, }; static char *cmds[] = { [EAccumulate] = "accumulate", [EAdd] = "add", [EAlign] = "align", [EBorder] = "border", [EBordercolor] = "bordercolor", [EClear] = "clear", [EDelete] = "delete", [EFocus] = "focus", [EFont] = "font", [EImage] = "image", [ERect] = "rect", [EReplace] = "replace", [EScroll] = "scroll", [ESelect] = "select", [ESelectcolor] = "selectcolor", [ESelectmode] = "selectmode", [EShow] = "show", [ETextcolor] = "textcolor", [ETopline] = "topline", [EValue] = "value", nil }; static void textctl(Control*, char*); static void textshow(Text*); static void texttogglei(Text*, int); static void textfree(Text*); static int texttoggle(Text*, Point); static void textthread(void *v) { char buf[32]; Text *t; int lastbut, sel; t = v; snprint(buf, sizeof buf, "text-%s-0x%p", t->name, t); threadsetname(buf); t->line = ctlmalloc(sizeof(Rune*)); t->selected = ctlmalloc(1); t->nline = 0; t->image = _getctlimage("white"); t->textcolor = _getctlimage("black"); t->bordercolor = _getctlimage("black"); t->selectcolor = _getctlimage("yellow"); t->font = _getctlfont("font"); t->selectmode = Selsingle; lastbut = 0; for(;;){ switch(alt(t->alts)){ default: ctlerror("%q: unknown message", t->name); case AKey: /* ignore keystrokes */ break; case AMouse: if(lastbut != (t->m.buttons&1)){ if(t->m.buttons & 1){ sel = texttoggle(t, t->m.xy); if(sel >= 0) printctl(t->event, "%q: select %d %d", t->name, sel, t->selected[sel]); } lastbut = t->m.buttons & 1; } break; case ACtl: _ctlcontrol(t, t->str, textctl); free(t->str); break; case AWire: _ctlrewire(t); break; case AExit: textfree(t); sendul(t->exit, 0); return; } } } Control* createtext(Controlset *cs, char *name) { return _createctl(cs, "text", sizeof(Text), name, textthread, 0); } static void textfree(Text *t) { int i; _putctlfont(t->font); _putctlimage(t->image); _putctlimage(t->textcolor); _putctlimage(t->bordercolor); _putctlimage(t->selectcolor); for(i=0; inline; i++) free(t->line[i]); free(t->line); free(t->selected); } static void textshow(Text *t) { Rectangle r, tr; Point p; int i, ntext; Font *f; Rune *text; r = t->rect; f = t->font->font; draw(t->screen, r, t->image->image, nil, t->image->image->r.min); if(t->border > 0){ border(t->screen, r, t->border, t->bordercolor->image, t->bordercolor->image->r.min); r = insetrect(r, t->border); } tr = r; t->nvis = Dy(r)/f->height; for(i=t->topline; inline && itopline+t->nvis; i++){ text = t->line[i]; ntext = runestrlen(text); r.max.y = r.min.y+f->height; if(t->selected[i]) draw(t->screen, r, t->selectcolor->image, nil, ZP); p = _ctlalignpoint(r, runestringnwidth(f, text, ntext), f->height, t->align); _string(t->screen, p, t->textcolor->image, ZP, f, nil, text, ntext, tr, nil, ZP); r.min.y += f->height; } flushimage(display, 1); } static void textctl(Control *c, char *str) { int cmd, i, n; CParse cp; Rectangle r; Text *t; Rune *rp; t = (Text*)c; cmd = _ctlparse(&cp, str, cmds); switch(cmd){ default: ctlerror("%q: unrecognized message '%s'", t->name, cp.str); break; case EAlign: _ctlargcount(t, &cp, 2); t->align = _ctlalignment(cp.args[1]); break; case EBorder: _ctlargcount(t, &cp, 2); if(cp.iargs[1] < 0) ctlerror("%q: bad border: %c", t->name, cp.str); t->border = cp.iargs[1]; break; case EBordercolor: _ctlargcount(t, &cp, 2); _setctlimage(t, &t->bordercolor, cp.args[1]); break; case EClear: _ctlargcount(t, &cp, 1); for(i=0; inline; i++) free(t->line[i]); free(t->line); free(t->selected); t->line = ctlmalloc(sizeof(Rune*)); t->selected = ctlmalloc(1); t->nline = 0; textshow(t); break; case EDelete: _ctlargcount(t, &cp, 2); i = cp.iargs[1]; if(i<0 || i>=t->nline) ctlerror("%q: line number out of range: %s", t->name, cp.str); free(t->line[i]); memmove(t->line+i, t->line+i+1, (t->nline-(i+1))*sizeof(Rune*)); memmove(t->selected+i, t->selected+i+1, t->nline-(i+1)); t->nline--; textshow(t); break; case EFocus: break; case EFont: _ctlargcount(t, &cp, 2); _setctlfont(t, &t->font, cp.args[1]); break; case EImage: _ctlargcount(t, &cp, 2); _setctlimage(t, &t->image, cp.args[1]); break; case ERect: _ctlargcount(t, &cp, 5); r.min.x = cp.iargs[1]; r.min.y = cp.iargs[2]; r.max.x = cp.iargs[3]; r.max.y = cp.iargs[4]; if(Dx(r)<=0 || Dy(r)<=0) ctlerror("%q: bad rectangle: %s", t->name, cp.str); t->rect = r; t->nvis = (Dy(r)-2*t->border)/t->font->font->height; break; case EReplace: _ctlargcount(t, &cp, 3); i = cp.iargs[1]; if(i<0 || i>=t->nline) ctlerror("%q: line number out of range: %s", t->name, cp.str); free(t->line[i]); t->line[i] = _ctlrunestr(cp.args[2]); textshow(t); break; case EScroll: _ctlargcount(t, &cp, 2); t->scroll = cp.iargs[1]; break; case ESelect: if(cp.nargs!=2 && cp.nargs!=3) badselect: ctlerror("%q: bad select message: %s", t->name, cp.str); if(cp.nargs == 2){ if(strcmp(cp.args[1], "all") == 0){ memset(t->selected, 1, t->nline); break; } if(strcmp(cp.args[1], "none") == 0){ memset(t->selected, 0, t->nline); break; } if(cp.args[1][0]<'0' && '9'=t->nline) ctlerror("%q: selection index out of range (nline %d): %s", t->name, t->nline, cp.str); if(t->selected[cp.iargs[1]] != (cp.iargs[2]!=0)) texttogglei(t, cp.iargs[1]); break; case ESelectcolor: _ctlargcount(t, &cp, 2); _setctlimage(t, &t->selectcolor, cp.args[1]); break; case ESelectmode: _ctlargcount(t, &cp, 2); if(strcmp(cp.args[1], "single") == 0) t->selectmode = Selsingle; else if(strncmp(cp.args[1], "multi", 5) == 0) t->selectmode = Selmulti; break; case EShow: _ctlargcount(t, &cp, 1); textshow(t); break; case ETextcolor: _ctlargcount(t, &cp, 2); _setctlimage(t, &t->textcolor, cp.args[1]); break; break; case ETopline: _ctlargcount(t, &cp, 2); i = cp.iargs[1]; if(i < 0) i = 0; if(i > t->nline) i = t->nline; if(t->topline != i){ t->topline = i; textshow(t); } break; case EValue: /* set contents to single line */ /* free existing text and fall through to add */ for(i=0; inline; i++){ free(t->line[i]); t->line[i] = nil; } t->nline = 0; t->topline = 0; /* fall through */ case EAccumulate: case EAdd: switch (cp.nargs) { default: ctlerror("%q: wrong argument count in '%s'", t->name, cp.str); case 2: n = t->nline; break; case 3: n = cp.iargs[1]; if(n<0 || n>t->nline) ctlerror("%q: line number out of range: %s", t->name, cp.str); break; } rp = _ctlrunestr(cp.args[cp.nargs-1]); t->line = ctlrealloc(t->line, (t->nline+1)*sizeof(Rune*)); memmove(t->line+n+1, t->line+n, (t->nline-n)*sizeof(Rune*)); t->line[n] = rp; t->selected = ctlrealloc(t->selected, t->nline+1); memmove(t->selected+n+1, t->selected+n, t->nline-n); t->selected[n] = (t->selectmode==Selmulti && cmd!=EAccumulate); t->nline++; if(t->scroll) { if(n > t->topline + (t->nvis - 1)){ t->topline = n - (t->nvis - 1); if(t->topline < 0) t->topline = 0; } if(n < t->topline) t->topline = n; } if(cmd != EAccumulate) if(t->scroll || t->nline<=t->topline+t->nvis) textshow(t); break; } } static void texttogglei(Text *t, int i) { int prev; if(t->selectmode == Selsingle){ /* clear the others */ prev = t->selected[i]; memset(t->selected, 0, t->nline); t->selected[i] = prev; } t->selected[i] ^= 1; textshow(t); } static int texttoggle(Text *t, Point p) { Rectangle r; int i; r = t->rect; if(t->border > 0) r = insetrect(r, t->border); if(!ptinrect(p, r)) return -1; i = (p.y-r.min.y)/t->font->font->height; i += t->topline; if(i >= t->nline) return -1; texttogglei(t, i); return i; }