jehanne/sys/src/lib/control/text.c

552 lines
12 KiB
C

/*
* This file is part of the UCB release of Plan 9. It is subject to the license
* terms in the LICENSE file found in the top-level directory of this
* distribution and at http://akaros.cs.berkeley.edu/files/Plan9License. No
* part of the UCB release of Plan 9, including this file, may be copied,
* modified, propagated, or distributed except according to the terms contained
* in the LICENSE file.
*/
#include <u.h>
#include <lib9.h>
#include <draw.h>
#include <thread.h>
#include <mouse.h>
#include <keyboard.h>
#include <control.h>
static int debug = 0;
typedef struct Text Text;
struct Text
{
Control;
int border;
int topline;
int scroll;
int nvis;
int lastbut;
CFont *font;
CImage *image;
CImage *textcolor;
CImage *bordercolor;
CImage *selectcolor;
CImage *selectingcolor;
Rune **line;
int selectmode; // Selsingle, Selmulti
int selectstyle; // Seldown, Selup (use Selup only with Selsingle)
uint8_t *selected;
int nline;
int warp;
int align;
int sel; // line nr of selection made by last button down
int but; // last button down (still being hold)
int offsel; // we are on selection
};
enum
{
Selsingle,
Selmulti,
Seldown,
Selup,
};
enum{
EAccumulate,
EAdd,
EAlign,
EBorder,
EBordercolor,
EClear,
EDelete,
EFocus,
EFont,
EHide,
EImage,
ERect,
EReplace,
EReveal,
EScroll,
ESelect,
ESelectcolor,
ESelectingcolor,
ESelectmode,
ESelectstyle,
EShow,
ESize,
ETextcolor,
ETopline,
EValue,
EWarp,
};
static char *cmds[] = {
[EAccumulate] = "accumulate",
[EAdd] = "add",
[EAlign] = "align",
[EBorder] = "border",
[EBordercolor] = "bordercolor",
[EClear] = "clear",
[EDelete] = "delete",
[EFocus] = "focus",
[EFont] = "font",
[EHide] = "hide",
[EImage] = "image",
[ERect] = "rect",
[EReplace] = "replace",
[EReveal] = "reveal",
[EScroll] = "scroll",
[ESelect] = "select",
[ESelectcolor] = "selectcolor",
[ESelectingcolor] = "selectingcolor",
[ESelectmode] = "selectmode",
[ESelectstyle] = "selectstyle",
[EShow] = "show",
[ESize] = "size",
[ETextcolor] = "textcolor",
[ETopline] = "topline",
[EValue] = "value",
[EWarp] = "warp",
nil
};
static void textshow(Text*);
static void texttogglei(Text*, int);
static int textline(Text*, Point);
static int texttoggle(Text*, Point);
static void
textmouse(Control *c, Mouse *m)
{
Text *t;
int sel;
t = (Text*)c;
if (debug) fprint(2, "textmouse %s t->lastbut %d; m->buttons %d\n", t->name, t->lastbut, m->buttons);
if (t->warp >= 0)
return;
if ((t->selectstyle == Selup) && (m->buttons&7)) {
sel = textline(t, m->xy);
if (t->sel >= 0) {
// if (debug) fprint(2, "textmouse Selup %q sel=%d t->sel=%d t->but=%d\n",
// t->name, sel, t->sel, t->but);
t->offsel = (sel == t->sel) ? 0 : 1;
if ((sel == t->sel &&
((t->selected[t->sel] && !t->but) ||
((!t->selected[t->sel]) && t->but))) ||
(sel != t->sel &&
((t->selected[t->sel] && t->but) ||
((!t->selected[t->sel]) && (!t->but))))) {
texttogglei(t, t->sel);
}
}
}
if(t->lastbut != (m->buttons&7)){
if(m->buttons & 7){
sel = texttoggle(t, m->xy);
if(sel >= 0) {
if (t->selectstyle == Seldown) {
chanprint(t->event, "%q: select %d %d",
t->name, sel, t->selected[sel] ? (m->buttons & 7) : 0);
if (debug) fprint(2, "textmouse Seldown event %q: select %d %d\n",
t->name, sel, t->selected[sel] ? (m->buttons & 7) : 0);
} else {
if (debug) fprint(2, "textmouse Selup no event yet %q: select %d %d\n",
t->name, sel, t->selected[sel] ? (m->buttons & 7) : 0);
t->sel = sel;
t->but = t->selected[sel] ? (m->buttons & 7) : 0;
}
}
} else if (t->selectstyle == Selup) {
sel = textline(t, m->xy);
t->offsel = 0;
if ((sel >= 0) && (sel == t->sel)) {
chanprint(t->event, "%q: select %d %d",
t->name, sel, t->but);
if (debug) fprint(2, "textmouse Selup event %q: select %d %d\n",
t->name, sel, t->but);
} else if (sel != t->sel) {
if ((t->selected[t->sel] && t->but) ||
((!t->selected[t->sel]) && (!t->but))) {
texttogglei(t, t->sel);
} else {
textshow(t);
}
if (debug) fprint(2, "textmouse Selup cancel %q: select %d %d\n",
t->name, sel, t->but);
}
t->sel = -1;
t->but = 0;
}
t->lastbut = m->buttons & 7;
}
}
static void
textfree(Control *c)
{
int i;
Text *t;
t = (Text*)c;
_putctlfont(t->font);
_putctlimage(t->image);
_putctlimage(t->textcolor);
_putctlimage(t->bordercolor);
_putctlimage(t->selectcolor);
_putctlimage(t->selectingcolor);
for(i=0; i<t->nline; 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;
if (t->hidden)
return;
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; i<t->nline && i<t->topline+t->nvis; i++){
text = t->line[i];
ntext = runestrlen(text);
r.max.y = r.min.y+f->height;
if(t->sel == i && t->offsel)
draw(t->screen, r, t->selectingcolor->image, nil, ZP);
else if(t->selected[i])
draw(t->screen, r, t->selectcolor->image, nil, ZP);
p = _ctlalignpoint(r,
runestringnwidth(f, text, ntext),
f->height, t->align);
if(t->warp == i) {
Point p2;
p2.x = p.x + 0.5*runestringnwidth(f, text, ntext);
p2.y = p.y + 0.5*f->height;
moveto(t->controlset->mousectl, p2);
t->warp = -1;
}
_string(t->screen, p, t->textcolor->image,
ZP, f, nil, text, ntext, tr,
nil, ZP, SoverD);
r.min.y += f->height;
}
flushimage(display, 1);
}
static void
textctl(Control *c, CParse *cp)
{
int cmd, i, n;
Rectangle r;
Text *t;
Rune *rp;
t = (Text*)c;
cmd = _ctllookup(cp->args[0], cmds, nelem(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; i<t->nline; 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 EHide:
_ctlargcount(t, cp, 1);
t->hidden = 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 EReveal:
_ctlargcount(t, cp, 1);
t->hidden = 0;
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'<cp->args[1][0])
goto badselect;
texttogglei(t, cp->iargs[1]);
break;
}
if(cp->iargs[1]<0 || cp->iargs[1]>=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 ESelectstyle:
_ctlargcount(t, cp, 2);
if(strcmp(cp->args[1], "down") == 0)
t->selectstyle = Seldown;
else if(strcmp(cp->args[1], "up") == 0)
t->selectstyle = Selup;
break;
case EShow:
_ctlargcount(t, cp, 1);
textshow(t);
break;
case ESize:
if (cp->nargs == 3)
r.max = Pt(10000, 10000);
else{
_ctlargcount(t, cp, 5);
r.max.x = cp->iargs[3];
r.max.y = cp->iargs[4];
}
r.min.x = cp->iargs[1];
r.min.y = cp->iargs[2];
if(r.min.x<=0 || r.min.y<=0 || r.max.x<=0 || r.max.y<=0 || r.max.x < r.min.x || r.max.y < r.min.y)
ctlerror("%q: bad sizes: %s", t->name, cp->str);
t->size.min = r.min;
t->size.max = r.max;
break;
case ETextcolor:
_ctlargcount(t, cp, 2);
_setctlimage(t, &t->textcolor, cp->args[1]);
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; i<t->nline; 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;
case EWarp:
_ctlargcount(t, cp, 2);
i = cp->iargs[1];
if(i <0 || i>=t->nline)
ctlerror("%q: selection index out of range (nline %d): %s", t->name, t->nline, cp->str);
if(i < t->topline || i >= t->topline+t->nvis){
t->topline = i;
}
t->warp = cp->iargs[1];
textshow(t);
t->warp = -1;
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
textline(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;
return i;
}
static int
texttoggle(Text *t, Point p)
{
int i;
i = textline(t, p);
if (i >= 0)
texttogglei(t, i);
return i;
}
Control*
createtext(Controlset *cs, char *name)
{
Text *t;
t = (Text*)_createctl(cs, "text", sizeof(Text), name);
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->selectingcolor = _getctlimage("paleyellow");
t->font = _getctlfont("font");
t->selectmode = Selsingle;
t->selectstyle = Selup; // Seldown;
t->lastbut = 0;
t->mouse = textmouse;
t->ctl = textctl;
t->exit = textfree;
t->warp = -1;
t->sel = -1;
t->offsel = 0;
t->but = 0;
return (Control *)t;
}