475 lines
12 KiB
C
475 lines
12 KiB
C
|
/*
|
||
|
* 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 %ld of %d, loc %d %d, off bottom\n",
|
||
|
*r, lp-t->loc, t->bot-t->top, lp->x, lp->y);
|
||
|
return;
|
||
|
}
|
||
|
switch(*r){
|
||
|
case '\n':
|
||
|
ur=*lp;
|
||
|
break;
|
||
|
case '\t':
|
||
|
ur=*lp;
|
||
|
if(lp[1].y!=lp[0].y)
|
||
|
ul=Pt(t->r.min.x, lp[1].y);
|
||
|
else
|
||
|
ul=*lp;
|
||
|
pl_clr(t->b, Rpt(ul, Pt(lp[1].x, ul.y+t->hgt)));
|
||
|
break;
|
||
|
default:
|
||
|
buf[runetochar(buf, r)]='\0';
|
||
|
/***/ pl_clr(t->b, Rpt(*lp, addpt(*lp, stringsize(t->font, buf))));
|
||
|
ur=string(t->b, *lp, display->black, ZP, t->font, buf);
|
||
|
break;
|
||
|
}
|
||
|
if(lp[1].y!=lp[0].y)
|
||
|
/***/ pl_clr(t->b, Rpt(ur, Pt(t->r.max.x, ur.y+t->hgt)));
|
||
|
}
|
||
|
}
|
||
|
/*
|
||
|
* Hilight the characters with tops between ul and ur
|
||
|
*/
|
||
|
void tw_hilitep(Textwin *t, Point ul, Point ur){
|
||
|
Point swap;
|
||
|
int y;
|
||
|
if(tw_before(t, ur, ul)){ swap=ul; ul=ur; ur=swap;}
|
||
|
y=ul.y+t->hgt;
|
||
|
if(y>t->r.max.y) y=t->r.max.y;
|
||
|
if(ul.y==ur.y)
|
||
|
pl_highlight(t->b, Rpt(ul, Pt(ur.x, y)));
|
||
|
else{
|
||
|
pl_highlight(t->b, Rpt(ul, Pt(t->r.max.x, y)));
|
||
|
ul=Pt(t->r.min.x, y);
|
||
|
pl_highlight(t->b, Rpt(ul, Pt(t->r.max.x, ur.y)));
|
||
|
ul=Pt(t->r.min.x, ur.y);
|
||
|
y=ur.y+t->hgt;
|
||
|
if(y>t->r.max.y) y=t->r.max.y;
|
||
|
pl_highlight(t->b, Rpt(ul, Pt(ur.x, y)));
|
||
|
}
|
||
|
}
|
||
|
/*
|
||
|
* Hilite/unhilite the given range of characters
|
||
|
*/
|
||
|
void twhilite(Textwin *t, int sel0, int sel1, int on){
|
||
|
Point ul, ur;
|
||
|
int swap, y;
|
||
|
if(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);
|
||
|
}
|