#
# Text drawing for owptext.b
# Draws a lot more than needed, but it's more simple to debug this way.
# A frame is created by a call to Frame.new()
# Nothing can be done to it before calling init() to fill it with text.
# ss, se, mark, and showsel may be adjusted between new() and init()
# In general, init() is called to reposition or to refill the frame.
# sel() is used to adjust ss and se (selection).
# The frame keeps blks as given to init, and keeps offsets into them.
# Frame.ins/.del are used after calling ins/del on blks to update the frame idea of offsets
# and to update the frame.
# The image, Frame.i, may be given as nil to init(), and may be set to nil at any time between
# calls to prevent the image from drawing.
# A new image (rectangle) may be set for the frame by calling Frame.resize().
# redraw() forces a complete redraw of the frame.
# scroll() scrolls up/down nlines.
# and pt2pos/pos2pt convert between points and positions in blks.
# Other functions are auxiliary and meant for the implementation.
# We could do something about tabs. Like for example: do not wrap long lines,
# and group blocks of text so that a single tab manages to tab all the text properly.
# Also, we do more work than needed. Might draw a lot less.
# This is not as highly coupled to the rest of o/live as it may seem due to init().
# The only module actually needed is Tblks. We use the rest to access the window image
# and colors, mostly.
# A frame is a list of Tbox, mostly. fmt() is the main routine in charge of text formatting.
# The text from Tblks drawn is cached in the boxes, but for debug checks only.
implement Tframe;
include "mods.m";
mods, win, tree: import dat;
NCOL, HIGH, BACK, TEXT: import gui;
prefix: import str;
include "tblks.m";
tblks: Tblks;
fixpos, fixposins, fixposdel, dtxt, strstr, Maxline, Blks, strchr, Str: import tblks;
include "tframe.m";
debug := 0;
init(d: Livedat, b: Tblks, dbg: int)
{
dat = d;
initmods();
debug = dbg;
tblks = b;
}
Frame.panic(fr: self ref Frame, s: string)
{
fr.dump();
panic(s);
}
charwidth(f : ref Font, c : int) : int
{
s : string = "z";
s[0] = c;
return f.width(s);
}
nullframe: Frame;
Frame.new(r: Rect, i: ref Image, f: ref Font, cols: array of ref Image, beof: int): ref Frame
{
if (debug)
fprint(stderr, "new frame [%d %d %d %d]\n", r.min.x, r.min.y, r.max.x, r.max.y);
fr := ref nullframe;
fr.cols = array[NCOL] of ref Image;
fr.cols[0:] = cols[0:NCOL];
fr.font = f;
fr.tabsz = 4;
fr.showsel = 1;
fr.showbeof = beof;
fr.resize(r, i);
return fr;
}
Frame.resize(fr: self ref Frame, r: Rect, i: ref Image)
{
if (debug)
if (i != nil)
fprint(stderr, "resize frame [%d %d %d %d] img\n", r.min.x, r.min.y, r.max.x, r.max.y);
else
fprint(stderr, "resize frame [%d %d %d %d] noimg\n", r.min.x, r.min.y, r.max.x, r.max.y);
fr.r = r;
if (fr.showbeof){
fr.r.min.y += 1;
fr.r.max.y -= 1;
}
fr.i = i;
fr.sz.x = fr.r.dx()/fr.font.width("M");
fr.sz.y = fr.r.dy()/fr.font.height;
fr.boxes = array[0] of ref Tbox;
fr.nboxes = fr.nr = 0;
fr.tabwid = fr.tabsz * fr.font.width("O");
fr.spwid = fr.font.width(" ");
x := fr.r.dx() / 3;
if (x > Beofwid)
x = Beofwid;
fr.sbeof = fr.r.min.x + (fr.r.dx() - x) / 2;
fr.ebeof = fr.sbeof + x;
fr.mktick();
dpy := win.display;
fr.lni = dpy.newimage(Rect((0,0), (fr.r.dx(), fr.font.height)), win.image.chans, 0, Draw->White);
fr.lni.draw(fr.lni.r, fr.cols[BACK], nil, (0,0));
if (fr.blks != nil) # nil when called by Frame.new()
fr.init(fr.blks, fr.pos);
}
# This is being using to change to position and not just blks.
# BUG: throws it all away. Could reuse many of the boxes/images it has
# when new content intersects old one.
Frame.init(fr: self ref Frame, blks: ref Blks, pos: int)
{
if (debug)
fprint(stderr, "frame init pos %d\n", pos);
fr.blks = blks;
l := blks.blen();
fr.pos = fixpos(pos, l);
fr.ss = fixpos(fr.ss, l);
fr.se = fixpos(fr.se, l);
fr.mark = fixpos(fr.mark, l);
fr.boxes = array[0] of ref Tbox;
fr.nboxes = fr.nr = 0;
fr.fill();
fr.chk();
}
Frame.mktick(fr : self ref Frame)
{
dpy := win.display;
t := fr.tick = dpy.newimage(Rect((0, 0), (Tickwid, fr.font.height)), win.image.chans, 0, Draw->White);
t.draw(t.r, fr.cols[BACK], nil, (0, 0));
t.draw(Rect((Tickwid/2, 0), (Tickwid/2+1, fr.font.height)), fr.cols[TEXT], nil, (0, 0));
t.draw(Rect((Tickwid/2, 0), (Tickwid, Tickwid)), fr.cols[TEXT], nil, (0,0));
t.draw(Rect((Tickwid/2, fr.font.height-Tickwid), (Tickwid, fr.font.height)), fr.cols[TEXT], nil, (0,0));
}
getboxes(blks: ref Blks, pos: int, l: int, f: ref Font, maxwid: int): (array of ref Tbox, int)
{
boxes := array[16] of ref Tbox;
nboxes := 0;
b: ref Tbox;
b = nil;
wid := 0;
nr := 0;
for (i := pos; i < pos + l && wid < maxwid; i++){
c := blks.getc(i);
if (c == -1)
break;
nr++;
if (nboxes == len boxes){
bb := array[len boxes+16] of ref Tbox;
bb[0:] = boxes;
boxes = bb;
}
case c {
'\t' =>
b = ref Tbox('\t', i, 1, (0,0), 0, 1, "\t");
boxes[nboxes++] = b;
'\n' =>
b = ref Tbox('\n', i, 1, (0,0), 0, 1, "\n");
boxes[nboxes++] = b;
* =>
cwid := charwidth(f, c);
if (b == nil || b.sep){
b = ref Tbox(0, i, 1, (0,0), cwid, 1, "");
b.txt[0] = c;
boxes[nboxes++] = b;
} else {
b.txt[b.nr] = c;
b.nr++;
b.wid += cwid;
}
wid += cwid;
}
}
return (boxes[0:nboxes], nr);
}
# pos is not before fr.pos.
# returns line, field, rune number in box for pos (perhaps one past the last valid ones).
Frame.seek(fr: self ref Frame, pos: int): (int, int)
{
for(i := 0; i < fr.nboxes; i++){
b := fr.boxes[i];
if (pos < b.pos + b.nr){
ri := pos - b.pos;
if (b.sep && ri > 0)
fr.panic("seek bug");
return (i, ri);
}
}
return (i, 0);
}
Frame.findnl(fr: self ref Frame, bi: int): int
{
for (i := bi; i < fr.nboxes; i++)
if (fr.boxes[i].sep == '\n')
return i;
return -1;
}
Frame.pt2pos(fr: self ref Frame, pt: Point): int
{
if (pt.x < fr.r.min.x)
pt.x = fr.r.min.x;
if (pt.x > fr.r.max.x)
pt.x = fr.r.max.x;
if (pt.y < fr.r.min.y)
return 0;
if (pt.y > fr.r.max.y)
return fr.pos+fr.nr;
for (i := 0; i < fr.nboxes; i++){
b := fr.boxes[i];
r := Rect(b.pt, (b.pt.x+b.wid, b.pt.y+fr.font.height));
if (pt.in(r)){
if (b.sep)
return b.pos;
else {
for (i = 0; i < b.nr; i++){
wid := charwidth(fr.font, fr.blks.getc(b.pos+i));
r.max.x = r.min.x + wid;
if (pt.in(r))
return b.pos+i;
r.min.x = r.max.x;
}
return b.pos+b.nr -1; # aprox. last rune in box
}
}
}
return fr.pos+fr.nr; # aprox. by eof
}
Frame.pos2pt(fr: self ref Frame, pos: int): Point
{
if (pos < fr.pos)
pos = fr.pos;
if (pos > fr.pos + fr.nr)
pos = fr.pos + fr.nr;
pt := Point(fr.r.min);
for (i := 0; i < fr.nboxes; i++){
b := fr.boxes[i];
if (pos < b.pos + b.nr){
pt = b.pt;
if (b.sep)
return pt;
for (i = 0; b.pos + i < pos; i++)
pt.x += charwidth(fr.font, fr.blks.getc(b.pos+i));
return pt;
}
}
if (i == fr.nboxes){ # pos was >= fr.pos + fr.nr . place at end.
if (fr.nboxes == 0)
return fr.r.min;
b := fr.boxes[fr.nboxes-1];
if (b.pt.x + b.wid < fr.r.max.x) # at end of last line.
pt = Point(b.pt.x+b.wid, b.pt.y);
else if (b.pt.y + fr.font.height < fr.r.max.y) # would be at next line
pt = Point(fr.r.min.x, b.pt.y + fr.font.height);
else
pt = Point(b.pt.x+b.wid, b.pt.y); # does not fit. step back.
}
return pt;
}
# adjusts frame points. does nothing else.
Frame.move(fr: self ref Frame, at: Point)
{
if (debug)
fprint(stderr, "frame move [%d %d]\n", at.x, at.y);
dx := at.x - fr.r.min.x;
dy := at.y - fr.r.min.y;
delta := Point(dx, dy);
fr.r = fr.r.addpt(delta);
for (i := 0; i < fr.nboxes; i++){
fr.boxes[i].pt = fr.boxes[i].pt.add(delta);
fr.boxes[i].dirty = 1;
}
}
# length in runes of boxes
blen(boxes: array of ref Tbox): int
{
l := 0;
for (i := 0; i < len boxes; i++)
if (boxes[i].sep)
l++;
else
l += boxes[i].nr;
return l;
}
# see note in Frame.ins
# renumber is true when chars are new and do not come from box split or whatever.
Frame.addboxes(fr: self ref Frame, bi: int, boxes: array of ref Tbox, renumber: int)
{
nlen := len boxes + fr.nboxes;
if (nlen > len fr.boxes){
nboxes := array[nlen] of ref Tbox;
if (bi > 0)
nboxes[0:] = fr.boxes[0:bi];
if (bi < fr.nboxes)
nboxes[bi + len boxes:] = fr.boxes[bi:fr.nboxes];
nboxes[bi:] = boxes;
fr.boxes = nboxes;
} else {
for (i := fr.nboxes - 1; i >= bi; i--)
fr.boxes[i + len boxes] = fr.boxes[i];
fr.boxes[bi:] = boxes;
}
fr.nboxes += len boxes;
if (renumber){
nr := blen(boxes);
fr.nr += nr;
for (i := bi + len boxes; i < fr.nboxes; i++)
fr.boxes[i].pos += nr;
}
}
# see note in Frame.ins
# renumber is true when chars are new and do not come from box coalescing or whatever.
Frame.delboxes(fr: self ref Frame, bi: int, nb: int, renumber: int)
{
nr := blen(fr.boxes[bi:bi+nb]);
for (i := bi; i+nb< fr.nboxes; i++)
fr.boxes[i] = fr.boxes[i+nb];
if (i < len fr.boxes)
fr.boxes[i] = nil; # poison
fr.nboxes -= nb;
if (renumber){
fr.nr -= nr;
for (i = bi; i < fr.nboxes; i++)
fr.boxes[i].pos -= nr;
}
}
# splits boxes[bi] leaving rune at ri in the second box.
# widths are NOT computed (because during ins/del in frame postions
# would be wrong.
Frame.splitbox(fr: self ref Frame, bi: int, ri: int)
{
b := fr.boxes[bi];
if (debug>1)
fprint(stderr, "\t splitbox %d %d {%s}\n", bi, ri, b.text());
if (ri == 0 || b.sep || ri > b.nr || b.nr != len b.txt)
fr.panic("split bug");
nb := ref Tbox(0, b.pos + ri, b.nr - ri, (0, 0), 0, 0, b.txt[ri:]);
nb.wid = b.wid = 0;
b.nr = ri;
b.txt = b.txt[0:ri];
b.dirty = nb.dirty = 1;
fr.addboxes(bi+1, array[] of {nb}, 0);
}
Frame.fixselins(fr: self ref Frame, pos: int, nr: int)
{
if (pos >= fr.ss && pos <= fr.se){
if (fr.ss == fr.se)
fr.ss = fr.se = pos + nr;
else if (pos+nr > fr.se)
fr.se = pos+nr;
else
fr.se += nr;
} else if (pos < fr.ss){
fr.ss += nr;
fr.se += nr;
}
}
# If text was inserted before/after the frame. we adjust positions and do nothing. Otherwise,
# some text has been inserted in fr.blks, which makes all the offsets void from
# the point of insertion. We build new boxes for all the new text, and insert them in place.
# To avoid adding too many boxes once the frame is full, we add only as many as
# needed to fill an entire frame, considering that the tab and newline width is 0.
# That is conservative, but it's simple and at least we know that offsets are right.
# At that point we can reformat the text boxes in the frame.
# Returns true if further insertion is futile (eof or frame full).
# Also, adjust the selection to so it moves when inserting, but is otherwise
# kept ok despite insertions before/after the current selection.
Frame.ins(fr: self ref Frame, pos: int, nr: int): int
{
if (debug)
fprint(stderr, "frame ins %d %d\n", pos, nr);
fr.mark = fixposins(fr.mark, pos, nr);
fr.fixselins(pos, nr);
if (pos > fr.pos + fr.nr || nr == 0)
return 1;
if (pos < fr.pos){
fr.pos += nr;
for (i := 0; i < len fr.boxes; i++)
fr.boxes[i].pos += nr;
return 0;
}
r :=ins(fr, pos, nr);
fr.sel(fr.ss, fr.se);
return r;
}
# This is the actual insert, but does not fix up ss, se, mark, nor pos.
# Used by both Frame.ins and Frame.fill
ins(fr: ref Frame, pos: int, nr: int): int
{
if (debug)
fprint(stderr, "frame _ins %d %d\n", pos, nr);
(bi, ri) := fr.seek(pos);
maxwid := fr.r.dx() * (fr.r.dy()/fr.font.height);
if (bi > 0)
maxwid -= fr.boxes[bi-1].pt.y / fr.font.height;
(boxes, nbr) := getboxes(fr.blks, pos, nr, fr.font, maxwid);
if (nbr == 0)
return 1;
nboxes := len boxes;
if (ri > 0){
fr.splitbox(bi, ri);
nboxes++;
fr.sizebox(bi);
fr.sizebox(bi+1);
fr.addboxes(bi+1, boxes, 1);
} else
fr.addboxes(bi, boxes, 1);
fr.chk();
if (bi > 0)
bi--; # the first box might recombine with the first new one.
full := fr.fmt(bi);
if (nbr < nr)
full = 1;
if (debug > 1)
fr.dump();
return full;
}
Frame.fill(fr: self ref Frame)
{
# insertion at end extends the frame, does not update offsets.
for(pos := fr.pos + fr.nr; ins(fr, pos, 256) == 0; pos += 256)
;
}
Frame.fixseldel(fr: self ref Frame, pos: int, nr: int)
{
# compute intersection of selection and deleted text,
# and number of runes removed from selection.
ds := pos;
de := pos + nr;
ns := fr.se - fr.ss;
if (de <= fr.ss) # empty isect;
nr = nr;
else if (ds >= fr.se) # empty isect
nr = 0;
else {
if (ds < fr.ss){ # isect == [max(ds, fr.ss), min(de, fr.se)]
ds = fr.ss;
nr = ds - fr.ss;
} else
nr = 0;
if (de > fr.se)
de = fr.se;
ns -= (de - ds);
}
# adjust
fr.ss -= nr;
fr.se = fr.ss + ns;
if (fr.ss > fr.blks.blen()) # pure paranoia
fr.ss = fr.se = fr.blks.blen();
}
# We proceed like we did for ins. Adust offsets if does not affect frame,
# or remove involved boxes and then reformat. However, this time we
# refill the frame after formatting it, because there might be more room for text.
Frame.del(fr: self ref Frame, pos: int, nr: int)
{
if (debug)
fprint(stderr, "frame del %d %d\n", pos, nr);
fr.fixseldel(pos, nr);
fr.mark = fixposdel(fr.mark, pos, nr);
if (pos >= fr.pos + fr.nr || nr == 0)
return;
if (pos + nr< fr.pos){
fr.pos -= nr;
for (i := 0; i < len fr.boxes; i++)
fr.boxes[i].pos -= nr;
return;
}
(bs, rs) := fr.seek(pos);
if (rs > 0){
fr.splitbox(bs, rs);
bs++;
}
(be, re) := fr.seek(pos+nr);
if (re > 0){
fr.splitbox(be, re);
be++; # and include the first part
re = 0;
}
fr.delboxes(bs, be-bs, 1);
fr.chk();
# bs-1 is now right before deletion. bs is right after deletion.
# must redraw/resize them.
if (bs > 0 && fr.nboxes > 0){
fr.boxes[bs-1].dirty = 1;
fr.sizebox(bs-1);
}
if (bs<fr.nboxes){
fr.boxes[bs].dirty = 1;
fr.sizebox(bs);
}
fr.fmt(bs);
fr.fill();
if (debug > 1)
fr.dump();
sel(fr, fr.ss, fr.se);
}
selrange(fr: ref Frame, s, e: int): (int, int)
{
(bs, nil) := fr.seek(s);
(be, nil) := fr.seek(e);
if (be < fr.nboxes)
be++;
if (bs == be && bs == fr.nboxes && bs > 0)
bs--; # last box draws eot tick
return (bs, be);
}
sel(fr: ref Frame, ss: int, se: int)
{
os := fr.ss;
oe := fr.se;
fr.ss = ss;
fr.se = se;
if (fr.i == nil)
return;
bs := be := ns := ne := -1;
if (fr.ss < os && fr.se == oe) # extend back
(bs, be) = selrange(fr, fr.ss, os);
else if (fr.ss == os && fr.se > oe) # extend fwd
(bs, be) = selrange(fr, oe, fr.se);
else {
(bs, be) = selrange(fr, os, oe); # clear old
(ns, ne) = selrange(fr, fr.ss, fr.se); # and draw new.
}
if (debug)
fprint(stderr, "frame sel %d %d ob(%d:%d) nb(%d:%d)\n", ss, se, bs, be, ns, ne);
fr.draw(bs, be, 1);
if (ns >= 0)
for (i := ns; i < ne; i++)
if (i < bs || i >= be)
fr.drawbox(i);
}
# change selection to ss se
Frame.sel(fr: self ref Frame, ss: int, se: int)
{
ss = fixpos(ss, fr.blks.blen());
se = fixpos(se, fr.blks.blen());
if (fr.ss != ss || fr.se != se)
sel(fr, ss, se);
}
# Sets positions for boxes[b0:be], sizing sep boxes and
# splitting/combining text boxes as needed to format the text.
# should the frame fill, remaining boxes are removed and returns true.
Frame.fmt(fr: self ref Frame, bi: int): int
{
if (bi >= fr.nboxes)
return 0;
if (debug)
fprint(stderr, "frame fmt b %d {%s}...\n", bi, fr.boxes[bi].text());
bs := bi;
for(; bi < fr.nboxes; bi++){
b := fr.boxes[bi];
fr.placebox(bi);
if (b.pt.y+fr.font.height > fr.r.max.y){ # frame full, truncate
fr.delboxes(bi, fr.nboxes - bi, 1);
fr.draw(bs, bi, 0);
return 1;
}
if (b.sep)
fr.sizebox(bi);
else {
if (b.pt.x + b.wid > fr.r.max.x){
if (debug>1) fprint(stderr, "\t wrap %s\n", b.text());
nbi := fr.wrapbox(bi);
if (fr.boxes[nbi].pt.y+fr.font.height > fr.r.max.y){
fr.delboxes(nbi, fr.nboxes-nbi, 1);
fr.draw(bs, bi, 0);
return 1;
}
} else if (bi+1 < fr.nboxes && !fr.boxes[bi+1].sep){
# try to combine at least two of them.
if (fr.combinebox(bi))
if (debug>1) fprint(stderr, "\t combine %s\n", b.text());
if (fr.boxes[bi+1].nr == 0)
fr.delboxes(bi+1, 1, 0);
}
}
}
# Adjust y coord for the rest of boxes. But don't do so if
# the first does not change placement: no lines added/removed.
if (bi < fr.nboxes){
pt := fr.boxes[bi].pt;
fr.placebox(bi);
if (pt.eq(fr.boxes[bi].pt)){
fr.draw(bs, fr.nboxes, 0);
return 1;
}
}
for(; bi < fr.nboxes; bi++){
fr.placebox(bi);
if (fr.boxes[bi].pt.y+fr.font.height > fr.r.max.y){
fr.delboxes(bi, fr.nboxes-bi, 1);
fr.draw(bs, bi, 0);
return 1;
}
}
fr.draw(bs, fr.nboxes, 0);
return 0;
}
# determine point for box. It might go off-limits, but the pt
# is set ok according to the previous boxes.
Frame.placebox(fr: self ref Frame, bi: int)
{
b := fr.boxes[bi];
opt := b.pt;
if (bi > 0){
lb := fr.boxes[bi-1];
if (lb.sep != '\n' && lb.pt.x + lb.wid < fr.r.max.x)
b.pt = Point(lb.pt.x+lb.wid, lb.pt.y);
else
b.pt = Point(fr.r.min.x, lb.pt.y+fr.font.height);
} else
b.pt = fr.r.min;
if (!opt.eq(b.pt))
b.dirty = 1;
}
# drain as many runes as feasingle from bi+1 into bi.
Frame.combinebox(fr: self ref Frame, bi: int): int
{
b := fr.boxes[bi];
nb := fr.boxes[bi+1];
# Try to do at once
if (b.pt.x + b.wid + nb.wid <= fr.r.max.x){
b.nr += nb.nr;
b.wid += nb.wid;
b.txt += nb.txt;
nb.nr = 0;
nb.wid = 0;
nb.txt = "";
b.dirty = 1;
return 1;
}
x := b.pt.x + b.wid;
wid := 0;
for(i := 0; i < nb.nr; i++){
cw := charwidth(fr.font, fr.blks.getc(nb.pos+i));
if (x + wid + cw > fr.r.max.x)
break;
wid += cw;
}
if (i > 0){
b.nr += i;
b.txt += nb.txt[0:i];
b.wid += wid;
b.dirty = 1;
nb.pos += i;
nb.nr -= i;
nb.txt = nb.txt[i:];
nb.wid -= wid;
nb.dirty = 1;
return 1;
}
return 0;
}
# Wraps box bi (which has to) either complete or a suffix.
# returns the index for the wrapped box (itself or suffix).
Frame.wrapbox(fr: self ref Frame, bi: int): int
{
b := fr.boxes[bi];
wid := 0;
for (i := 0; i < b.nr; i++){
cw := charwidth(fr.font, fr.blks.getc(b.pos+i));
if (b.pt.x + wid + cw > fr.r.max.x)
break;
wid += cw;
}
if (i == b.nr)
fr.panic("wrap at the end of a box?");
pt := Point(fr.r.min.x, b.pt.y + fr.font.height);
if (i == 0){
b.pt = pt;
b.dirty = 1;
return bi;
}
nb := ref Tbox(0, b.pos + i, b.nr - i, pt, b.wid - wid, 1, b.txt[i:b.nr]);
b.wid = wid;
b.nr = i;
b.dirty = 1;
b.txt = b.txt[0:i];
b.dirty = 1;
nb.dirty = 1;
fr.addboxes(bi+1, array[] of {nb}, 0);
return bi+1;
}
# adjust sizes for sep boxes, which are dynamic.
# recompute width for text boxes.
Frame.sizebox(fr: self ref Frame, bi: int)
{
b := fr.boxes[bi];
owid := b.wid;
case b.sep {
'\t' =>
b.wid = fr.tabwid - (b.pt.x%fr.tabwid);
if (b.wid < fr.spwid)
b.wid = fr.spwid;
if (b.pt.x < fr.r.max.x && b.pt.x + b.wid > fr.r.max.x)
b.wid = fr.r.max.x - b.pt.x;
'\n' =>
b.wid = fr.r.max.x - b.pt.x;
0 =>
b.wid = 0;
for (i := 0; i < b.nr; i++){
c := fr.blks.getc(b.pos+i);
b.wid += charwidth(fr.font, c);
}
}
if (b.wid != owid)
b.dirty = 1;
}
Frame.boxtext(fr: self ref Frame, bi: int): string
{
b := fr.boxes[bi];
s := "";
for (j:= 0; j < b.nr; j++)
s[j] = fr.blks.getc(b.pos+j);
return s;
}
Frame.chk(fr: self ref Frame)
{
nr := blen(fr.boxes[0:fr.nboxes]);
if (nr != fr.nr)
fr.panic("nr check");
for (i := 0; i < fr.nboxes; i++){
b := fr.boxes[i];
s := fr.boxtext(i);
if (s != b.txt)
fr.panic(sprint("box %d check: pos %d nr %d txt [%s] blks [%s]\n",
i, b.pos, b.nr, b.txt, s));
}
if (fr.ss < 0 || fr.se < 0 || fr.mark < 0)
fr.panic(sprint("bad sel/mark %d %d %d\n", fr.ss, fr.se, fr.mark));
l := fr.blks.blen();
if (fr.ss > l || fr.se > l || fr.mark > l)
fprint(stderr, "warning: sel out of range: %d %d %d\n", fr.ss, fr.se, fr.mark);
}
Frame.drawtick(fr: self ref Frame, i: ref Image, pt: Point)
{
if (pt.x > fr.r.min.x) # looks better
pt.x--;
r := Rect(pt, (pt.x+Tickwid, pt.y+fr.font.height));
i.draw(r, fr.tick, nil, (0, 0));
}
drawmark(fr: ref Frame, y: int, mark: int)
{
if (mark){
rr := Rect((fr.r.min.x, y), (fr.sbeof, y+1));
fr.i.draw(rr, fr.cols[BACK], nil, (0,0));
rr = Rect((fr.sbeof, y), (fr.ebeof, y+1));
fr.i.draw(rr, fr.cols[TEXT], nil, (0,0));
rr = Rect((fr.ebeof, y), (fr.r.max.x, y+1));
fr.i.draw(rr, fr.cols[BACK], nil, (0,0));
} else {
rr := Rect((fr.r.min.x, y), (fr.r.max.x, y+1));
fr.i.draw(rr, fr.cols[BACK], nil, (0,0));
}
}
Frame.draw(fr: self ref Frame, bi: int, be: int, force: int)
{
if (fr.i == nil)
return;
if (debug)
fprint(stderr, "frame draw bi %d be %d fz %d\n", bi, be, force);
if (fr.nboxes == 0){
if (fr.showbeof){
drawmark(fr, fr.r.min.y-1, 1);
drawmark(fr, fr.r.min.y+fr.font.height, 1);
if (fr.r.min.y+fr.font.height != fr.r.max.y)
drawmark(fr, fr.r.max.y, 0);
}
fr.i.draw(fr.r, fr.cols[BACK], nil, (0,0));
if (fr.showsel)
fr.drawtick(fr.i, fr.r.min);
} else
for (i := bi; i < be; i++)
if (force || fr.boxes[i].dirty)
fr.drawbox(i);
}
Frame.redraw(fr: self ref Frame)
{
fr.draw(0, fr.nboxes, 1);
}
# The primary drawing function.
# Draws the box mentioned, drawing the selection within the box if it's affected.
# Also, last box in line clears the rest of the line and last in frame clears rest of f.r.
# drawing the tick past it when it's at end of text in frame.
Frame.drawbox(fr: self ref Frame, bi: int)
{
b := fr.boxes[bi];
if (debug>1)
fprint(stderr, "frame drawbox %s\n", b.text());
tickpt := Point(-1, -1);
s := b.pos;
e := b.pos + b.nr;
pt := Point(0,0);
r := Rect((0, 0), (b.wid, fr.font.height));
fr.lni.draw(r, fr.cols[BACK], nil, (0, 0));
# draw selection or determine tick position if box is affected.
if (fr.ss < s && fr.se >= e || fr.ss >= s && fr.ss < e || fr.se >= s && fr.se < e){
if (fr.ss != fr.se){
ss := fr.ss;
if (ss < s)
ss = s;
spt := fr.pos2pt(ss);
ept := Point(spt.x+b.wid, 0);
if (fr.se < e)
ept = fr.pos2pt(fr.se);
ept.y = 0;
if (debug>1)
fprint(stderr, "\tsel: pos %d %d [%d %d] [%d %d]\n", fr.ss, fr.se, spt.x, spt.y, ept.x, ept.y);
fr.lni.draw(Rect((spt.x-b.pt.x, 0), (ept.x-b.pt.x, fr.font.height)), fr.cols[HIGH], nil, (0,0));
} else {
tickpt = fr.pos2pt(fr.ss);
if (debug>1)
fprint(stderr, "\ttick: pos %d pt [%d %d]\n", fr.ss, tickpt.x, tickpt.y);
tickpt.y = 0;
tickpt.x -= b.pt.x;
}
}
if (!b.sep)
fr.lni.text(pt, fr.cols[TEXT], (0,0), fr.font, fr.boxtext(bi));
if (tickpt.x != -1 && fr.showsel)
fr.drawtick(fr.lni, tickpt);
fr.i.draw((b.pt, (b.pt.x+b.wid, b.pt.y+fr.font.height)), fr.lni, nil, (0,0));
b.dirty = 0;
# clear rest of line if last box on line (plain boxes do not consume all the line)
if (bi == fr.nboxes-1 || fr.boxes[bi+1].pt.y != b.pt.y )
if (b.pt.x + b.wid < fr.r.max.x){
col := fr.cols[BACK];
if (b.pos+b.nr > fr.ss && b.pos+b.nr < fr.se)
col = fr.cols[HIGH];
r = Rect((b.pt.x+b.wid, b.pt.y), (fr.r.max.x, b.pt.y+fr.font.height));
fr.i.draw(r, col, nil, (0,0));
}
# clear rest of rect if last box on frame and empty space below.
if (bi == fr.nboxes-1 && b.pt.y + fr.font.height < fr.r.max.y){
r= Rect((fr.r.min.x, b.pt.y+fr.font.height), fr.r.max);
if (fr.showbeof)
r.min.y++; # do not clear the eof mark line (flicker)
fr.i.draw(r, fr.cols[BACK], nil, (0,0));
}
# draw tick if past the last box.
if (bi == fr.nboxes-1 && fr.showsel && fr.ss == fr.se && fr.ss == fr.pos + fr.nr)
fr.drawtick(fr.i, fr.pos2pt(fr.ss));
# draw bof/eof if appropriate
if (fr.showbeof && bi == 0)
drawmark(fr, fr.r.min.y-1, fr.pos == 0);
if (fr.showbeof && bi == fr.nboxes-1){
y := b.pt.y + fr.font.height;
drawmark(fr, y, fr.pos+fr.nr >= fr.blks.blen());
if (y != fr.r.max.y)
drawmark(fr, fr.r.max.y, 0); # clear reserved space.
}
}
# Scrolls down (negative nl) or up (possitive nl) by moving the frame up or down.
# Scroll up/down stops when at the first line/only last line is shown.
# A scroll might move more than requested, because we guess how many
# chars to move (cf. line wrap).
Frame.scroll(fr: self ref Frame, nl: int)
{
if (debug)
fprint(stderr, "frame scroll %d\n", nl);
s := fr.blks.pack();
l := fr.blks.blen();
if (nl == 0 || nl > 0 && fr.pos + fr.nr >= l || nl < 0 && fr.pos == 0)
return;
npos := fr.pos;
if (nl > 0){ # move down (scroll up)
do {
p := s.find(npos, '\n', Maxline);
if (p < 0)
break;
npos = p;
if (s.s[p] == '\n' && p < l) # position just after the \n
npos++;
} while (--nl > 0);
} else { # move up (scroll down)
do {
p := s.findr(npos, '\n', Maxline);
if (p < 0){
npos = 0;
break;
}
npos = p;
if (s.s[p] == '\n' && p > 0) # position right before the \n
npos--;
} while (++nl < 1); # <1 for the \n right before the frame
if (npos > 0 && npos+2 < l) # skip after the \n just found,
npos += 2; # but not when at the start of text.
}
if (npos == fr.pos)
return;
# Easier but too slow.
fr.init(fr.blks, npos);
}
Tbox.text(b: self ref Tbox): string
{
s := "";
if (b.dirty)
s += "d ";
case b.sep {
'\t' =>
s += sprint("%d %d:%d [\\t ]", b.pos, b.pt.x, b.wid);
'\n' =>
s += sprint("%d %d:%d [\\n]", b.pos, b.pt.x, b.wid);
* =>
s += sprint("%d %d:%d [%s]", b.pos, b.pt.x, b.wid, b.txt);
}
return s;
}
Frame.dump(fr: self ref Frame)
{
s := sprint("frame: %d boxes (arry %d) pos %d nr %d sel %d %d\n",
fr.nboxes, len fr.boxes, fr.pos, fr.nr, fr.ss, fr.se);
i := fr.i;
if (fr.i != nil)
s += sprint(" img [%d %d %d %d]", i.r.min.x, i.r.min.y, i.r.max.x, i.r.max.y);
r := fr.r;
s += sprint(" r [%d %d %d %d] sz [%d %d] rsz [%d %d] :\n", r.min.x, r.min.y, r.max.x, r.max.y,
fr.sz.x, fr.sz.y, fr.r.dx(), fr.r.dy());
for (bi := 0; bi < fr.nboxes; bi++){
b := fr.boxes[bi];
s += sprint("\t%d: %s\n", bi, b.text());
}
fprint(stderr, "%s\n\n", s);
}
|