# # 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 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); }