ref: 8bebe3ab193201f2bd9c32328e229bf36fd2ad82
dir: /sys/src/cmd/mothra/libpanel/textwin.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 %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); }