code: plan9front

Download patch

ref: 79cf39c53ab311f61fd3a3826cefeaead6342601
parent: 04bf70d344698014f9c85ef3c1ae8215223cdb45
author: Sigrid <ftrvxmtrx@gmail.com>
date: Tue Feb 16 17:04:50 EST 2021

mothra: tune up entry control logic for easier text editing

--- a/sys/src/cmd/mothra/libpanel/draw.c
+++ b/sys/src/cmd/mothra/libpanel/draw.c
@@ -13,8 +13,8 @@
 #define	CKWID	1	/* width of frame around check mark */
 #define	CKINSET	1	/* space around check mark frame */
 #define	CKBORDER 2	/* space around X inside frame */
-static Image *pl_white, *pl_light, *pl_dark, *pl_black, *pl_hilit;
-Image *pl_blue;
+static Image *pl_light, *pl_dark, *pl_tick, *pl_hilit;
+Image *pl_blue, *pl_white, *pl_black;
 int pl_drawinit(void){
 	pl_white=allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xFFFFFFFF);
 	pl_light=allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xFFFFFFFF);
@@ -22,7 +22,13 @@
 	pl_black=allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x000000FF);
 	pl_hilit=allocimage(display, Rect(0,0,1,1), CHAN1(CAlpha,8), 1, 0x80);
 	pl_blue=allocimage(display, Rect(0,0,1,1), RGB24, 1, 0x0000FFFF);
-	if(pl_white==0 || pl_light==0 || pl_black==0 || pl_dark==0 || pl_blue==0) sysfatal("allocimage: %r");
+	if((pl_tick = allocimage(display, Rect(0, 0, TICKW, font->height), screen->chan, 0, DNofill)) != nil){
+		draw(pl_tick, pl_tick->r, pl_white, nil, ZP);
+		draw(pl_tick, Rect(TICKW/2, 0, TICKW/2+1, font->height), pl_black, nil, ZP);
+		draw(pl_tick, Rect(0, 0, TICKW, TICKW), pl_black, nil, ZP);
+		draw(pl_tick, Rect(0, font->height-TICKW, TICKW, font->height), pl_black, nil, ZP);
+	}
+	if(pl_white==0 || pl_light==0 || pl_black==0 || pl_dark==0 || pl_blue==0 || pl_tick==0) sysfatal("allocimage: %r");
 	return 1;
 }
 Rectangle pl_boxoutline(Image *b, Rectangle r, int style, int fill){
@@ -226,6 +232,9 @@
 }
 void pl_highlight(Image *b, Rectangle r){
 	draw(b, r, pl_dark, pl_hilit, ZP);
+}
+void pl_drawtick(Image *b, Rectangle r){
+	draw(b, r, pl_tick, nil, ZP);
 }
 void pl_clr(Image *b, Rectangle r){
 	draw(b, r, display->white, 0, ZP);
--- a/sys/src/cmd/mothra/libpanel/entry.c
+++ b/sys/src/cmd/mothra/libpanel/entry.c
@@ -8,13 +8,25 @@
 
 typedef struct Entry Entry;
 struct Entry{
-	char *entry;
-	char *entp;
-	char *eent;
+	Rectangle lastr;
+	Rune *entry;
+	char *sentry;
+	int sz, n;
+	int a, b;
+	Point text;
 	void (*hit)(Panel *, char *);
 	Point minsize;
 };
 #define	SLACK	7	/* enough for one extra rune and ◀ and a nul */
+void pl_cutentry(Panel *p){
+	Entry *ep;
+
+	ep=p->data;
+	memmove(ep->entry+ep->a, ep->entry+ep->b, (ep->n-ep->b)*sizeof(Rune));
+	ep->n -= ep->b-ep->a;
+	ep->entry[ep->n]=0;
+	ep->b=ep->a;
+}
 char *pl_snarfentry(Panel *p){
 	Entry *ep;
 	int n;
@@ -22,54 +34,111 @@
 	if(p->flags&USERFL)	/* no snarfing from password entry */
 		return nil;
 	ep=p->data;
-	n=utfnlen(ep->entry, ep->entp-ep->entry);
+	n=ep->b-ep->a;
 	if(n<1) return nil;
-	return smprint("%.*s", n, ep->entry);
+	return smprint("%.*S", n, ep->entry+ep->a);
 }
 void pl_pasteentry(Panel *p, char *s){
 	Entry *ep;
-	char *e;
-	int n, m;
+	int m;
 
 	ep=p->data;
-	n=ep->entp-ep->entry;
-	m=strlen(s);
-	e=pl_erealloc(ep->entry,n+m+SLACK);
-	ep->entry=e;
-	e+=n;
-	strncpy(e, s, m);
-	e+=m;
-	*e='\0';
-	ep->entp=ep->eent=e;
+	m=utflen(s);
+	ep->sz=ep->n+m+100+SLACK;
+	ep->entry=pl_erealloc(ep->entry,ep->sz*sizeof(Rune));
+	memmove(ep->entry+ep->a+m, ep->entry+ep->b, (ep->n-ep->b)*sizeof(Rune));
+	ep->n+=m-(ep->b-ep->a);
+	while(m-- > 0)
+		s += chartorune(&ep->entry[ep->a++], s);
+	ep->b=ep->a;
+	ep->entry[ep->n]=0;
 	pldraw(p, p->b);
 }
+static void drawentry(Panel *p, Rectangle r, Rune *s){
+	Rectangle save;
+	Point tick;
+	Entry *ep;
+	Image *b;
+	int d;
+
+	ep = p->data;
+	b = p->b;
+
+	if(Dx(r) != Dx(ep->lastr)){
+		ep->text = r.min;
+		ep->lastr = r;
+	}
+	tick = ep->text;
+	tick.x += runestringnwidth(font, s, ep->a);
+
+	if(plkbfocus == p)
+		r.max.x -= TICKW;
+	ep->text.y = r.min.y;
+	if(!ptinrect(tick, r)){
+		d = 0;
+		if(tick.x < r.min.x)
+			d = r.min.x - tick.x;
+		else if(tick.x > r.max.x)
+			d = r.max.x - tick.x;
+		tick.x += d;
+		ep->text.x += d;
+	}
+	if(plkbfocus == p)
+		r.max.x += TICKW;
+
+	save = b->clipr;
+	if(!rectclip(&r, save))
+		return;
+	replclipr(b, b->repl, r);
+	runestring(b, ep->text, pl_black, ZP, font, s);
+	if(plkbfocus == p){
+		r.min = tick;
+		if(ep->a != ep->b){
+			r.max.x = ep->text.x+runestringnwidth(font, s, ep->b);
+			if(r.max.x < r.min.x){
+				d = r.min.x;
+				r.min.x = r.max.x;
+				r.max.x = d;
+			}
+			pl_highlight(b, r);
+		}else
+			pl_drawtick(b, r);
+	}
+	replclipr(b, b->repl, save);
+}
 void pl_drawentry(Panel *p){
 	Rectangle r;
 	Entry *ep;
-	char *s;
+	Rune *s;
 
 	ep=p->data;
 	r=pl_box(p->b, p->r, p->state|BORDER);
 	s=ep->entry;
 	if(p->flags & USERFL){
-		char *p;
-		s=strdup(s);
+		Rune *p;
+		s=runestrdup(s);
 		for(p=s; *p; p++)
 			*p='*';
 	}
-	if(stringwidth(font, s)<=r.max.x-r.min.x)
-		pl_drawicon(p->b, r, PLACEW, 0, s);
-	else
-		pl_drawicon(p->b, r, PLACEE, 0, s);
+	drawentry(p, r, s);
 	if(s != ep->entry)
 		free(s);
 }
 int pl_hitentry(Panel *p, Mouse *m){
+	Entry *ep;
+	int i, n, selecting;
 	if((m->buttons&7)==1){
+		if(plkbfocus != p)
+			p->state=DOWN;
 		plgrabkb(p);
-
-		p->state=DOWN;
+		ep = p->data;
+		for(i = 1; i <= ep->n; i++)
+			if(runestringnwidth(font, ep->entry, i) > m->xy.x-ep->text.x)
+				break;
+		n = i-1;
+		ep->a = ep->b = n;
 		pldraw(p, p->b);
+		selecting = 1;
 		while(m->buttons&1){
 			int old;
 			old=m->buttons;
@@ -76,18 +145,33 @@
 			if(display->bufp > display->buf)
 				flushimage(display, 1);
 			*m=emouse();
+			p->state=UP;
 			if((old&7)==1){
 				if((m->buttons&7)==3){
-					Entry *ep;
-
 					plsnarf(p);
-
-					/* cut */
-					ep=p->data;
-					ep->entp=ep->entry;
-					*ep->entp='\0';
+					pl_cutentry(p);
 					pldraw(p, p->b);
+					ep->b = n = ep->a;
 				}
+				if(selecting && (m->buttons&7)==1){
+					p->state=UP;
+					for(i = 0; i < ep->n; i++)
+						if(runestringnwidth(font, ep->entry, i)+TICKW > m->xy.x-ep->text.x)
+							break;
+					/*
+					 * tick is moved towards the mouse pointer dragging the selection
+					 * after drawing it has to be set so that (a <= b), since
+					 * the rest of the logic assumes that's always the case
+					 */
+					ep->a = i;
+					ep->b = n;
+					pldraw(p, p->b);
+					if(ep->a > ep->b){
+						ep->a = n;
+						ep->b = i;
+					}
+				}else
+					selecting = 0;
 				if((m->buttons&7)==5)
 					plpaste(p);
 			}
@@ -98,45 +182,67 @@
 	return 0;
 }
 void pl_typeentry(Panel *p, Rune c){
-	int n;
 	Entry *ep;
 	ep=p->data;
 	switch(c){
 	case '\n':
 	case '\r':
-		*ep->entp='\0';
-		if(ep->hit) ep->hit(p, ep->entry);
+		if(ep->hit) ep->hit(p, plentryval(p));
 		return;
+	case Kleft:
+		if(ep->a > 0)
+			ep->a--;
+		ep->b=ep->a;
+		break;
+	case Kright:
+		if(ep->a<ep->n)
+			ep->a++;
+		ep->b = ep->a;
+		break;
+	case Ksoh:
+		ep->a=ep->b=0;
+		break;
+	case Kenq:
+		ep->a=ep->b=ep->n;
+		break;
 	case Kesc:
+		ep->a=0;
+		ep->b=ep->n;
 		plsnarf(p);
 		/* no break */
 	case Kdel:	/* clear */
+		ep->a = ep->b = ep->n = 0;
+		*ep->entry = 0;
+		break;
 	case Knack:	/* ^U: erase line */
-		ep->entp=ep->entry;
-		*ep->entp='\0';
+		ep->a = 0;
+		pl_cutentry(p);
 		break;
 	case Kbs:	/* ^H: erase character */
-		while(ep->entp!=ep->entry && !pl_rune1st(ep->entp[-1])) *--ep->entp='\0';
-		if(ep->entp!=ep->entry) *--ep->entp='\0';
-		break;
+		if(ep->a > 0 && ep->a == ep->b)
+			ep->a--;
+		/* wet floor */
+		if(0){
 	case Ketb:	/* ^W: erase word */
-		while(ep->entp!=ep->entry && !pl_idchar(ep->entp[-1]))
-			--ep->entp;
-		while(ep->entp!=ep->entry && pl_idchar(ep->entp[-1]))
-			--ep->entp;
-		*ep->entp='\0';
+			while(ep->a>0 && !pl_idchar(ep->entry[ep->a-1]))
+				--ep->a;
+			while(ep->a>0 && pl_idchar(ep->entry[ep->a-1]))
+				--ep->a;
+		}
+		pl_cutentry(p);
 		break;
 	default:
 		if(c < 0x20 || (c & 0xFF00) == KF || (c & 0xFF00) == Spec)
 			break;
-		ep->entp+=runetochar(ep->entp, &c);
-		if(ep->entp>ep->eent){
-			n=ep->entp-ep->entry;
-			ep->entry=pl_erealloc(ep->entry, n+100+SLACK);
-			ep->entp=ep->entry+n;
-			ep->eent=ep->entp+100;
+		memmove(ep->entry+ep->a+1, ep->entry+ep->b, (ep->n-ep->b)*sizeof(Rune));
+		ep->n -= ep->b - ep->a - 1;
+		ep->entry[ep->a++] = c;
+		ep->b = ep->a;
+		if(ep->n>ep->sz){
+			ep->sz = ep->n+100;
+			ep->entry=pl_erealloc(ep->entry, (ep->sz+SLACK)*sizeof(Rune));
 		}
-		*ep->entp='\0';
+		ep->entry[ep->n]=0;
 		break;
 	}
 	pldraw(p, p->b);
@@ -152,10 +258,11 @@
 	Entry *ep;
 	ep = p->data;
 	free(ep->entry);
-	ep->entry = ep->eent = ep->entp = 0;
+	free(ep->sentry);
+	ep->entry = nil;
+	ep->sentry = nil;
 }
 void plinitentry(Panel *v, int flags, int wid, char *str, void (*hit)(Panel *, char *)){
-	int elen;
 	Entry *ep;
 	ep=v->data;
 	v->flags=flags|LEAF;
@@ -169,12 +276,11 @@
 	v->free=pl_freeentry;
 	v->snarf=pl_snarfentry;
 	v->paste=pl_pasteentry;
-	elen=100;
-	if(str) elen+=strlen(str);
-	ep->entry=pl_erealloc(ep->entry, elen+SLACK);
-	ep->eent=ep->entry+elen;
-	strecpy(ep->entry, ep->eent, str ? str : "");
-	ep->entp=ep->entry+strlen(ep->entry);
+	ep->a = ep->b = 0;
+	ep->n = str ? utflen(str) : 0;
+	ep->sz = ep->n + 100;
+	ep->entry=pl_erealloc(ep->entry, (ep->sz+SLACK)*sizeof(Rune));
+	runesnprint(ep->entry, ep->sz, "%s", str ? str : "");
 	ep->hit=hit;
 	v->kind="entry";
 }
@@ -187,6 +293,7 @@
 char *plentryval(Panel *p){
 	Entry *ep;
 	ep=p->data;
-	*ep->entp='\0';
-	return ep->entry;
+	free(ep->sentry);
+	ep->sentry = smprint("%S", ep->entry);
+	return ep->sentry;
 }
--- a/sys/src/cmd/mothra/libpanel/event.c
+++ b/sys/src/cmd/mothra/libpanel/event.c
@@ -6,6 +6,11 @@
 #include "pldefs.h"
 
 void plgrabkb(Panel *g){
+	Panel *o;
+	o=plkbfocus;
+	plkbfocus=nil;
+	if(o && o!=g) /* redraw if lost focus */
+		pldraw(o, o->b);
 	plkbfocus=g;
 }
 void plkeyboard(Rune c){
--- a/sys/src/cmd/mothra/libpanel/pldefs.h
+++ b/sys/src/cmd/mothra/libpanel/pldefs.h
@@ -13,6 +13,7 @@
 #define	LEAF	0x10000		/* newpanel will refuse to attach children */
 #define	INVIS	0x20000		/* don't draw this */
 #define	REMOUSE	0x40000		/* send next mouse event here, even if not inside */
+#define	TICKW	3			/* tick width */
 /*
  * States, also styles
  */
@@ -40,7 +41,7 @@
 	SCROLLABSX,
 };
 
-extern Image *pl_blue;
+extern Image *pl_blue, *pl_white, *pl_black;
 
 /*
  * Scrollbar, slider orientations
@@ -70,6 +71,7 @@
 void pl_invis(Panel *, int);
 Point pl_iconsize(int, Icon *);
 void pl_highlight(Image *, Rectangle);
+void pl_drawtick(Image *, Rectangle);
 void pl_clr(Image *, Rectangle);
 void pl_fill(Image *, Rectangle);
 void pl_cpy(Image *, Point, Rectangle);
--- a/sys/src/cmd/mothra/mothra.c
+++ b/sys/src/cmd/mothra/mothra.c
@@ -399,6 +399,7 @@
 		case Ekeyboard:
 			switch(e.kbdc){
 			default:
+Plkey:
 				adjkb();
 				plkeyboard(e.kbdc);
 				break;
@@ -424,9 +425,13 @@
 				search();
 				break;
 			case Kright:
+				if(plkbfocus)
+					goto Plkey;
 				sidescroll(text->size.x/4, 1);
 				break;
 			case Kleft:
+				if(plkbfocus)
+					goto Plkey;
 				sidescroll(-text->size.x/4, 1);
 				break;
 			}
@@ -441,6 +446,8 @@
 				break;
 			}
 			plmouse(root, &mouse);
+			if(mouse.buttons == 1 && root->lastmouse == root)
+				plgrabkb(nil);
 			break;
 		case Eplumb:
 			pm=e.v;
--- a/sys/src/cmd/mothra/rdhtml.c
+++ b/sys/src/cmd/mothra/rdhtml.c
@@ -374,7 +374,7 @@
 /*
  * Skip over white space
  */
-char *pl_white(char *s){
+char *pl_whitespace(char *s){
 	while(*s==' ' || *s=='\t' || *s=='\n' || *s=='\r') s++;
 	return s;
 }
@@ -431,7 +431,7 @@
 	g->tag=tagp-tag;
 	if(g->tag==Tag_end) htmlerror(g->name, g->lineno, "no tag %s", name);
 	for(;;){
-		s=pl_white(s);
+		s=pl_whitespace(s);
 		if(*s=='\0'){
 			ap->name=0;
 			return;
@@ -438,12 +438,12 @@
 		}
 		ap->name=s;
 		s=pl_word(s);
-		t=pl_white(s);
+		t=pl_whitespace(s);
 		c=*t;
 		*s='\0';
 		for(s=ap->name;*s;s++) if('A'<=*s && *s<='Z') *s+='a'-'A';
 		if(c=='='){
-			s=pl_white(t+1);
+			s=pl_whitespace(t+1);
 			if(*s=='\'' || *s=='"'){
 				ap->value=s+1;
 				s=pl_quote(s);