shithub: purgatorio

ref: fb7dd4b3a868cb8987049c95bb32e6425a73c8b9
dir: /appl/acme/frame.b/

View raw version
implement Framem;

include "common.m";

sys : Sys;
drawm : Draw;
acme : Acme;
gui : Gui;
graph : Graph;
utils : Utils;
textm : Textm;

sprint : import sys;
Point, Rect, Font, Image, Pointer : import drawm;
draw, berror, charwidth, strwidth : import graph;
black, white : import gui;

SLOP : con 25;

noglyphs := array[4] of { 16rFFFD, 16r80, '?', ' ' };

frame : ref Frame;

init(mods : ref Dat->Mods)
{
	sys = mods.sys;
	drawm = mods.draw;
	acme = mods.acme;
	gui = mods.gui;
	graph = mods.graph;
	utils = mods.utils;
	textm = mods.textm;

	frame = newframe();
}

nullframe : Frame;

newframe() : ref Frame
{
	f := ref nullframe;
	f.cols = array[NCOL] of ref Draw->Image;
	return f;
}

frdump(f : ref Frame)
{
	utils->debug(sprint("nchars=%d\n", f.nchars));
	for (i := 0; i < f.nbox; i++) {
		utils->debug(sprint("box %d : ", i));
		fb := f.box[i];
		if (fb.nrune >= 0)
			utils->debug(sprint("%d %d %s\n", fb.nrune, len fb.ptr, fb.ptr));
		else
			utils->debug(sprint("%d\n", fb.nrune));
	}
}

# debugcheck(f : ref Frame, n : int)
# {
# 	if (f.nchars != xfrstrlen(f, 0)) {
#		utils->debug(sprint("%d : bad frame nchars\n", n));
#		frdump(f);
#		berror("");
#	}
# }
		
xfraddbox(f : ref Frame, bn : int, n : int)		# add n boxes after bn, shift the rest up,
									#  * box[bn+n]==box[bn]
{
	i : int;

	if(bn > f.nbox)
		berror("xfraddbox");
	# bn = f.nbox has same effect as bn = f.nbox-1
	if(f.nbox+n > f.nalloc)
		xfrgrowbox(f, n+SLOP);
	for (i=f.nbox; --i > bn; ) {
		t := f.box[i+n];
		f.box[i+n] = f.box[i];
		f.box[i] = t;
	}
	if (bn < f.nbox)
		*f.box[bn+n] = *f.box[bn];
	f.nbox+=n;
}

xfrclosebox(f : ref Frame, n0 : int, n1 : int)	# inclusive
{
	i: int;

	if(n0>=f.nbox || n1>=f.nbox || n1<n0)
		berror("xfrclosebox");
	n1++;
	for(i=n1; i<f.nbox; i++) {
		t := f.box[i-(n1-n0)];
		f.box[i-(n1-n0)] = f.box[i];
		f.box[i] = t;
	}
	f.nbox -= n1-n0;
}

xfrdelbox(f : ref Frame, n0 : int, n1 : int)		# inclusive
{
	if(n0>=f.nbox || n1>=f.nbox || n1<n0)
		berror("xfrdelbox");
	xfrfreebox(f, n0, n1);
	xfrclosebox(f, n0, n1);
}

xfrfreebox(f : ref Frame, n0 : int, n1 : int)		# inclusive
{
	i : int;

	if(n1<n0)
		return;
	if(n0>=f.nbox || n1>=f.nbox)
		berror("xfrfreebox");
	n1++;
	for(i=n0; i<n1; i++)
		if(f.box[i].nrune >= 0) {
			f.box[i].nrune = 0;
			f.box[i].ptr = nil;
		}
}

nilfrbox : Frbox;

xfrgrowbox(f : ref Frame, delta : int)
{
	ofb := f.box;
	f.box = array[f.nalloc+delta] of ref Frbox;
	if(f.box == nil)
		berror("xfrgrowbox");
	f.box[0:] = ofb[0:f.nalloc];
	for (i := 0; i < delta; i++)
		f.box[i+f.nalloc] = ref nilfrbox;
	f.nalloc += delta;
	ofb = nil;
}

dupbox(f : ref Frame, bn : int)
{
	if(f.box[bn].nrune < 0)
		berror("dupbox");
	xfraddbox(f, bn, 1);
	if(f.box[bn].nrune >= 0) {
		f.box[bn+1].nrune = f.box[bn].nrune;
		f.box[bn+1].ptr = f.box[bn].ptr;
	}
}

truncatebox(f : ref Frame, b : ref Frbox, n : int)	# drop last n chars; no allocation done
{
	if(b.nrune<0 || b.nrune<n)
		berror("truncatebox");
	b.nrune -= n;
	b.ptr = b.ptr[0:b.nrune];
	b.wid = strwidth(f.font, b.ptr);
}

chopbox(f : ref Frame, b : ref Frbox, n : int)	# drop first n chars; no allocation done
{
	if(b.nrune<0 || b.nrune<n)
		berror("chopbox");
	b.nrune -= n;
	b.ptr = b.ptr[n:];
	b.wid = strwidth(f.font, b.ptr);
}

xfrsplitbox(f : ref Frame, bn : int, n : int)
{
	dupbox(f, bn);
	truncatebox(f, f.box[bn], f.box[bn].nrune-n);
	chopbox(f, f.box[bn+1], n);
}

xfrmergebox(f : ref Frame, bn : int)		# merge bn and bn+1
{
	b0 := f.box[bn];
	b1 := f.box[bn+1];
	b0.ptr += b1.ptr;
	b0.wid += b1.wid;
	b0.nrune += b1.nrune;
	xfrdelbox(f, bn+1, bn+1);
}

xfrfindbox(f : ref Frame, bn : int, p : int, q : int) : int	# find box containing q and put q on a box boundary
{
	nrune : int;

	for( ; bn < f.nbox; bn++) {
		nrune = 1;
		b := f.box[bn];
# if (b.nrune >= 0 && len b.ptr != b.nrune) {
#	frdump(f);
#	berror(sprint("findbox %d %d %d\n", bn, p, q));
# }
		if(b.nrune >= 0)
			nrune = b.nrune;
		if(p+nrune > q)
			break; 
		p += nrune;
	}
	if(p != q)
		xfrsplitbox(f, bn++, q-p);
	return bn;
}

frdelete(f : ref Frame, p0 : int, p1 : int) : int
{
	pt0, pt1, ppt0 : Point;
	n0, n1, n, s : int;
	r : Rect;
	nn0 : int;
	col : ref Image;

	if(p0 >= f.nchars || p0 == p1 || f.b == nil)
		return 0;
	if(p1 > f.nchars)
		p1 = f.nchars;
	n0 = xfrfindbox(f, 0, 0, p0);
	if(n0 == f.nbox)
		berror("off end in frdelete");
	n1 = xfrfindbox(f, n0, p0, p1);
	pt0 = xfrptofcharnb(f, p0, n0);
	pt1 = frptofchar(f, p1);
	if(f.p0 == f.p1)
		frtick(f, frptofchar(f, f.p0), 0);
	nn0 = n0;
	ppt0 = pt0;
	xfrfreebox(f, n0, n1-1);
	f.modified = 1;

	#
	# Invariants:
	#  pt0 points to beginning, pt1 points to end
	#  n0 is box containing beginning of stuff being deleted
	#  n1, b are box containing beginning of stuff to be kept after deletion
	# cn1 is char position of n1
	# f.p0 and f.p1 are not adjusted until after all deletion is done
	#  region between pt0 and pt1 is clear
	#
	cn1 := p1;
	while(pt1.x!=pt0.x && n1<f.nbox){
		b := f.box[n1];
		pt0 = xfrcklinewrap0(f, pt0, b);
		pt1 = xfrcklinewrap(f, pt1, b);
		n = xfrcanfit(f, pt0, b);
		if(n==0)
			berror("xfrcanfit==0");
		r.min = pt0;
		r.max = pt0;
		r.max.y += f.font.height;
		if(b.nrune > 0){
			if(n != b.nrune){
				xfrsplitbox(f, n1, n);
				b = f.box[n1];
			}
			r.max.x += b.wid;
			draw(f.b, r, f.b, nil, pt1);
			cn1 += b.nrune;
		}
		else{
			r.max.x += xfrnewwid0(f, pt0, b);
			if(r.max.x > f.r.max.x)
				r.max.x = f.r.max.x;
			col = f.cols[BACK];
			if(f.p0<=cn1 && cn1<f.p1)
				col = f.cols[HIGH];
			draw(f.b, r, col, nil, pt0);
			cn1++;
		}
		pt1 = xfradvance(f, pt1, b);
		pt0.x += xfrnewwid(f, pt0, b);
		*f.box[n0++] = *f.box[n1++];
	}
	if(n1==f.nbox && pt0.x!=pt1.x)	# deleting last thing in window; must clean up
		frselectpaint(f, pt0, pt1, f.cols[BACK]);
	if(pt1.y != pt0.y){
		pt2 : Point;

		pt2 = xfrptofcharptb(f, 32767, pt1, n1);
		if(pt2.y > f.r.max.y)
			berror("frptofchar in frdelete");
		if(n1 < f.nbox){
			q0, q1, q2 : int;

			q0 = pt0.y+f.font.height;
			q1 = pt1.y+f.font.height;
			q2 = pt2.y+f.font.height;
			# rob: before was just q2 = pt1.y+f.font.height;
			# q2 = pt2.y;
			if(q2 > f.r.max.y)
				q2 = f.r.max.y;
			draw(f.b, (pt0, (pt0.x+(f.r.max.x-pt1.x), q0)),
				f.b, nil, pt1);
			draw(f.b, ((f.r.min.x, q0), (f.r.max.x, q0+(q2-q1))),
				f.b, nil, (f.r.min.x, q1));
			frselectpaint(f, (pt2.x, pt2.y-(pt1.y-pt0.y)), pt2, f.cols[BACK]);
		}else
			frselectpaint(f, pt0, pt2, f.cols[BACK]);
	}
	xfrclosebox(f, n0, n1-1);
	if(nn0>0 && f.box[nn0-1].nrune>=0 && ppt0.x-f.box[nn0-1].wid>=f.r.min.x){
		--nn0;
		ppt0.x -= f.box[nn0].wid;
	}
	s = n0;
	if(n0 < f.nbox-1)
		s++;
	xfrclean(f, ppt0, nn0, s);
	if(f.p1 > p1)
		f.p1 -= p1-p0;
	else if(f.p1 > p0)
		f.p1 = p0;
	if(f.p0 > p1)
		f.p0 -= p1-p0;
	else if(f.p0 > p0)
		f.p0 = p0;
	f.nchars -= p1-p0;
	if(f.p0 == f.p1)
		frtick(f, frptofchar(f, f.p0), 1);
	pt0 = frptofchar(f, f.nchars);
	n = f.nlines;
	f.nlines = (pt0.y-f.r.min.y)/f.font.height+(pt0.x>f.r.min.x);
	return n - f.nlines;
}

xfrredraw(f : ref Frame, pt : Point)
{
	nb : int;

	for(nb = 0; nb < f.nbox; nb++) {
		b := f.box[nb];
		pt = xfrcklinewrap(f, pt, b);
		if(b.nrune >= 0)
			graph->stringx(f.b, pt, f.font, b.ptr, f.cols[TEXT]);
		pt.x += b.wid;
	}
}

frdrawsel(f : ref Frame, pt : Point, p0 : int, p1 : int, issel : int)
{
	back, text : ref Image;

	if(f.ticked)
		frtick(f, frptofchar(f, f.p0), 0);
	if(p0 == p1){
		frtick(f, pt, issel);
		return;
	}
	if(issel){
		back = f.cols[HIGH];
		text = f.cols[HTEXT];
	}else{
		back = f.cols[BACK];
		text = f.cols[TEXT];
	}
	frdrawsel0(f, pt, p0, p1, back, text);
}

frdrawsel0(f : ref Frame, pt : Point, p0 : int, p1 : int, back : ref Image, text : ref Image)
{
	b : ref Frbox;
	nb, nr, w, x, trim : int;
	qt : Point;
	p : int;
	ptr : string;

	p = 0;
	trim = 0;
	for(nb=0; nb<f.nbox && p<p1; nb++){
		b = f.box[nb];
		nr = b.nrune;
		if(nr < 0)
			nr = 1;
		if(p+nr <= p0){
			p += nr;
			continue;
		}
		if(p >= p0){
			qt = pt;
			pt = xfrcklinewrap(f, pt, b);
			if(pt.y > qt.y)
				draw(f.b, (qt, (f.r.max.x, pt.y)), back, nil, qt);
		}
		ptr = b.ptr;
		if(p < p0){		# beginning of region: advance into box
			ptr = ptr[p0-p:];
			nr -= (p0-p);
			p = p0;
		}
		trim = 0;
		if(p+nr > p1){	# end of region: trim box
			nr -= (p+nr)-p1;
			trim = 1;
		}
		if(b.nrune<0 || nr==b.nrune)
			w = b.wid;
		else
			w = strwidth(f.font, ptr[0:nr]);
		x = pt.x+w;
		if(x > f.r.max.x)
			x = f.r.max.x;
		draw(f.b, (pt, (x, pt.y+f.font.height)), back, nil, pt);
		if(b.nrune >= 0)
			graph->stringx(f.b, pt, f.font, ptr[0:nr], text);
		pt.x += w;
		p += nr;
	}
	# if this is end of last plain text box on wrapped line, fill to end of line
	if(p1>p0 &&  nb>0 && nb<f.nbox && f.box[nb-1].nrune>0 && !trim){
		qt = pt;
		pt = xfrcklinewrap(f, pt, f.box[nb]);
		if(pt.y > qt.y)
			draw(f.b, (qt, (f.r.max.x, pt.y)), back, nil, qt);
	}
}

frtick(f : ref Frame, pt : Point, ticked : int)
{
	r : Rect;

	if(f.ticked==ticked || f.tick==nil || !pt.in(f.r))
		return;
	pt.x--;	# looks best just left of where requested
	r = (pt, (pt.x+FRTICKW, pt.y+f.font.height));
	if(ticked){
		draw(f.tickback, f.tickback.r, f.b, nil, pt);
		draw(f.b, r, f.tick, nil, (0, 0));
	}else
		draw(f.b, r, f.tickback, nil, (0, 0));
	f.ticked = ticked;
}

xfrdraw(f : ref Frame, pt : Point) : Point
{
	nb, n : int;

	for(nb=0; nb < f.nbox; nb++){
		b := f.box[nb];
		pt = xfrcklinewrap0(f, pt, b);
		if(pt.y == f.r.max.y){
			f.nchars -= xfrstrlen(f, nb);
			xfrdelbox(f, nb, f.nbox-1);
			break;
		}
		if(b.nrune > 0){
			n = xfrcanfit(f, pt, b);
			if(n == 0)
				berror("draw: xfrcanfit==0");
			if(n != b.nrune){
				xfrsplitbox(f, nb, n);
				b = f.box[nb];
			}
			pt.x += b.wid;
		}else{
			if(b.bc == '\n') {
				pt.x = f.r.min.x;
				pt.y += f.font.height;
			}
			else
				pt.x += xfrnewwid(f, pt, b);
		}
	}
	return pt;
}

xfrstrlen(f : ref Frame, nb : int) : int
{
	n, nrune : int;

	for(n=0; nb<f.nbox; nb++) {
		nrune = f.box[nb].nrune;
		if(nrune < 0)
			nrune = 1;
		n += nrune;
	}
	return n;
}

frinit(f : ref Frame, r : Rect, ft : ref Font, b : ref Image, cols : array of ref Draw->Image)
{
	f.font = ft;
	f.scroll = 0;
	f.maxtab = 8*charwidth(ft, '0');
	f.nbox = 0;
	f.nalloc = 0;
	f.nchars = 0;
	f.nlines = 0;
	f.p0 = 0;
	f.p1 = 0;
	f.box = nil;
	f.lastlinefull = 0;
	if(cols != nil)
		for(i := 0; i < NCOL; i++)
			f.cols[i] = cols[i];
	for (i = 0; i < len noglyphs; i++) {
		if (charwidth(ft, noglyphs[i]) != 0) {
			f.noglyph = noglyphs[i];
			break;
		}
	}
	frsetrects(f, r, b);
	if (f.tick==nil && f.cols[BACK] != nil)
		frinittick(f);
}

frinittick(f : ref Frame)
{
	ft : ref Font;

	ft = f.font;
	f.tick = nil;
	f.tick = graph->balloc(((0, 0), (FRTICKW, ft.height)), (gui->mainwin).chans, Draw->White);
	if(f.tick == nil)
		return;
	f.tickback = graph->balloc(f.tick.r, (gui->mainwin).chans, Draw->White);
	if(f.tickback == nil){
		f.tick = nil;
		return;
	}
	# background color
	draw(f.tick, f.tick.r, f.cols[BACK], nil, (0, 0));
	# vertical line
	draw(f.tick, ((FRTICKW/2, 0), (FRTICKW/2+1, ft.height)), black, nil, (0, 0));
	# box on each end
	# draw(f->tick, Rect(0, 0, FRTICKW, FRTICKW), f->cols[TEXT], nil, ZP);
	# draw(f->tick, Rect(0, ft->height-FRTICKW, FRTICKW, ft->height), f->cols[TEXT], nil, ZP);
}

frsetrects(f : ref Frame, r : Rect, b : ref Image)
{
	f.b = b;
	f.entire = r;
	f.r = r;
	f.r.max.y -= (r.max.y-r.min.y)%f.font.height;
	f.maxlines = (r.max.y-r.min.y)/f.font.height;
}

frclear(f : ref Frame, freeall : int)
{
	if(f.nbox)
		xfrdelbox(f, 0, f.nbox-1);
	for (i := 0; i < f.nalloc; i++)
		f.box[i] = nil;
	if(freeall)
		f.tick = f.tickback = nil;
	f.box = nil;
	f.ticked = 0;
}

DELTA : con 25;
TMPSIZE : con 256;

Plist : adt {
	pt0 : Point;
	pt1 : Point;
};

nalloc : int = 0;
pts : array of Plist;

bxscan(f : ref Frame, rp : string, l : int, ppt : Point) : (Point, Point)
{
	w, c, nb, delta, nl, nr : int;
	sp : int = 0;

	frame.r = f.r;
	frame.b = f.b;
	frame.font = f.font;
	frame.maxtab = f.maxtab;
	frame.nbox = 0;
	frame.nchars = 0;
	for(i := 0; i < NCOL; i++)
		frame.cols[i] = f.cols[i];
	frame.noglyph = f.noglyph;
	delta = DELTA;
	nl = 0;
	for(nb=0; sp<l && nl <= f.maxlines; nb++){
		if(nb == frame.nalloc){
			xfrgrowbox(frame, delta);
			if(delta < 10000)
				delta *= 2;
		}
		b := frame.box[nb];
		c = rp[sp];
		if(c=='\t' || c=='\n'){
			b.bc = c;
			b.wid = 5000;
			if(c == '\n')
				b.minwid = 0;
			else
				b.minwid = charwidth(frame.font, ' ');
			b.nrune = -1;
			if(c=='\n')
				nl++;
			frame.nchars++;
			sp++;
		}else{
			nr = 0;
			w = 0;
			ssp := sp;
			nul := 0;
			while(sp < l){
				c = rp[sp];
				if(c=='\t' || c=='\n')
					break;
				if(nr+1 >= TMPSIZE)
					break;
				if ((cw := charwidth(frame.font, c)) == 0) {	# used to be only for c == 0
					c = frame.noglyph;
					cw = charwidth(frame.font, c);
					nul = 1;
				}
				w += cw;
				sp++;
				nr++;
			}
			b = frame.box[nb];
			b.ptr = rp[ssp:sp];
			b.wid = w;
			b.nrune = nr;
			frame.nchars += nr;
			if (nul) {
				for (i = 0; i < nr; i++)
					if (charwidth(frame.font, b.ptr[i]) == 0)
						b.ptr[i] = frame.noglyph;
			}
		}
		frame.nbox++;
	}
	ppt = xfrcklinewrap0(f, ppt, frame.box[0]);
	return (xfrdraw(frame, ppt), ppt);
}

chopframe(f : ref Frame, pt : Point, p : int, bn : int)
{
	nb, nrune : int;

	for(nb = bn; ; nb++){
		if(nb >= f.nbox)
			berror("endofframe");
		b := f.box[nb];
		pt = xfrcklinewrap(f, pt, b);
		if(pt.y >= f.r.max.y)
			break;
		nrune = b.nrune;
		if(nrune < 0)
			nrune = 1;
		p += nrune;
		pt = xfradvance(f, pt, b);
	}
	f.nchars = p;
	f.nlines = f.maxlines;
	if (nb < f.nbox)				# BUG
		xfrdelbox(f, nb, f.nbox-1);
}

frinsert(f : ref Frame, rp : string, l : int, p0 : int)
{
	pt0, pt1, ppt0, ppt1, pt : Point;
	s, n, n0, nn0, y : int;
	r : Rect;
	npts : int;
	col : ref Image;

	if(p0 > f.nchars || l == 0 || f.b == nil)
		return;
	n0 = xfrfindbox(f, 0, 0, p0);
	cn0 := p0;
	nn0 = n0;
	pt0 = xfrptofcharnb(f, p0, n0);
	ppt0 = pt0;
	(pt1, ppt0) = bxscan(f, rp, l, ppt0);
	ppt1 = pt1;
	if(n0 < f.nbox){
		b := f.box[n0];
		pt0 = xfrcklinewrap(f, pt0, b);	# for frdrawsel()
		ppt1 = xfrcklinewrap0(f, ppt1, b);
	}
	f.modified = 1;
	#
	# ppt0 and ppt1 are start and end of insertion as they will appear when
	# insertion is complete. pt0 is current location of insertion position
	# (p0); pt1 is terminal point (without line wrap) of insertion.
	#
	if(f.p0 == f.p1)
		frtick(f, frptofchar(f, f.p0), 0);
	
	#
	# Find point where old and new x's line up
	# Invariants:
	#	pt0 is where the next box (b, n0) is now
	#	pt1 is where it will be after then insertion
	# If pt1 goes off the rectangle, we can toss everything from there on
	#

	for(npts=0; pt1.x!= pt0.x && pt1.y!=f.r.max.y && n0<f.nbox; npts++){
		b := f.box[n0];
		pt0 = xfrcklinewrap(f, pt0, b);
		pt1 = xfrcklinewrap0(f, pt1, b);
		if(b.nrune > 0){
			n = xfrcanfit(f, pt1, b);
			if(n == 0)
				berror("xfrcanfit==0");
			if(n != b.nrune){
				xfrsplitbox(f, n0, n);
				b = f.box[n0];
			}
		}
		if(npts == nalloc){
			opts := pts;
			pts = array[npts+DELTA] of Plist;
			pts[0:] = opts[0:npts];
			for (k := 0; k < DELTA; k++)
				pts[k+npts].pt0 = pts[k+npts].pt1 = (0, 0);
			opts = nil;
			nalloc += DELTA;
			b = f.box[n0];
		}
		pts[npts].pt0 = pt0;
		pts[npts].pt1 = pt1;
		# has a text box overflowed off the frame?
		if(pt1.y == f.r.max.y)
			break;
		pt0 = xfradvance(f, pt0, b);
		pt1.x += xfrnewwid(f, pt1, b);
		n0++;
		nrune := b.nrune;
		if(nrune < 0)
			nrune = 1;
		cn0 += nrune;
	}
	if(pt1.y > f.r.max.y)
		berror("frinsert pt1 too far");
	if(pt1.y==f.r.max.y && n0<f.nbox){
		f.nchars -= xfrstrlen(f, n0);
		xfrdelbox(f, n0, f.nbox-1);
	}
	if(n0 == f.nbox)
		f.nlines = (pt1.y-f.r.min.y)/f.font.height+(pt1.x>f.r.min.x);
	else if(pt1.y!=pt0.y){
		q0, q1 : int;

		y = f.r.max.y;
		q0 = pt0.y+f.font.height;
		q1 = pt1.y+f.font.height;
		f.nlines += (q1-q0)/f.font.height;
		if(f.nlines > f.maxlines)
			chopframe(f, ppt1, p0, nn0);
		if(pt1.y < y){
			r = f.r;
			r.min.y = q1;
			r.max.y = y;
			if(q1 < y)
				draw(f.b, r, f.b, nil, (f.r.min.x, q0));
			r.min = pt1;
			r.max.x = pt1.x+(f.r.max.x-pt0.x);
			r.max.y = q1;
			draw(f.b, r, f.b, nil, pt0);
		}
	}
	#
	# Move the old stuff down to make room.  The loop will move the stuff
	# between the insertion and the point where the x's lined up.
	# The draws above moved everything down after the point they lined up.
	#
	y = 0;
	if(pt1.y == f.r.max.y)
		y = pt1.y;
	for(j := n0-1; --npts >= 0; --j){
		pt = pts[npts].pt1;
		b := f.box[j];
		if(b.nrune > 0){
			r.min = pt;
			r.max = r.min;
			r.max.x += b.wid;
			r.max.y += f.font.height;
			draw(f.b, r, f.b, nil, pts[npts].pt0);
			if(pt.y < y){	# clear bit hanging off right
				r.min = pt;
				r.max = pt;
				r.min.x += b.wid;
				r.max.x = f.r.max.x;
				r.max.y += f.font.height;
				if(f.p0<=cn0 && cn0<f.p1)	# b+1 is inside selection
					col = f.cols[HIGH];
				else
					col = f.cols[BACK];
				draw(f.b, r, col, nil, r.min);
			}
			y = pt.y;
			cn0 -= b.nrune;
		}else{
			r.min = pt;
			r.max = pt;
			r.max.x += b.wid;
			r.max.y += f.font.height;
			if(r.max.x >= f.r.max.x)
				r.max.x = f.r.max.x;
			cn0--;
			if(f.p0<=cn0 && cn0<f.p1)	# b is inside selection
				col = f.cols[HIGH];
			else
				col = f.cols[BACK];
			draw(f.b, r, col, nil, r.min);
			y = 0;
			if(pt.x == f.r.min.x)
				y = pt.y;
		}
	}
	# insertion can extend the selection, so the condition here is different 
	if(f.p0<p0 && p0<=f.p1)
		col = f.cols[HIGH];
	else
		col = f.cols[BACK];
	frselectpaint(f, ppt0, ppt1, col);
	xfrredraw(frame, ppt0);
	xfraddbox(f, nn0, frame.nbox);
	for(n=0; n<frame.nbox; n++)
		*f.box[nn0+n] = *frame.box[n];
	if(nn0>0 && f.box[nn0-1].nrune>=0 && ppt0.x-f.box[nn0-1].wid>=f.r.min.x){
		--nn0;
		ppt0.x -= f.box[nn0].wid;
	}
	n0 += frame.nbox;
	s = n0;
	if(n0 < f.nbox-1)
		s++;
	xfrclean(f, ppt0, nn0, s);
	f.nchars += frame.nchars;
	if(f.p0 >= p0)
		f.p0 += frame.nchars;
	if(f.p0 > f.nchars)
		f.p0 = f.nchars;
	if(f.p1 >= p0)
		f.p1 += frame.nchars;
	if(f.p1 > f.nchars)
		f.p1 = f.nchars;
	if(f.p0 == f.p1)
		frtick(f, frptofchar(f, f.p0), 1);
}

xfrptofcharptb(f : ref Frame, p : int, pt : Point, bn : int) : Point
{
	s : int;
	l : int;
	r : int;

	for( ; bn < f.nbox; bn++){
		b := f.box[bn];
		pt = xfrcklinewrap(f, pt, b);
		l = b.nrune;
		if(l < 0)
			l = 1;
		if(p < l){
			if(b.nrune > 0)
				for(s = 0; p > 0; s++){
					r = b.ptr[s];
					pt.x += charwidth(f.font, r);
					if(r==0 || pt.x>f.r.max.x)
						berror("frptofchar");
					p--;
				}
			break;
		}
		p -= l;
		pt = xfradvance(f, pt, b);
	}
	return pt;
}

frptofchar(f : ref Frame, p : int) : Point
{
	return xfrptofcharptb(f, p, f.r.min, 0);
}

xfrptofcharnb(f : ref Frame, p : int, nb : int) : Point	# doesn't do final xfradvance to next line
{
	pt : Point;
	nbox : int;

	nbox = f.nbox;
	f.nbox = nb;
	pt = xfrptofcharptb(f, p, f.r.min, 0);
	f.nbox = nbox;
	return pt;
}

xfrgrid(f : ref Frame, p: Point) : Point
{
	p.y -= f.r.min.y;
	p.y -= p.y%f.font.height;
	p.y += f.r.min.y;
	if(p.x > f.r.max.x)
		p.x = f.r.max.x;
	return p;
}

frcharofpt(f : ref Frame, pt : Point) : int
{
	qt : Point;
	bn, nrune : int;
	s : int;
	p : int;
	r : int;

	pt = xfrgrid(f, pt);
	qt = f.r.min;

	bn=0;
	for(p=0; bn<f.nbox && qt.y<pt.y; bn++){
		b := f.box[bn];
		qt = xfrcklinewrap(f, qt, b);
		if(qt.y >= pt.y)
			break;
		qt = xfradvance(f, qt, b);
		nrune = b.nrune;
		if(nrune < 0)
			nrune = 1;
		p += nrune;
	}

	for(; bn<f.nbox && qt.x<=pt.x; bn++){
		b := f.box[bn];
		qt = xfrcklinewrap(f, qt, b);
		if(qt.y > pt.y)
			break;
		if(qt.x+b.wid > pt.x){
			if(b.nrune < 0)
				qt = xfradvance(f, qt, b);
			else{
				s = 0;
				for(;;){
					r = b.ptr[s++];
					qt.x += charwidth(f.font, r);
					if(qt.x > pt.x)
						break;
					p++;
				}
			}
		}else{
			nrune = b.nrune;
			if(nrune < 0)
				nrune = 1;
			p += nrune;
			qt = xfradvance(f, qt, b);
		}
	}
	return p;
}

region(a, b : int) : int
{
	if(a < b)
		return -1;
	if(a == b)
		return 0;
	return 1;
}

frselect(f : ref Frame, m : ref Pointer)	# when called, button 1 is down
{
	p0, p1, q : int;
	mp, pt0, pt1, qt : Point;
	b, scrled, reg : int;

	mp = m.xy;
	b = m.buttons;

	f.modified = 0;
	frdrawsel(f, frptofchar(f, f.p0), f.p0, f.p1, 0);
	p0 = p1 = frcharofpt(f, mp);
	f.p0 = p0;
	f.p1 = p1;
	pt0 = frptofchar(f, p0);
	pt1 = frptofchar(f, p1);
	frdrawsel(f, pt0, p0, p1, 1);
	do{
		scrled = 0;
		if(f.scroll){
			if(m.xy.y < f.r.min.y){
				textm->framescroll(f, -(f.r.min.y-m.xy.y)/f.font.height-1);
				p0 = f.p1;
				p1 = f.p0;
				scrled = 1;
			}else if(m.xy.y > f.r.max.y){
				textm->framescroll(f, (m.xy.y-f.r.max.y)/f.font.height+1);
				p0 = f.p0;
				p1 = f.p1;
				scrled = 1;
			}
			if(scrled){
				pt0 = frptofchar(f, p0);
				pt1 = frptofchar(f, p1);
				reg = region(p1, p0);
			}
		}
		q = frcharofpt(f, m.xy);
		if(p1 != q){
			if(reg != region(q, p0)){	# crossed starting point; reset
				if(reg > 0)
					frdrawsel(f, pt0, p0, p1, 0);
				else if(reg < 0)
					frdrawsel(f, pt1, p1, p0, 0);
				p1 = p0;
				pt1 = pt0;
				reg = region(q, p0);
				if(reg == 0)
					frdrawsel(f, pt0, p0, p1, 1);
			}
			qt = frptofchar(f, q);
			if(reg > 0){
				if(q > p1)
					frdrawsel(f, pt1, p1, q, 1);
				else if(q < p1)
					frdrawsel(f, qt, q, p1, 0);
			}else if(reg < 0){
				if(q > p1)
					frdrawsel(f, pt1, p1, q, 0);
				else
					frdrawsel(f, qt, q, p1, 1);
			}
			p1 = q;
			pt1 = qt;
		}
		f.modified = 0;
		if(p0 < p1) {
			f.p0 = p0;
			f.p1 = p1;
		}
		else {
			f.p0 = p1;
			f.p1 = p0;
		}
		if(scrled)
			textm->framescroll(f, 0);
		graph->bflush();
		if(!scrled)
			acme->frgetmouse();
	}while(m.buttons == b);
}

frselectpaint(f : ref Frame, p0 : Point, p1 : Point, col : ref Image)
{
	n : int;
	q0, q1 : Point;

	q0 = p0;
	q1 = p1;
	q0.y += f.font.height;
	q1.y += f.font.height;
	n = (p1.y-p0.y)/f.font.height;
	if(f.b == nil)
		berror("frselectpaint b==0");
	if(p0.y == f.r.max.y)
		return;
	if(n == 0)
		draw(f.b, (p0, q1), col, nil, (0, 0));
	else{
		if(p0.x >= f.r.max.x)
			p0.x = f.r.max.x-1;
		draw(f.b, ((p0.x, p0.y), (f.r.max.x, q0.y)), col, nil, (0, 0));
		if(n > 1)
			draw(f.b, ((f.r.min.x, q0.y), (f.r.max.x, p1.y)),
				col, nil, (0, 0));
		draw(f.b, ((f.r.min.x, p1.y), (q1.x, q1.y)),
			col, nil, (0, 0));
	}
}

xfrcanfit(f : ref Frame, pt : Point, b : ref Frbox) : int
{
	left, nr : int;
	p : int;
	r : int;

	left = f.r.max.x-pt.x;
	if(b.nrune < 0)
		return b.minwid <= left;
	if(left >= b.wid)
		return b.nrune;
	nr = 0;
	for(p = 0; p < len b.ptr; p++){
		r = b.ptr[p];
		left -= charwidth(f.font, r);
		if(left < 0)
			return nr;
		nr++;
	}
	berror("xfrcanfit can't");
	return 0;
}

xfrcklinewrap(f : ref Frame, p : Point, b : ref Frbox) : Point
{
	wid : int;

	if(b.nrune < 0)
		wid = b.minwid;
	else
		wid = b.wid;

	if(wid > f.r.max.x-p.x){
		p.x = f.r.min.x;
		p.y += f.font.height;
	}
	return p;
}

xfrcklinewrap0(f : ref Frame, p : Point, b : ref Frbox) : Point
{
	if(xfrcanfit(f, p, b) == 0){
		p.x = f.r.min.x;
		p.y += f.font.height;
	}
	return p;
}

xfrcklinewrap1(f : ref Frame, p : Point, wid : int) : Point
{
	if(wid > f.r.max.x-p.x){
		p.x = f.r.min.x;
		p.y += f.font.height;
	}
	return p;
}

xfradvance(f : ref Frame, p : Point, b : ref Frbox) : Point
{
	if(b.nrune<0 && b.bc=='\n'){
		p.x = f.r.min.x;
		p.y += f.font.height;
	}else
		p.x += b.wid;
	return p;
}

xfrnewwid(f : ref Frame, pt : Point, b : ref Frbox) : int
{
	b.wid = xfrnewwid0(f, pt, b);
	return b.wid;
}

xfrnewwid0(f : ref Frame, pt : Point, b : ref Frbox) : int
{
	c, x : int;

	c = f.r.max.x;
	x = pt.x;
	if(b.nrune >= 0 || b.bc != '\t')
		return b.wid;
	if(x+b.minwid > c)
		x = pt.x = f.r.min.x;
	x += f.maxtab;
	x -= (x-f.r.min.x)%f.maxtab;
	if(x-pt.x<b.minwid || x>c)
		x = pt.x+b.minwid;
	return x-pt.x;
}

xfrclean(f : ref Frame, pt : Point, n0 : int, n1 : int)	# look for mergeable boxes
{
	nb, c : int;

	c = f.r.max.x;
	for(nb=n0; nb<n1-1; nb++){
		b0 := f.box[nb];
		b1 := f.box[nb+1];
		pt = xfrcklinewrap(f, pt, b0);
		while(b0.nrune>=0 && nb<n1-1 && b1.nrune>=0 && pt.x+b0.wid+b1.wid<c){
			xfrmergebox(f, nb);
			n1--;
			b0 = f.box[nb];
			b1 = f.box[nb+1];
		}
		pt = xfradvance(f, pt, f.box[nb]);
	}
	for(; nb<f.nbox; nb++){
		b := f.box[nb];
		pt = xfrcklinewrap(f, pt, b);
		pt = xfradvance(f, pt, f.box[nb]);
	}
	f.lastlinefull = 0;
	if(pt.y >= f.r.max.y)
		f.lastlinefull = 1;
}