summaryrefslogtreecommitdiff
path: root/appl/charon/layout.b
diff options
context:
space:
mode:
authorCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
committerCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
commit37da2899f40661e3e9631e497da8dc59b971cbd0 (patch)
treecbc6d4680e347d906f5fa7fca73214418741df72 /appl/charon/layout.b
parent54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff)
20060303a
Diffstat (limited to 'appl/charon/layout.b')
-rw-r--r--appl/charon/layout.b4828
1 files changed, 4828 insertions, 0 deletions
diff --git a/appl/charon/layout.b b/appl/charon/layout.b
new file mode 100644
index 00000000..6db8f619
--- /dev/null
+++ b/appl/charon/layout.b
@@ -0,0 +1,4828 @@
+implement Layout;
+
+include "common.m";
+include "keyboard.m";
+
+sys: Sys;
+CU: CharonUtils;
+ ByteSource, MaskedImage, CImage, ImageCache, max, min,
+ White, Black, Grey, DarkGrey, LightGrey, Blue, Navy, Red, Green, DarkRed: import CU;
+
+D: Draw;
+ Point, Rect, Font, Image, Display: import D;
+S: String;
+T: StringIntTab;
+U: Url;
+ Parsedurl: import U;
+I: Img;
+ ImageSource: import I;
+J: Script;
+E: Events;
+ Event: import E;
+G: Gui;
+ Popup: import G;
+B: Build;
+
+# B : Build, declared in layout.m so main program can use it
+ Item, ItemSource,
+ IFbrk, IFbrksp, IFnobrk, IFcleft, IFcright, IFwrap, IFhang,
+ IFrjust, IFcjust, IFsmap, IFindentshift, IFindentmask,
+ IFhangmask,
+ Voffbias,
+ ISPnull, ISPvline, ISPhspace, ISPgeneral,
+ Align, Dimen, Formfield, Option, Form,
+ Table, Tablecol, Tablerow, Tablecell,
+ Anchor, DestAnchor, Map, Area, Kidinfo, Docinfo,
+ Anone, Aleft, Acenter, Aright, Ajustify, Achar, Atop, Amiddle,
+ Abottom, Abaseline,
+ Dnone, Dpixels, Dpercent, Drelative,
+ Ftext, Fpassword, Fcheckbox, Fradio, Fsubmit, Fhidden, Fimage,
+ Freset, Ffile, Fbutton, Fselect, Ftextarea,
+ Background,
+ FntR, FntI, FntB, FntT, NumStyle,
+ Tiny, Small, Normal, Large, Verylarge, NumSize, NumFnt, DefFnt,
+ ULnone, ULunder, ULmid,
+ FRnoresize, FRnoscroll, FRhscroll, FRvscroll,
+ FRhscrollauto, FRvscrollauto
+ : import B;
+
+# font stuff
+Fontinfo : adt {
+ name: string;
+ f: ref Font;
+ spw: int; # width of a space in this font
+};
+
+fonts := array[NumFnt] of {
+ FntR*NumSize+Tiny => Fontinfo("/fonts/charon/plain.tiny.font", nil, 0),
+ FntR*NumSize+Small => ("/fonts/charon/plain.small.font", nil, 0),
+ FntR*NumSize+Normal => ("/fonts/charon/plain.normal.font", nil, 0),
+ FntR*NumSize+Large => ("/fonts/charon/plain.large.font", nil, 0),
+ FntR*NumSize+Verylarge => ("/fonts/charon/plain.vlarge.font", nil, 0),
+
+ FntI*NumSize+Tiny => ("/fonts/charon/italic.tiny.font", nil, 0),
+ FntI*NumSize+Small => ("/fonts/charon/italic.small.font", nil, 0),
+ FntI*NumSize+Normal => ("/fonts/charon/italic.normal.font", nil, 0),
+ FntI*NumSize+Large => ("/fonts/charon/italic.large.font", nil, 0),
+ FntI*NumSize+Verylarge => ("/fonts/charon/italic.vlarge.font", nil, 0),
+
+ FntB*NumSize+Tiny => ("/fonts/charon/bold.tiny.font", nil, 0),
+ FntB*NumSize+Small => ("/fonts/charon/bold.small.font", nil, 0),
+ FntB*NumSize+Normal => ("/fonts/charon/bold.normal.font", nil, 0),
+ FntB*NumSize+Large => ("/fonts/charon/bold.large.font", nil, 0),
+ FntB*NumSize+Verylarge => ("/fonts/charon/bold.vlarge.font", nil, 0),
+
+ FntT*NumSize+Tiny => ("/fonts/charon/cw.tiny.font", nil, 0),
+ FntT*NumSize+Small => ("/fonts/charon/cw.small.font", nil, 0),
+ FntT*NumSize+Normal => ("/fonts/charon/cw.normal.font", nil, 0),
+ FntT*NumSize+Large => ("/fonts/charon/cw.large.font", nil, 0),
+ FntT*NumSize+Verylarge => ("/fonts/charon/cw.vlarge.font", nil, 0)
+};
+
+# Seems better to use a slightly smaller font in Controls, to match other browsers
+CtlFnt: con (FntR*NumSize+Small);
+
+# color stuff. have hash table mapping RGB values to D->Image for that color
+Colornode : adt {
+ rgb: int;
+ im: ref Image;
+ next: ref Colornode;
+};
+
+# Source of info for page (html, image, etc.)
+Source: adt {
+ bs: ref ByteSource;
+ redirects: int;
+ pick {
+ Srequired or
+ Shtml =>
+ itsrc: ref ItemSource;
+ Simage =>
+ ci: ref CImage;
+ itl: list of ref Item;
+ imsrc: ref ImageSource;
+ }
+};
+
+Sources: adt {
+ main: ref Source;
+ reqd: ref Source;
+ srcs: list of ref Source;
+
+ new: fn(m : ref Source) : ref Sources;
+ add: fn(srcs: self ref Sources, s: ref Source, required: int);
+ done: fn(srcs: self ref Sources, s: ref Source);
+ waitsrc: fn(srcs : self ref Sources) : ref Source;
+};
+
+NCOLHASH : con 19; # 19 checked for standard colors: only 1 collision
+colorhashtab := array[NCOLHASH] of ref Colornode;
+
+# No line break should happen between adjacent characters if
+# they are 'wordchars' : set in this array, or outside the array range.
+# We include certain punctuation characters that are not traditionally
+# regarded as 'word' characters.
+wordchar := array[16rA0] of {
+ '!' => byte 1,
+ '0'=>byte 1, '1'=>byte 1, '2'=>byte 1, '3'=>byte 1, '4'=>byte 1,
+ '5'=>byte 1, '6'=>byte 1, '7'=>byte 1, '8'=>byte 1, '9'=>byte 1,
+ ':'=>byte 1, ';' => byte 1,
+ '?' => byte 1,
+ 'A'=>byte 1, 'B'=>byte 1, 'C'=>byte 1, 'D'=>byte 1, 'E'=>byte 1, 'F'=>byte 1,
+ 'G'=>byte 1, 'H'=>byte 1, 'I'=>byte 1, 'J'=>byte 1, 'K'=>byte 1, 'L'=>byte 1,
+ 'M'=>byte 1, 'N'=>byte 1, 'O'=>byte 1, 'P'=>byte 1, 'Q'=>byte 1, 'R'=>byte 1,
+ 'S'=>byte 1, 'T'=>byte 1, 'U'=>byte 1, 'V'=>byte 1, 'W'=>byte 1, 'X'=>byte 1,
+ 'Y'=>byte 1, 'Z'=>byte 1,
+ 'a'=>byte 1, 'b'=>byte 1, 'c'=>byte 1, 'd'=>byte 1, 'e'=>byte 1, 'f'=>byte 1,
+ 'g'=>byte 1, 'h'=>byte 1, 'i'=>byte 1, 'j'=>byte 1, 'k'=>byte 1, 'l'=>byte 1,
+ 'm'=>byte 1, 'n'=>byte 1, 'o'=>byte 1, 'p'=>byte 1, 'q'=>byte 1, 'r'=>byte 1,
+ 's'=>byte 1, 't'=>byte 1, 'u'=>byte 1, 'v'=>byte 1, 'w'=>byte 1, 'x'=>byte 1,
+ 'y'=>byte 1, 'z'=>byte 1,
+ '_'=>byte 1,
+ '\''=>byte 1, '"'=>byte 1, '.'=>byte 1, ','=>byte 1, '('=>byte 1, ')'=>byte 1,
+ * => byte 0
+};
+
+TABPIX: con 30; # number of pixels in a tab
+CAPSEP: con 5; # number of pixels separating tab from caption
+SCRBREADTH: con 14; # scrollbar breadth (normal)
+SCRFBREADTH: con 14; # scrollbar breadth (inside child frame or select control)
+FRMARGIN: con 0; # default margin around frames
+RULESP: con 7; # extra space before and after rules
+POPUPLINES: con 12; # number of lines in popup select list
+MINSCR: con 6; # min size in pixels of scrollbar drag widget
+SCRDELTASF: con 10000; # fixed-point scale factor for scrollbar per-pixel step
+
+# all of the following include room for relief
+CBOXWID: con 14; # check box width
+CBOXHT: con 12; # check box height
+ENTVMARGIN : con 4; # vertical margin inside entry box
+ENTHMARGIN : con 6; # horizontal margin inside entry box
+SELMARGIN : con 4; # margin inside select control
+BUTMARGIN: con 4; # margin inside button control
+PBOXWID: con 10; # progress box width
+PBOXHT: con 16; # progress box height
+PBOXBD: con 2; # progress box border width
+
+TABLEMAXTARGET: con 2000; # targetwidth to get max width of table cell
+TABLEFLOATTARGET: con 1; # targetwidth for floating tables
+
+SELBG: con 16r00FFFF; # aqua
+
+ARPAUSE : con 500; # autorepeat initial delay (ms)
+ARTICK : con 100; # autorepeat tick delay (ms)
+
+display: ref D->Display;
+
+dbg := 0;
+dbgtab := 0;
+dbgev := 0;
+linespace := 0;
+lineascent := 0;
+charspace := 0;
+spspace := 0;
+ctllinespace := 0;
+ctllineascent := 0;
+ctlcharspace := 0;
+ctlspspace := 0;
+frameid := 0;
+zp := Point(0,0);
+
+init(cu: CharonUtils)
+{
+ CU = cu;
+ sys = load Sys Sys->PATH;
+ D = load Draw Draw->PATH;
+ S = load String String->PATH;
+ T = load StringIntTab StringIntTab->PATH;
+ U = load Url Url->PATH;
+ if (U != nil)
+ U->init();
+ E = cu->E;
+ G = cu->G;
+ I = cu->I;
+ J = cu->J;
+ B = cu->B;
+ display = G->display;
+
+ # make sure default and control fonts are loaded
+ getfont(DefFnt);
+ fnt := fonts[DefFnt].f;
+ linespace = fnt.height;
+ lineascent = fnt.ascent;
+ charspace = fnt.width("a"); # a kind of average char width
+ spspace = fonts[DefFnt].spw;
+ getfont(CtlFnt);
+ fnt = fonts[CtlFnt].f;
+ ctllinespace = fnt.height;
+ ctllineascent = fnt.ascent;
+ ctlcharspace = fnt.width("a");
+ ctlspspace = fonts[CtlFnt].spw;
+}
+
+stringwidth(s: string): int
+{
+ return fonts[DefFnt].f.width(s)/charspace;
+}
+
+# Use bsmain to fill frame f.
+# Return buffer containing source when done.
+layout(f: ref Frame, bsmain: ref ByteSource, linkclick: int) : array of byte
+{
+ dbg = int (CU->config).dbg['l'];
+ dbgtab = int (CU->config).dbg['t'];
+ dbgev = int (CU->config).dbg['e'];
+ if(dbgev)
+ CU->event("LAYOUT", 0);
+ sources : ref Sources;
+ hdr := bsmain.hdr;
+ auth := "";
+ url : ref Parsedurl;
+ if (bsmain.req != nil) {
+ auth = bsmain.req.auth;
+ url = bsmain.req.url;
+ }
+# auth := bsmain.req.auth;
+ ans : array of byte = nil;
+ di := Docinfo.new();
+ if(linkclick && f.doc != nil)
+ di.referrer = f.doc.src;
+ f.reset();
+ f.doc = di;
+ di.frameid = f.id;
+ di.src = hdr.actual;
+ di.base = hdr.base;
+ di.refresh = hdr.refresh;
+ if (hdr.chset != nil)
+ di.chset = hdr.chset;
+ di.lastModified = hdr.lastModified;
+ if(J != nil)
+ J->havenewdoc(f);
+ oclipr := f.cim.clipr;
+ f.cim.clipr = f.cr;
+ if(f.framebd != 0) {
+ f.cr = f.r.inset(2);
+ drawborder(f.cim, f.cr, 2, DarkGrey);
+ }
+ fillbg(f, f.cr);
+ G->flush(f.cr);
+ f.cim.clipr = oclipr;
+ if(f.flags&FRvscroll)
+ createvscroll(f);
+ if(f.flags&FRhscroll)
+ createhscroll(f);
+ l := Lay.new(f.cr.dx(), Aleft, f.marginw, di.background);
+ f.layout = l;
+ anyanim := 0;
+ if(hdr.mtype == CU->TextHtml || hdr.mtype == CU->TextPlain) {
+ itsrc := ItemSource.new(bsmain, f, hdr.mtype);
+ sources = Sources.new(ref Source.Shtml(bsmain, 0, itsrc));
+ }
+ else {
+ # for now, must be supported image type
+ if(!I->supported(hdr.mtype)) {
+ sys->print("Need to implement something: source isn't supported image type\n");
+ return nil;
+ }
+ imsrc := I->ImageSource.new(bsmain, 0, 0);
+ ci := CImage.new(url, nil, 0, 0);
+ simage := ref Source.Simage(bsmain, 0, ci, nil, imsrc);
+ sources = Sources.new(simage);
+ it := ref Item.Iimage(nil, 0, 0, 0, 0, 0, nil, len di.images, ci, 0, 0, "", nil, nil, -1, Abottom, byte 0, byte 0, byte 0);
+ di.images = it :: nil;
+ appenditems(f, l, it);
+ simage.itl = it :: nil;
+ }
+ while ((src := sources.waitsrc()) != nil) {
+ if(dbgev)
+ CU->event("LAYOUT GETSOMETHING", 0);
+ bs := src.bs;
+ freeit := 0;
+ if(bs.err != "") {
+ if(dbg)
+ sys->print("error getting %s: %s\n", bs.req.url.tostring(), bs.err);
+ pick s := src {
+ Srequired =>
+ s.itsrc.reqddata = array [0] of byte;
+ sources.done(src);
+ CU->freebs(bs);
+ src.bs = nil;
+ continue;
+ }
+ freeit = 1;
+ }
+ else {
+ if(bs.hdr != nil && !bs.seenhdr) {
+ (use, error, challenge, newurl) := CU->hdraction(bs, 0, src.redirects);
+ if(challenge != nil) {
+ sys->print("Need to implement authorization credential dialog\n");
+ error = "Need authorization";
+ use = 0;
+ }
+ if(error != "" && dbg)
+ sys->print("subordinate error: %s\n", error);
+ if(newurl != nil) {
+ s := ref *src;
+ freeit = 1;
+ pick ps := src {
+ Shtml or Srequired =>
+ sys->print("unexpected redirect of subord\n");
+ Simage =>
+ newci := CImage.new(newurl, nil, ps.ci.width, ps.ci.height);
+ for(itl := ps.itl; itl != nil ; itl = tl itl) {
+ pick imi := hd itl {
+ Iimage =>
+ imi.ci = newci;
+ }
+ }
+ news := ref Source.Simage(nil, 0, newci, ps.itl, nil);
+ sources.add(news, 0);
+ startimreq(news, auth);
+ }
+ }
+ if(!use)
+ freeit = 1;
+ }
+ if(!freeit) {
+ pick s := src {
+ Srequired or
+ Shtml =>
+ if (tagof src == tagof Source.Srequired) {
+ s.itsrc.reqddata = bs.data;
+ sources.done(src);
+ CU->freebs(bs);
+ src.bs = nil;
+ continue;
+# src = sources.main;
+# CU->assert(src != nil);
+ }
+ itl := s.itsrc.getitems();
+ if(di.kidinfo != nil) {
+ if(s.itsrc.kidstk == nil) {
+ layframeset(f, di.kidinfo);
+ G->flush(f.r);
+ freeit = 1;
+ }
+ }
+ else {
+ l.background = di.background;
+ anyanim |= addsubords(sources, di, auth);
+ if(itl != nil) {
+ appenditems(f, l, itl);
+ fixframegeom(f);
+ if(dbgev)
+ CU->event("LAYOUT_DRAWALL", 0);
+ f.dirty(f.totalr);
+ drawall(f);
+ }
+ }
+ if (s.itsrc.reqdurl != nil) {
+ news := ref Source.Srequired(nil, 0, s.itsrc);
+ sources.add(news, 1);
+ rbs := CU->startreq(ref CU->ReqInfo(s.itsrc.reqdurl, CU->HGet, nil, "", ""));
+ news.bs = rbs;
+ } else {
+ if (bs.eof && bs.lim == bs.edata && s.itsrc.toks == nil)
+ freeit = 1;
+ }
+ Simage =>
+ (ret, mim) := s.imsrc.getmim();
+ # mark it done even if error
+ s.ci.complete = ret;
+ if(ret == I->Mimerror) {
+ bs.err = s.imsrc.err;
+ freeit = 1;
+ }
+ else if(ret != I->Mimnone) {
+ if(s.ci.mims == nil) {
+ s.ci.mims = array[1] of { mim };
+ s.ci.width = s.imsrc.width;
+ s.ci.height = s.imsrc.height;
+ if(ret == I->Mimdone && (CU->config).imagelvl <= CU->ImgNoAnim)
+ freeit = 1;
+ }
+ else {
+ n := len s.ci.mims;
+ if(mim != s.ci.mims[n-1]) {
+ newmims := array[n + 1] of ref MaskedImage;
+ newmims[0:] = s.ci.mims;
+ newmims[n] = mim;
+ s.ci.mims = newmims;
+ anyanim = 1;
+ }
+ }
+ if(s.ci.mims[0] == mim)
+ haveimage(f, s.ci, s.itl);
+ if(bs.eof && bs.lim == bs.edata)
+ (CU->imcache).add(s.ci);
+ }
+ if(!freeit && bs.eof && bs.lim == bs.edata)
+ freeit = 1;
+ }
+ }
+ }
+ if(freeit) {
+ if(bs == bsmain)
+ ans = bs.data[0:bs.edata];
+ CU->freebs(bs);
+ src.bs = nil;
+ sources.done(src);
+ }
+ }
+ if(anyanim && (CU->config).imagelvl > CU->ImgNoAnim)
+ spawn animproc(f);
+ if(dbgev)
+ CU->event("LAYOUT_END", 0);
+ return ans;
+}
+
+# return value is 1 if found any existing images needed animation
+addsubords(sources: ref Sources, di: ref Docinfo, auth: string) : int
+{
+ anyanim := 0;
+ if((CU->config).imagelvl == CU->ImgNone)
+ return anyanim;
+ newsims: list of ref Source.Simage = nil;
+ for(il := di.images; il != nil; il = tl il) {
+ it := hd il;
+ pick i := it {
+ Iimage =>
+ if(i.ci.mims == nil) {
+ cachedci := (CU->imcache).look(i.ci);
+ if(cachedci != nil) {
+ i.ci = cachedci;
+ if(i.imwidth == 0)
+ i.imwidth = i.ci.width;
+ if(i.imheight == 0)
+ i.imheight = i.ci.height;
+ anyanim |= (len cachedci.mims > 1);
+ }
+ else {
+ sloop:
+ for(sl := sources.srcs; sl != nil; sl = tl sl) {
+ pick s := hd sl {
+ Simage =>
+ if(s.ci.match(i.ci)) {
+ s.itl = it :: s.itl;
+ # want all items on list to share same ci;
+ # want most-specific dimension specs
+ iciw := i.ci.width;
+ icih := i.ci.height;
+ i.ci = s.ci;
+ if(s.ci.width == 0 && s.ci.height == 0) {
+ s.ci.width = iciw;
+ s.ci.height = icih;
+ }
+ break sloop;
+ }
+ }
+ }
+ if(sl == nil) {
+ # didn't find existing Source for this image
+ s := ref Source.Simage(nil, 0, i.ci, it:: nil, nil);
+ newsims = s :: newsims;
+ sources.add(s, 0);
+ }
+ }
+ }
+ }
+ }
+ # Start requests for new newsources.
+ # di.images are in last-in-document-first order,
+ # so newsources is in first-in-document-first order (good order to load in).
+ for(sl := newsims; sl != nil; sl = tl sl)
+ startimreq(hd sl, auth);
+ return anyanim;
+}
+
+startimreq(s: ref Source.Simage, auth: string)
+{
+ if(dbgev)
+ CU->event(sys->sprint("LAYOUT STARTREQ %s", s.ci.src.tostring()), 0);
+ bs := CU->startreq(ref CU->ReqInfo(s.ci.src, CU->HGet, nil, auth, ""));
+ s.bs = bs;
+ s.imsrc = I->ImageSource.new(bs, s.ci.width, s.ci.height);
+}
+
+createvscroll(f: ref Frame)
+{
+ breadth := SCRBREADTH;
+ if(f.parent != nil)
+ breadth = SCRFBREADTH;
+ length := f.cr.dy();
+ if(f.flags&FRhscroll)
+ length -= breadth;
+ f.vscr = Control.newscroll(f, 1, length, breadth);
+ f.vscr.r = f.vscr.r.addpt(Point(f.cr.max.x-breadth, f.cr.min.y));
+ f.cr.max.x -= breadth;
+ if(f.cr.dx() <= 2*f.marginw)
+ CU->raisex("EXInternal: frame too small for layout");
+ f.vscr.draw(1);
+}
+
+createhscroll(f: ref Frame)
+{
+ breadth := SCRBREADTH;
+ if(f.parent != nil)
+ breadth = SCRFBREADTH;
+ length := f.cr.dx();
+ x := f.cr.min.x;
+ f.hscr = Control.newscroll(f, 0, length, breadth);
+ f.hscr.r = f.hscr.r.addpt(Point(x,f.cr.max.y-breadth));
+ f.cr.max.y -= breadth;
+ if(f.cr.dy() <= 2*f.marginh)
+ CU->raisex("EXInternal: frame too small for layout");
+ f.hscr.draw(1);
+}
+
+# Call after a change to f.layout or f.viewr.min to fix totalr and viewr
+# (We are to leave viewr.min unchanged, if possible, as
+# user might be scrolling).
+fixframegeom(f: ref Frame)
+{
+ l := f.layout;
+ if(dbg)
+ sys->print("fixframegeom, layout width=%d, height=%d\n", l.width, l.height);
+ crwidth := f.cr.dx();
+ crheight := f.cr.dy();
+ layw := max(l.width, crwidth);
+ layh := max(l.height, crheight);
+ f.totalr.max = Point(layw, layh);
+ crchanged := 0;
+ n := l.height+l.margin-crheight;
+ if(n > 0 && f.vscr == nil && (f.flags&FRvscrollauto)) {
+ createvscroll(f);
+ crchanged = 1;
+ crwidth = f.cr.dx();
+ }
+ if(f.viewr.min.y > n)
+ f.viewr.min.y = max(0, n);
+ n = l.width+l.margin-crwidth;
+ if(!crchanged && n > 0 && f.hscr == nil && (f.flags&FRhscrollauto)) {
+ createhscroll(f);
+ crchanged = 1;
+ crheight = f.cr.dy();
+ }
+ if(crchanged) {
+ relayout(f, l, crwidth, l.just);
+ fixframegeom(f);
+ return;
+ }
+ if(f.viewr.min.x > n)
+ f.viewr.min.x = max(0, n);
+ f.viewr.max.x = min(f.viewr.min.x+crwidth, layw);
+ f.viewr.max.y = min(f.viewr.min.y+crheight, layh);
+ if(f.vscr != nil)
+ f.vscr.scrollset(f.viewr.min.y, f.viewr.max.y, f.totalr.max.y, 0, 1);
+ if(f.hscr != nil)
+ f.hscr.scrollset(f.viewr.min.x, f.viewr.max.x, f.totalr.max.x, f.viewr.dx()/5, 1);
+}
+
+# The items its within f are Iimage items,
+# and its image, ci, now has at least a ci.mims[0], which may be partially
+# or fully filled.
+haveimage(f: ref Frame, ci: ref CImage, itl: list of ref Item)
+{
+ if(dbgev)
+ CU->event("HAVEIMAGE", 0);
+ if(dbg)
+ sys->print("\nHAVEIMAGE src=%s w=%d h=%d\n", ci.src.tostring(), ci.width, ci.height);
+ # make all base images repl'd - makes handling backgrounds much easier
+ im := ci.mims[0].im;
+ im.repl = 1;
+ im.clipr = Rect((-16rFFFFFFF, -16r3FFFFFFF), (16r3FFFFFFF, 16r3FFFFFFF));
+ dorelayout := 0;
+ for( ; itl != nil; itl = tl itl) {
+ it := hd itl;
+ pick i := it {
+ Iimage =>
+ if (!(it.state & B->IFbkg)) {
+ # If i.imwidth and i.imheight are not both 0, the HTML specified the dimens.
+ # If one of them is 0, the other is to be scaled by the same factor;
+ # we have to relay the line in that case too.
+ if(i.imwidth == 0 || i.imheight == 0) {
+ i.imwidth = ci.width;
+ i.imheight = ci.height;
+ setimagedims(i);
+ loc := f.find(zp, it);
+ # sometimes the image was added to doc image list, but
+ # never made it to layout (e.g., because html bug prevented
+ # a table from being added).
+ # also, script-created images won't have items
+ if(loc != nil) {
+ f.layout.flags |= Lchanged;
+ markchanges(loc);
+ dorelayout = 1;
+ # Floats are assumed to be premeasured, so if there
+ # are any floats in the loc list, remeasure them
+ for(k := loc.n-1; k > 0; k--) {
+ if(loc.le[k].kind == LEitem) {
+ locit := loc.le[k].item;
+ pick fit := locit {
+ Ifloat =>
+ pick xi := fit.item {
+ Iimage =>
+ fit.height = fit.item.height;
+ Itable =>
+ checktabsize(f, xi, TABLEFLOATTARGET);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ if(dbg > 1) {
+ sys->print("\nhaveimage item: ");
+ it.print();
+ }
+ }
+ }
+ if(dorelayout) {
+ relayout(f, f.layout, f.layout.targetwidth, f.layout.just);
+ fixframegeom(f);
+ }
+ f.dirty(f.totalr);
+ drawall(f);
+ if(dbgev)
+ CU->event("HAVEIMAGE_END", 0);
+}
+# For first layout of subelements, such as table cells.
+# After this, content items will be dispersed throughout resulting lay.
+# Return index into f.sublays.
+# (This roundabout way of storing sublayouts avoids pointers to Lay
+# in Build, so that all of the layout-related stuff can be in Layout
+# where it belongs.)
+sublayout(f: ref Frame, targetwidth: int, just: byte, bg: Background, content: ref Item) : int
+{
+ if(dbg)
+ sys->print("sublayout, targetwidth=%d\n", targetwidth);
+ l := Lay.new(targetwidth, just, 0, bg);
+ if(f.sublayid >= len f.sublays) {
+ newsublays := array[len f.sublays + 30] of ref Lay;
+ newsublays[0:] = f.sublays;
+ f.sublays = newsublays;
+ }
+ id := f.sublayid;
+ f.sublays[id] = l;
+ f.sublayid++;
+ appenditems(f, l, content);
+ l.flags &= ~Lchanged;
+ if(dbg)
+ sys->print("after sublayout, width=%d\n", l.width);
+ return id;
+}
+
+# Relayout of lay, given a new target width or if something changed inside
+# or if the global justification for the layout changed.
+# Floats are hard: for now, just relay everything with floats temporarily
+# moved way down, if there are any floats.
+relayout(f: ref Frame, lay: ref Lay, targetwidth: int, just: byte)
+{
+ if(dbg)
+ sys->print("relayout, targetwidth=%d, old target=%d, changed=%d\n",
+ targetwidth, lay.targetwidth, (lay.flags&Lchanged) != byte 0);
+ changeall := (lay.targetwidth != targetwidth || lay.just != just);
+ if(!changeall && !int(lay.flags&Lchanged))
+ return;
+ if(lay.floats != nil) {
+ # move the current y positions of floats to a big value,
+ # so they don't contribute to floatw until after they've
+ # been encountered in current fixgeom
+ for(flist := lay.floats; flist != nil; flist = tl flist) {
+ ff := hd flist;
+ ff.y = 16r6fffffff;
+ }
+ changeall = 1;
+ }
+ lay.targetwidth = targetwidth;
+ lay.just = just;
+ lay.height = 0;
+ lay.width = 0;
+ if(changeall)
+ changelines(lay.start.next, lay.end);
+ fixgeom(f, lay, lay.start.next);
+ lay.flags &= ~Lchanged;
+ if(dbg)
+ sys->print("after relayout, width=%d\n", lay.width);
+}
+
+# Measure and append the items to the end of layout lay,
+# and fix the geometry.
+appenditems(f: ref Frame, lay: ref Lay, items: ref Item)
+{
+ measure(f, items);
+ if(dbg)
+ items.printlist("appenditems, after measure");
+ it := items;
+ if(it == nil)
+ return;
+ lprev := lay.end.prev;
+ l : ref Line;
+ lit := lastitem(lprev.items);
+ if(lit == nil || (it.state&IFbrk)) {
+ # start a new line after existing last line
+ l = Line.new();
+ appendline(lprev, l);
+ l.items = it;
+ }
+ else {
+ # start appending items to existing last line
+ l = lprev;
+ lit.next = it;
+ }
+ l.flags |= Lchanged;
+ while(it != nil) {
+ nexti := it.next;
+ if(nexti == nil || (nexti.state&IFbrk)) {
+ it.next = nil;
+ fixgeom(f, lay, l);
+ if(nexti == nil)
+ break;
+ # now there may be multiple lines containing the
+ # items from l, but the one after the last is lay.end
+ l = Line.new();
+ appendline(lay.end.prev, l);
+ l.flags |= Lchanged;
+ it = nexti;
+ l.items = it;
+ }
+ else
+ it = nexti;
+ }
+}
+
+# Fix up the geometry of line l and successors.
+# Assume geometry of previous line is correct.
+fixgeom(f: ref Frame, lay: ref Lay, l: ref Line)
+{
+ while(l != nil) {
+ fixlinegeom(f, lay, l);
+ mergetext(l);
+ l = l.next;
+ }
+ lay.height = max(lay.height, lay.end.pos.y);
+}
+
+mergetext(l: ref Line)
+{
+ lastit : ref Item;
+ for (it := l.items; it != nil; it = it.next) {
+ pick i := it {
+ Itext =>
+ if (lastit == nil)
+ break; #pick
+ pick pi := lastit {
+ Itext =>
+ # ignore item state flags as fixlinegeom()
+ # will have taken account of them.
+ if (pi.anchorid == i.anchorid &&
+ pi.fnt == i.fnt && pi.fg == i.fg && pi.voff == i.voff && pi.ul == i.ul) {
+ # compatible - merge
+ pi.s += i.s;
+ pi.width += i.width;
+ pi.next = i.next;
+ continue;
+ }
+ }
+ }
+ lastit = it;
+ }
+}
+
+# Fix geom for one line.
+# This may change the overall lay.width, if there is no way
+# to fit the line into the target width.
+fixlinegeom(f: ref Frame, lay: ref Lay, l: ref Line)
+{
+ lprev := l.prev;
+ y := lprev.pos.y + lprev.height;
+ it := l.items;
+ state := it.state;
+ if(dbg > 1) {
+ sys->print("\nfixlinegeom start, y=prev.y+prev.height=%d+%d=%d, changed=%d\n",
+ l.prev.pos.y, lprev.height, y, int (l.flags&Lchanged));
+ if(dbg > 2)
+ it.printlist("items");
+ else {
+ sys->print("first item: ");
+ it.print();
+ }
+ }
+ if(state&IFbrk) {
+ y = pastbrk(lay, y, state);
+ if(dbg > 1 && y != lprev.pos.y + lprev.height)
+ sys->print("after pastbrk, line y is now %d\n", y);
+ }
+ l.pos.y = y;
+ lineh := max(l.height, linespace);
+ lfloatw := floatw(y, y+lineh, lay.floats, Aleft);
+ rfloatw := floatw(y, y+lineh, lay.floats, Aright);
+ if((l.flags&Lchanged) == byte 0) {
+ # possibly adjust lay.width
+ n := (lay.width-rfloatw)-(l.pos.x-lay.margin+l.width);
+ if(n < 0)
+ lay.width += -n;
+ return;
+ }
+ hang := (state&IFhangmask)*TABPIX/10;
+ linehang := hang;
+ hangtogo := hang;
+ indent := ((state&IFindentmask)>>IFindentshift)*TABPIX;
+ just := (state&(IFcjust|IFrjust));
+ if(just == 0 && lay.just != Aleft) {
+ if(lay.just == byte Acenter)
+ just = IFcjust;
+ else if(lay.just == Aright)
+ just = IFrjust;
+ }
+ right := lay.targetwidth - lay.margin;
+ lwid := right - (lfloatw+rfloatw+indent+lay.margin);
+ if(lwid < 0) {
+ if (right - lwid > lay.width)
+ lay.width = right - lwid;
+ right += -lwid;
+ lwid = 0;
+ }
+ lwid += hang;
+ if(dbg > 1) {
+ sys->print("fixlinegeom, now y=%d, lfloatw=%d, rfloatw=%d, indent=%d, hang=%d, lwid=%d\n",
+ y, lfloatw, rfloatw, indent, hang, lwid);
+ }
+ w := 0;
+ lineh = 0;
+ linea := 0;
+ lastit: ref Item = nil;
+ nextfloats: list of ref Item.Ifloat = nil;
+ anystuff := 0;
+ eol := 0;
+ while(it != nil && !eol) {
+ if(dbg > 2) {
+ sys->print("fixlinegeom loop head, w=%d, loop item:\n", w);
+ it.print();
+ }
+ state = it.state;
+ wrapping := int (state&IFwrap);
+ if(anystuff && (state&IFbrk))
+ break;
+ checkw := 1;
+ if(hang && !(state&IFhangmask)) {
+ lwid -= hang;
+ hang = 0;
+ if(hangtogo > 0) {
+ # insert a null spacer item
+ spaceit := Item.newspacer(ISPgeneral, 0);
+ spaceit.width = hangtogo;
+ if(lastit != nil) {
+ spaceit.state = lastit.state & ~(IFbrk|IFbrksp|IFnobrk|IFcleft|IFcright);
+ lastit.next = spaceit;
+ }
+ else
+ lastit = spaceit;
+ spaceit.next = it;
+ }
+ }
+ pick i := it {
+ Ifloat =>
+ if(anystuff) {
+ # float will go after this line
+ nextfloats = i :: nextfloats;
+ }
+ else {
+ # add float beside current line, adjust widths
+ fixfloatxy(lay, y, i);
+ # TODO: only do following if y and/or height changed
+ changelines(l.next, lay.end);
+ newlfloatw := floatw(y, y+lineh, lay.floats, Aleft);
+ newrfloatw := floatw(y, y+lineh, lay.floats, Aright);
+ lwid -= (newlfloatw-lfloatw) + (newrfloatw-rfloatw);
+ if (lwid < 0) {
+ right += -lwid;
+ lwid = 0;
+ }
+ lfloatw = newlfloatw;
+ rfloatw = newrfloatw;
+ }
+ checkw = 0;
+ Itable =>
+ # When just doing layout for cell dimensions, don't
+ # want a "100%" spec to make the table really wide
+ kindspec := 0;
+ if(lay.targetwidth == TABLEMAXTARGET && i.table.width.kind() == Dpercent) {
+ kindspec = i.table.width.kindspec;
+ i.table.width = Dimen.make(Dnone, 0);
+ }
+ checktabsize(f, i, lwid-w);
+ if(kindspec != 0)
+ i.table.width.kindspec = kindspec;
+ Irule =>
+ avail := lwid-w;
+ # When just doing layout for cell dimensions, don't
+ # want a "100%" spec to make the rule really wide
+ if(lay.targetwidth == TABLEMAXTARGET)
+ avail = min(10, avail);
+ i.width = widthfromspec(i.wspec, avail);
+ Iformfield =>
+ checkffsize(f, i, i.formfield);
+ }
+ if(checkw) {
+ iw := it.width;
+ if(wrapping && w + iw > lwid) {
+ # it doesn't fit; see if it can be broken
+ takeit: int;
+ noneok := (anystuff || lfloatw != 0 || rfloatw != 0) && !(state&IFnobrk);
+ (takeit, iw) = trybreak(it, lwid-w, iw, noneok);
+ eol = 1;
+ if(!takeit) {
+ if(lastit == nil) {
+ # Nothing added because one of the float widths
+ # is nonzero, and not enough room for anything else.
+ # Move y down until there's more room and try again.
+ CU->assert(lfloatw != 0 || rfloatw != 0);
+ oldy := y;
+ y = pastbrk(lay, y, IFcleft|IFcright);
+ if(dbg > 1)
+ sys->print("moved y past %d, now y=%d\n", oldy, y);
+ CU->assert(y > oldy); # else infinite recurse
+ # Do the move down by artificially increasing the
+ # height of the previous line
+ lprev.height += y-oldy;
+ fixlinegeom(f, lay, l);
+ return;
+ } else
+ break;
+ }
+ }
+ w += iw;
+ if(hang)
+ hangtogo -= w;
+ (lineh, linea) = lgeom(lineh, linea, it);
+ if(!anystuff) {
+ anystuff = 1;
+ # don't count an ordinary space as 'stuff' if wrapping
+ pick t := it {
+ Itext =>
+ if(wrapping && t.s == " ")
+ anystuff = 0;
+ }
+ }
+ }
+ lastit = it;
+ it = it.next;
+ if(it == nil && !eol) {
+ # perhaps next lines items can now fit on this line
+ nextl := l.next;
+ nit := nextl.items;
+ if(nextl != lay.end && !(nit.state&IFbrk)) {
+ lastit.next = nit;
+ # remove nextl
+ l.next = nextl.next;
+ l.next.prev = l;
+ it = nit;
+ }
+ }
+ }
+ # line is complete, next line will start with it (or it is nil)
+ rest := it;
+ if(lastit == nil)
+ CU->raisex("EXInternal: no items on line");
+ lastit.next = nil;
+
+ l.width = w;
+ x := lfloatw + lay.margin + indent - linehang;
+ # shift line if it begins with a space or a rule
+ pick pi := l.items {
+ Itext =>
+ if(pi.s != nil && pi.s[0] == ' ')
+ x -= fonts[pi.fnt].spw;
+ Irule =>
+ # note: build ensures that rules appear on lines
+ # by themselves
+ if(pi.align == Acenter)
+ just = IFcjust;
+ else if(pi.align == Aright)
+ just = IFrjust;
+ Ifloat =>
+ if(pi.next != nil) {
+ pick qi := pi.next {
+ Itext =>
+ if(qi.s != nil && qi.s[0] == ' ')
+ x -= fonts[qi.fnt].spw;
+ }
+ }
+ }
+ xright := x+w;
+ if (xright + rfloatw > lay.width)
+ lay.width = xright+rfloatw;
+ n := lay.targetwidth-(lay.margin+rfloatw+xright);
+ if(n > 0 && just) {
+ if(just&IFcjust)
+ x += n/2;
+ else
+ x += n;
+ }
+ if(dbg > 1) {
+ sys->print("line geometry fixed, (x,y)=(%d,%d), w=%d, h=%d, a=%d, lfloatw=%d, rfloatw=%d, lay.width=%d\n",
+ x, l.pos.y, w, lineh, linea, lfloatw, rfloatw, lay.width);
+ if(dbg > 2)
+ l.items.printlist("final line items");
+ }
+ l.pos.x = x;
+ l.height = lineh;
+ l.ascent = linea;
+ l.flags &= ~Lchanged;
+
+ if(nextfloats != nil)
+ fixfloatsafter(lay, l, nextfloats);
+
+ if(rest != nil) {
+ nextl := l.next;
+ if(nextl == lay.end || (nextl.items.state&IFbrk)) {
+ nextl = Line.new();
+ appendline(l, nextl);
+ }
+ li := lastitem(rest);
+ li.next = nextl.items;
+ nextl.items = rest;
+ nextl.flags |= Lchanged;
+ }
+}
+
+# Return y coord after y due to a break.
+pastbrk(lay: ref Lay, y, state: int) : int
+{
+ nextralines := 0;
+ if(state&IFbrksp)
+ nextralines = 1;
+ ynext := y;
+ if(state&IFcleft)
+ ynext = floatclry(lay.floats, Aleft, ynext);
+ if(state&IFcright)
+ ynext = max(ynext, floatclry(lay.floats, Aright, ynext));
+ ynext += nextralines*linespace;
+ return ynext;
+}
+
+# Add line l after lprev (and before lprev's current successor)
+appendline(lprev, l: ref Line)
+{
+ l.next = lprev.next;
+ l.prev = lprev;
+ l.next.prev = l;
+ lprev.next = l;
+}
+
+# Mark lines l up to but not including lend as changed
+changelines(l, lend: ref Line)
+{
+ for( ; l != lend; l = l.next)
+ l.flags |= Lchanged;
+}
+
+# Return a ref Font for font number num = (style*NumSize + size)
+getfont(num: int) : ref Font
+{
+ f := fonts[num].f;
+ if(f == nil) {
+ f = Font.open(display, fonts[num].name);
+ if(f == nil) {
+ if(num == DefFnt)
+ CU->raisex(sys->sprint("exLayout: can't open default font %s: %r", fonts[num].name));
+ else {
+ if(int (CU->config).dbg['w'])
+ sys->print("warning: substituting default for font %s\n",
+ fonts[num].name);
+ f = fonts[DefFnt].f;
+ }
+ }
+ fonts[num].f = f;
+ fonts[num].spw = f.width(" ");
+ }
+ return f;
+}
+
+# Set the width, height and ascent fields of all items, getting any necessary fonts.
+# Some widths and heights depend on the available width on the line, and may be
+# wrong until checked during fixlinegeom.
+# Don't do tables here at all (except floating tables).
+# Configure Controls for form fields.
+measure(fr: ref Frame, items: ref Item)
+{
+ for(it := items; it != nil; it = it.next) {
+ pick t := it {
+ Itext =>
+ f := getfont(t.fnt);
+ it.width = f.width(t.s);
+ a := f.ascent;
+ h := f.height;
+ if(t.voff != byte Voffbias) {
+ a -= (int t.voff) - Voffbias;
+ if(a > h)
+ h = a;
+ }
+ it.height = h;
+ it.ascent = a;
+ Irule =>
+ it.height = t.size + 2*RULESP;
+ it.ascent = t.size + RULESP;
+ Iimage =>
+ setimagedims(t);
+ Iformfield =>
+ c := Control.newff(fr, t.formfield);
+ if(c != nil) {
+ t.formfield.ctlid = fr.addcontrol(c);
+ it.width = c.r.dx();
+ it.height = c.r.dy();
+ it.ascent = it.height;
+ pick pc := c {
+ Centry =>
+ it.ascent = lineascent + ENTVMARGIN;
+ Cselect =>
+ it.ascent = lineascent + SELMARGIN;
+ Cbutton =>
+ if(pc.dorelief)
+ it.ascent -= BUTMARGIN;
+ }
+ }
+ Ifloat =>
+ # Leave w at zero, so it doesn't contribute to line width in normal way
+ # (Can find its width in t.item.width).
+ pick i := t.item {
+ Iimage =>
+ setimagedims(i);
+ it.height = t.item.height;
+ Itable =>
+ checktabsize(fr, i, TABLEFLOATTARGET);
+ * =>
+ CU->assert(0);
+ }
+ it.ascent = it.height;
+ Ispacer =>
+ case t.spkind {
+ ISPvline =>
+ f := getfont(t.fnt);
+ it.height = f.height;
+ it.ascent = f.ascent;
+ ISPhspace =>
+ getfont(t.fnt);
+ it.width = fonts[t.fnt].spw;
+ }
+ }
+ }
+}
+
+# Set the dimensions of an image item
+setimagedims(i: ref Item.Iimage)
+{
+ i.width = i.imwidth + 2*(int i.hspace + int i.border);
+ i.height = i.imheight + 2*(int i.vspace + int i.border);
+ i.ascent = i.height - (int i.vspace + int i.border);
+ if((CU->config).imagelvl == CU->ImgNone && i.altrep != "") {
+ f := fonts[DefFnt].f;
+ i.width = max(i.width, f.width(i.altrep));
+ i.height = max(i.height, f.height);
+ i.ascent = f.ascent;
+ }
+}
+
+# Line geometry function:
+# Given current line height (H) and ascent (distance from top to baseline) (A),
+# and an item, see if that item changes height and ascent.
+# Return (H', A'), the updated line height and ascent.
+lgeom(H, A: int, it: ref Item) : (int, int)
+{
+ h := it.height;
+ a := it.ascent;
+ atype := Abaseline;
+ pick i := it {
+ Iimage =>
+ atype = i.align;
+ Itable =>
+ atype = Atop;
+ Ifloat =>
+ return (H, A);
+ }
+ d := h-a;
+ Hnew := H;
+ Anew := A;
+ case int atype {
+ int Abaseline or int Abottom =>
+ if(a > A) {
+ Anew = a;
+ Hnew += (Anew - A);
+ }
+ if(d > Hnew - Anew)
+ Hnew = Anew + d;
+ int Atop =>
+ # OK to ignore what comes after in the line
+ if(h > H)
+ Hnew = h;
+ int Amiddle or int Acenter =>
+ # supposed to align middle with baseline
+ hhalf := h/2;
+ if(hhalf > A)
+ Anew = hhalf;
+ if(hhalf > H-Anew)
+ Hnew = Anew + hhalf;
+ }
+ return (Hnew, Anew);
+}
+
+# Try breaking item bit to make it fit in availw.
+# If that is possible, change bit to be the part that fits
+# and insert the rest between bit and bit.next.
+# iw is the current width of bit.
+# If noneok is 0, break off the minimum size word
+# even if it exceeds availw.
+# Return (1 if supposed to take bit, iw' = new width of bit)
+trybreak(bit: ref Item, availw, iw, noneok: int) : (int, int)
+{
+ if(iw <= 0)
+ return (1, iw);
+ if(availw < 0) {
+ if(noneok)
+ return (0, iw);
+ else
+ availw = 0;
+ }
+ pick t := bit {
+ Itext =>
+ if(len t.s < 2)
+ return (!noneok, iw);
+ (s1, w1, s2, w2) := breakstring(t.s, iw, fonts[t.fnt].f, availw, noneok);
+ if(w1 == 0)
+ return (0, iw);
+ itn := Item.newtext(s2, t.fnt, t.fg, int t.voff, t.ul);
+ itn.width = w2;
+ itn.height = t.height;
+ itn.ascent = t.ascent;
+ itn.anchorid = t.anchorid;
+ itn.state = t.state & ~(IFbrk|IFbrksp|IFnobrk|IFcleft|IFcright);
+ itn.next = t.next;
+ t.next = itn;
+ t.s = s1;
+ t.width = w1;
+ return (1, w1);
+ }
+ return (!noneok, iw);
+}
+
+# s has width sw when drawn in fnt.
+# Break s into s1 and s2 so that s1 fits in availw.
+# If noneok is true, it is ok for s1 to be nil, otherwise might
+# have to return an s1 that overflows availw somewhat.
+# Return (s1, w1, s2, w2) where w1 and w2 are widths of s1 and s2.
+# Assume caller has already checked that sw > availw.
+breakstring(s: string, sw: int, fnt: ref Font, availw, noneok: int) : (string, int, string, int)
+{
+ slen := len s;
+ if(slen < 2) {
+ if(noneok)
+ return (nil, 0, s, sw);
+ else
+ return (s, sw, nil, 0);
+ }
+
+ # Use linear interpolation to guess break point.
+ # We know avail < iw by conditions of trybreak call.
+ i := slen*availw / sw - 1;
+ if(i < 0)
+ i = 0;
+ i = breakpoint(s, i, -1);
+ (ss, ww) := tryw(fnt, s, i);
+ if(ww > availw) {
+ while(ww > availw) {
+ i = breakpoint(s, i-1, -1);
+ if(i <= 0)
+ break;
+ (ss, ww) = tryw(fnt, s, i);
+ }
+ }
+ else {
+ oldi := i;
+ oldss := ss;
+ oldww := ww;
+ while(ww < availw) {
+ oldi = i;
+ oldss = ss;
+ oldww = ww;
+ i = breakpoint(s, i+1, 1);
+ if(i >= slen)
+ break;
+ (ss, ww) = tryw(fnt, s, i);
+ }
+ i = oldi;
+ ss = oldss;
+ ww = oldww;
+ }
+ if(i <= 0 || i >= slen) {
+ if(noneok)
+ return (nil, 0, s, sw);
+ i = breakpoint(s, 1, 1);
+ (ss,ww) = tryw(fnt, s, i);
+ }
+ return (ss, ww, s[i:slen], sw-ww);
+}
+
+# If can break between s[i-1] and s[i], return i.
+# Else move i in direction incr until this is true.
+# (Might end up returning 0 or len s).
+breakpoint(s: string, i, incr: int) : int
+{
+ slen := len s;
+ ans := 0;
+ while(i > 0 && i < slen) {
+ ci := s[i];
+ di := s[i-1];
+
+ # ASCII rules
+ if ((ci < 16rA0 && !int wordchar[ci]) || (di < 16rA0 && !int wordchar[di])) {
+ ans = i;
+ break;
+ }
+
+ # Treat all ideographs as breakable.
+ # The following range includes unassigned unicode code points.
+ # All assigned code points in the range are class ID (ideograph) as defined
+ # by the Unicode consortium's LineBreak data.
+ # There are many other class ID code points outside of this range.
+ # For details on how to do unicode line breaking properly see:
+ # Unicode Standard Annex #14 (http://www.unicode.org/unicode/reports/tr14/)
+
+ if ((ci >= 16r30E && ci <= 16r9FA5) || (di >= 16r30E && di <= 16r9FA5)) {
+ ans = i;
+ break;
+ }
+
+ # consider all other characters as unbreakable
+ i += incr;
+ }
+ if(i == slen)
+ ans = slen;
+ return ans;
+}
+
+# Return (s[0:i], width of that slice in font fnt)
+tryw(fnt: ref Font, s: string, i: int) : (string, int)
+{
+ if(i == 0)
+ return ("", 0);
+ ss := s[0:i];
+ return (ss, fnt.width(ss));
+}
+
+# Return max width of a float that overlaps [ymin, ymax) on given side.
+# Floats are in reverse order of addition, so each float's y is <= that of
+# preceding floats in list. Floats from both sides are intermixed.
+floatw(ymin, ymax: int, flist: list of ref Item.Ifloat, side: byte) : int
+{
+ ans := 0;
+ for( ; flist != nil; flist = tl flist) {
+ fl := hd flist;
+ if(fl.side != side)
+ continue;
+ fymin := fl.y;
+ fymax := fymin + fl.item.height;
+ if((fymin <= ymin && ymin < fymax) ||
+ (ymin <= fymin && fymin < ymax)) {
+ w := fl.x;
+ if(side == Aleft)
+ w += fl.item.width;
+ if(ans < w)
+ ans = w;
+ }
+ }
+ return ans;
+}
+
+# Float f is to be at vertical position >= y.
+# Fix its (x,y) pos and add it to lay.floats, if not already there.
+fixfloatxy(lay: ref Lay, y: int, f: ref Item.Ifloat)
+{
+ height := f.item.height;
+ width := f.item.width;
+ f.y = y;
+ flist := lay.floats;
+ if(f.infloats != byte 0) {
+ # only take previous floats into account for width
+ while(flist != nil) {
+ x := hd flist;
+ flist = tl flist;
+ if(x == f)
+ break;
+ }
+ }
+ f.x = floatw(y, y+height, flist, f.side);
+ endx := f.x + width + lay.margin;
+ if (endx > lay.width)
+ lay.width = endx;
+ if (f.side == Aright)
+ f.x += width;
+ endy := f.y + height + lay.margin;
+ if (endy > lay.height)
+ lay.height = endy;
+ if(f.infloats == byte 0) {
+ lay.floats = f :: lay.floats;
+ f.infloats = byte 1;
+ }
+}
+
+# Floats in flist are to go after line l.
+fixfloatsafter(lay: ref Lay, l: ref Line, flist: list of ref Item.Ifloat)
+{
+ change := 0;
+ y := l.pos.y + l.height;
+ for(itl := Item.revlist(flist); itl != nil; itl = tl itl) {
+ pick fl := hd itl {
+ Ifloat =>
+ oldy := fl.y;
+ fixfloatxy(lay, y, fl);
+ if(fl.y != oldy)
+ change = 1;
+ y += fl.item.height;
+ }
+ }
+# if(change)
+# TODO only change if y and/or height changed
+ changelines(l.next, lay.end);
+}
+
+# If there's a float on given side that starts on or before y and
+# ends after y, return ending y of that float, else return original y.
+# Assume float list is bottom up.
+floatclry(flist: list of ref Item.Ifloat, side: byte, y: int) : int
+{
+ ymax := y;
+ for( ; flist != nil; flist = tl flist) {
+ fl := hd flist;
+ if(fl.side == side) {
+ if(fl.y <= y) {
+ flymax := fl.y + fl.item.height;
+ if (fl.item.height == 0)
+ # assume it will have some height later
+ flymax++;
+ if(flymax > ymax)
+ ymax = flymax;
+ }
+ }
+ }
+ return ymax;
+}
+
+# Do preliminaries to laying out table tab in target width linewidth,
+# setting total height and width.
+sizetable(f: ref Frame, tab: ref Table, availwidth: int)
+{
+ if(dbgtab)
+ sys->print("sizetable %d, availwidth=%d, nrow=%d, ncol=%d, changed=%x, tab.availw=%d\n",
+ tab.tableid, availwidth, tab.nrow, tab.ncol, int (tab.flags&Lchanged), tab.availw);
+ if(tab.ncol == 0 || tab.nrow == 0)
+ return;
+ if(tab.availw == availwidth && (tab.flags&Lchanged) == byte 0)
+ return;
+ (hsp, vsp, pad, bd, cbd, hsep, vsep) := tableparams(tab);
+ totw := widthfromspec(tab.width, availwidth);
+ # reduce totw by spacing, padding, and rule widths
+ # to leave amount left for contents
+ totw -= (tab.ncol-1)*hsep+ 2*(hsp+bd+pad+cbd);
+ if(totw <= 0)
+ totw = 1;
+ if(dbgtab)
+ sys->print("\nsizetable %d, totw=%d, hsp=%d, vsp=%d, pad=%d, bd=%d, cbd=%d, hsep=%d, vsep=%d\n",
+ tab.tableid, totw, hsp, vsp, pad, bd, cbd, hsep, vsep);
+ for(cl := tab.cells; cl != nil; cl = tl cl) {
+ c := hd cl;
+ clay : ref Lay = nil;
+ if(c.layid >= 0)
+ clay = f.sublays[c.layid];
+ if(clay == nil || (clay.flags&Lchanged) != byte 0) {
+ c.minw = -1;
+ tw := TABLEMAXTARGET;
+ if(c.wspec.kind() != Dnone)
+ tw = widthfromspec(c.wspec, totw);
+
+ # When finding max widths, want to lay out using Aleft alignment,
+ # because we don't yet know final width for proper justification.
+ # If the max widths are accepted, we'll redo those needing other justification.
+ if(clay == nil) {
+ if(dbg)
+ sys->print("Initial layout for cell %d.%d\n", tab.tableid, c.cellid);
+ c.layid = sublayout(f, tw, Aleft, c.background, c.content);
+ clay = f.sublays[c.layid];
+ c.content = nil;
+ }
+ else {
+ if(dbg)
+ sys->print("Relayout (for max) for cell %d.%d\n", tab.tableid, c.cellid);
+ relayout(f, clay, tw, Aleft);
+ }
+ clay.flags |= Lchanged; # for min test, below
+ c.maxw = clay.width;
+ if(dbgtab)
+ sys->print("sizetable %d for cell %d max layout done, targw=%d, c.maxw=%d\n",
+ tab.tableid, c.cellid, tw, c.maxw);
+ if(c.wspec.kind() == Dpixels) {
+ # Other browsers don't make the following adjustment for
+ # percentage and relative widths
+ if(c.maxw <= tw)
+ c.maxw = tw;
+ if(dbgtab)
+ sys->print("after spec adjustment, c.maxw=%d\n", c.maxw);
+ }
+ }
+ }
+
+ # calc max column widths
+ colmaxw := array[tab.ncol] of { * => 0};
+ maxw := widthcalc(tab, colmaxw, hsep, 1);
+
+ if(dbgtab)
+ sys->print("sizetable %d maxw=%d, totw=%d\n", tab.tableid, maxw, totw);
+ ci: int;
+ if(maxw <= totw) {
+ # trial layouts are fine,
+ # but if table width was specified, add more space
+ d := 0;
+ adjust := (totw > maxw && tab.width.kind() != Dnone);
+ for(ci = 0; ci < tab.ncol; ci++) {
+ if (adjust) {
+ delta := (totw-maxw);
+ d = delta / (tab.ncol - ci);
+ if (d <= 0) {
+ d = delta;
+ adjust = 0;
+ }
+ maxw += d;
+ }
+ tab.cols[ci].width = colmaxw[ci] + d;
+ }
+ }
+ else {
+ # calc min column widths and apportion out
+ # differences
+ if(dbgtab)
+ sys->print("sizetable %d, availwidth %d, need min widths too\n", tab.tableid, availwidth);
+ for(cl = tab.cells; cl != nil; cl = tl cl) {
+ c := hd cl;
+ clay := f.sublays[c.layid];
+ if(c.minw == -1 || (clay.flags&Lchanged) != byte 0) {
+ if(dbg)
+ sys->print("Relayout (for min) for cell %d.%d\n", tab.tableid, c.cellid);
+ relayout(f, clay, 1, Aleft);
+ c.minw = clay.width;
+ if(dbgtab)
+ sys->print("sizetable %d for cell %d min layout done, c.min=%d\n",
+ tab.tableid, c.cellid, clay.width);
+ }
+ }
+ colminw := array[tab.ncol] of { * => 0};
+ minw := widthcalc(tab, colminw, hsep, 0);
+ w := totw - minw;
+ d := maxw - minw;
+ if(dbgtab)
+ sys->print("sizetable %d minw=%d, w=%d, d=%d\n", tab.tableid, minw, w, d);
+ for(ci = 0; ci < tab.ncol; ci++) {
+ wd : int;
+ if(w < 0 || d < 0)
+ wd = colminw[ci];
+ else
+ wd = colminw[ci] + (colmaxw[ci] - colminw[ci])*w/d;
+ if(dbgtab)
+ sys->print("sizetable %d col[%d].width = %d\n", tab.tableid, ci, wd);
+ tab.cols[ci].width = wd;
+ }
+
+ if(dbgtab)
+ sys->print("sizetable %d, availwidth %d, doing final layouts\n", tab.tableid, availwidth);
+ }
+
+ # now have col widths; set actual cell dimensions
+ # and relayout (note: relayout will do no work if the target width
+ # and just haven't changed from last layout)
+ for(cl = tab.cells; cl != nil; cl = tl cl) {
+ c := hd cl;
+ clay := f.sublays[c.layid];
+ wd := cellwidth(tab, c, hsep);
+ if(dbgtab)
+ sys->print("sizetable %d for cell %d, clay.width=%d, cellwidth=%d\n",
+ tab.tableid, c.cellid, clay.width, wd);
+ if(dbg)
+ sys->print("Relayout (final) for cell %d.%d\n", tab.tableid, c.cellid);
+ relayout(f, clay, wd, c.align.halign);
+ if(dbgtab)
+ sys->print("sizetable %d for cell %d, final width %d, got width %d, height %d\n",
+ tab.tableid, c.cellid, wd, clay.width, clay.height);
+ }
+
+ # set row heights and ascents
+ # first pass: ignore cells with rowspan > 1
+ for(ri := 0; ri < tab.nrow; ri++) {
+ row := tab.rows[ri];
+ h := 0;
+ a := 0;
+ n : int;
+ for(rcl := row.cells; rcl != nil; rcl = tl rcl) {
+ c := hd rcl;
+ if(c.rowspan > 1 || c.layid < 0)
+ continue;
+ al := c.align.valign;
+ if(al == Anone)
+ al = tab.rows[c.row].align.valign;
+ clay := f.sublays[c.layid];
+ if(al == Abaseline) {
+ n = c.ascent;
+ if(n > a) {
+ h += (n - a);
+ a = n;
+ }
+ n = clay.height - c.ascent;
+ if(n > h-a)
+ h = a + n;
+ }
+ else {
+ n = clay.height;
+ if(n > h)
+ h = n;
+ }
+ }
+ row.height = h;
+ row.ascent = a;
+ }
+ # second pass: take care of rowspan > 1
+ # (this algorithm isn't quite right -- it might add more space
+ # than is needed in the presence of multiple overlapping rowspans)
+ for(cl = tab.cells; cl != nil; cl = tl cl) {
+ c := hd cl;
+ if(c.rowspan > 1) {
+ spanht := 0;
+ for(i := 0; i < c.rowspan && c.row+i < tab.nrow; i++)
+ spanht += tab.rows[c.row+i].height;
+ if(c.layid < 0)
+ continue;
+ clay := f.sublays[c.layid];
+ ht := clay.height - (c.rowspan-1)*vsep;
+ if(ht > spanht) {
+ # add extra space to last spanned row
+ i = c.row+c.rowspan-1;
+ if(i >= tab.nrow)
+ i = tab.nrow - 1;
+ tab.rows[i].height += ht - spanht;
+ if(dbgtab)
+ sys->print("sizetable %d, row %d height %d\n", tab.tableid, i, tab.rows[i].height);
+ }
+ }
+ }
+ # get total width, heights, and col x / row y positions
+ totw = bd + hsp + cbd + pad;
+ for(ci = 0; ci < tab.ncol; ci++) {
+ tab.cols[ci].pos.x = totw;
+ if(dbgtab)
+ sys->print("sizetable %d, col %d at x=%d\n", tab.tableid, ci, totw);
+ totw += tab.cols[ci].width + hsep;
+ }
+ totw = totw - (cbd+pad) + bd;
+ toth := bd + vsp + cbd + pad;
+ # first time: move tab.caption items into layout
+ if(tab.caption != nil) {
+ # lay caption with Aleft; drawing will center it over the table width
+ tab.caption_lay = sublayout(f, availwidth, Aleft, f.layout.background, tab.caption);
+ caplay := f.sublays[tab.caption_lay];
+ tab.caph = caplay.height + CAPSEP;
+ tab.caption = nil;
+ }
+ else if(tab.caption_lay >= 0) {
+ caplay := f.sublays[tab.caption_lay];
+ if(tab.availw != availwidth || (caplay.flags&Lchanged) != byte 0) {
+ relayout(f, caplay, availwidth, Aleft);
+ tab.caph = caplay.height + CAPSEP;
+ }
+ }
+ if(tab.caption_place == Atop)
+ toth += tab.caph;
+ for(ri = 0; ri < tab.nrow; ri++) {
+ tab.rows[ri].pos.y = toth;
+ if(dbgtab)
+ sys->print("sizetable %d, row %d at y=%d\n", tab.tableid, ri, toth);
+ toth += tab.rows[ri].height + vsep;
+ }
+ toth = toth - (cbd+pad) + bd;
+ if(tab.caption_place == Abottom)
+ toth += tab.caph;
+ tab.totw = totw;
+ tab.toth = toth;
+ tab.availw = availwidth;
+ tab.flags &= ~Lchanged;
+ if(dbgtab)
+ sys->print("\ndone sizetable %d, availwidth %d, totw=%d, toth=%d\n\n",
+ tab.tableid, availwidth, totw, toth);
+}
+
+# Calculate various table spacing parameters
+tableparams(tab: ref Table) : (int, int, int, int, int, int, int)
+{
+ bd := tab.border;
+ hsp := tab.cellspacing;
+ vsp := hsp;
+ pad := tab.cellpadding;
+ if(bd != 0)
+ cbd := 1;
+ else
+ cbd = 0;
+ hsep := 2*(cbd+pad)+hsp;
+ vsep := 2*(cbd+pad)+vsp;
+ return (hsp, vsp, pad, bd, cbd, hsep, vsep);
+}
+
+# return cell width, taking multicol spanning into account
+cellwidth(tab: ref Table, c: ref Tablecell, hsep: int) : int
+{
+ if(c.colspan == 1)
+ return tab.cols[c.col].width;
+ wd := (c.colspan-1)*hsep;
+ for(i := 0; i < c.colspan && c.col + i < tab.ncol; i++)
+ wd += tab.cols[c.col + i].width;
+ return wd;
+}
+
+# return cell height, taking multirow spanning into account
+cellheight(tab: ref Table, c: ref Tablecell, vsep: int) : int
+{
+ if(c.rowspan == 1)
+ return tab.rows[c.row].height;
+ ht := (c.rowspan-1)*vsep;
+ for(i := 0; i < c.rowspan && c.row + i < tab.nrow; i++)
+ ht += tab.rows[c.row + i].height;
+ return ht;
+}
+
+# Calculate the column widths w as the max of the cells
+# maxw or minw (as domax is 1 or 0).
+# Return the total of all w.
+# (hseps were accounted for by the adjustment that got
+# totw from availwidth).
+# hsep is amount of free space available between columns
+# where there is multicolumn spanning.
+# This is a two-pass algorithm. The first pass ignores
+# cells that span multiple columns. The second pass
+# sees if those multispanners need still more space, and
+# if so, apportions the space out.
+widthcalc(tab: ref Table, w: array of int, hsep, domax: int) : int
+{
+ anyspan := 0;
+ totw := 0;
+ for(pass := 1; pass <= 2; pass++) {
+ if(pass==2 && !anyspan)
+ break;
+ totw = 0;
+ for(ci := 0; ci < tab.ncol; ci++) {
+ for(ri := 0; ri < tab.nrow; ri++) {
+ c := tab.grid[ri][ci];
+ if(c == nil)
+ continue;
+ if(domax)
+ cwd := c.maxw;
+ else
+ cwd = c.minw;
+ if(pass == 1) {
+ if(c.colspan > 1) {
+ anyspan = 1;
+ continue;
+ }
+ if(cwd > w[ci])
+ w[ci] = cwd;
+ }
+ else {
+ if(c.colspan == 1 || !(ci==c.col && ri==c.row))
+ continue;
+ curw := 0;
+ iend := ci+c.colspan;
+ if(iend > tab.ncol)
+ iend = tab.ncol;
+ for(i:=ci; i < iend; i++)
+ curw += w[i];
+
+ # padding between spanned cols is free
+ cwd -= hsep*(c.colspan-1);
+ diff := cwd-curw;
+ if(diff <= 0)
+ continue;
+ # doesn't fit: apportion diff among cols
+ # in proportion to their current w
+ for(i = ci; i < iend; i++) {
+ if(curw == 0)
+ w[i] = diff/c.colspan;
+ else
+ w[i] += diff*w[i]/curw;
+ }
+ }
+ }
+ totw += w[ci];
+ }
+ }
+ return totw;
+}
+
+layframeset(f: ref Frame, ki: ref Kidinfo)
+{
+ fwid := f.cr.dx();
+ fht := f.cr.dy();
+ if(dbg)
+ sys->print("layframeset, configuring frame %d wide by %d high\n", fwid, fht);
+ (nrow, rowh) := frdimens(ki.rows, fht);
+ (ncol, colw) := frdimens(ki.cols, fwid);
+ l := ki.kidinfos;
+ y := f.cr.min.y;
+ for(i := 0; i < nrow; i++) {
+ x := f.cr.min.x;
+ for(j := 0; j < ncol; j++) {
+ if(l == nil)
+ return;
+ r := Rect(Point(x,y), Point(x+colw[j],y+rowh[i]));
+ if(dbg)
+ sys->print("kid gets rect (%d,%d)(%d,%d)\n", r.min.x, r.min.y, r.max.x, r.max.y);
+ kidki := hd l;
+ l = tl l;
+ kidf := Frame.newkid(f, kidki, r);
+ if(!kidki.isframeset)
+ f.kids = kidf :: f.kids;
+ if(kidf.framebd != 0) {
+ kidf.cr = kidf.r.inset(2);
+ drawborder(kidf.cim, kidf.cr, 2, DarkGrey);
+ }
+ if(kidki.isframeset) {
+ layframeset(kidf, kidki);
+ for(al := kidf.kids; al != nil; al = tl al)
+ f.kids = (hd al) :: f.kids;
+ }
+ x += colw[j];
+ }
+ y += rowh[i];
+ }
+}
+
+# Use the dimension specs in dims to allocate total space t.
+# Return (number of dimens, array of allocated space)
+frdimens(dims: array of B->Dimen, t: int): (int, array of int)
+{
+ n := len dims;
+ if(n == 1)
+ return (1, array[] of {t});
+ totpix := 0;
+ totpcnt := 0;
+ totrel := 0;
+ for(i := 0; i < n; i++) {
+ v := dims[i].spec();
+ kind := dims[i].kind();
+ if(v < 0) {
+ v = 0;
+ dims[i] = Dimen.make(kind, v);
+ }
+ case kind {
+ B->Dpixels => totpix += v;
+ B->Dpercent => totpcnt += v;
+ B->Drelative => totrel += v;
+ B->Dnone => totrel++;
+ }
+ }
+ spix := 1.0;
+ spcnt := 1.0;
+ min_relu := 0;
+ if(totrel > 0)
+ min_relu = 30; # allow for scrollbar (14) and a bit
+ relu := real min_relu;
+ tt := totpix + (t*totpcnt/100) + totrel*min_relu;
+ # want
+ # t == totpix*spix + (totpcnt/100)*spcnt*t + totrel*relu
+ if(tt < t) {
+ # need to expand one of spix, spcnt, relu
+ if(totrel == 0) {
+ if(totpcnt != 0)
+ # spix==1.0, relu==0, solve for spcnt
+ spcnt = real ((t-totpix) * 100)/ real (t*totpcnt);
+ else
+ # relu==0, totpcnt==0, solve for spix
+ spix = real t/ real totpix;
+ }
+ else
+ # spix=1.0, spcnt=1.0, solve for relu
+ relu += real (t-tt)/ real totrel;
+ }
+ else {
+ # need to contract one or more of spix, spcnt, and have relu==min_relu
+ totpixrel := totpix+totrel*min_relu;
+ if(totpixrel < t) {
+ # spix==1.0, solve for spcnt
+ spcnt = real ((t-totpixrel) * 100)/ real (t*totpcnt);
+ }
+ else {
+ # let spix==spcnt, solve
+ trest := t - totrel*min_relu;
+ if(trest > 0) {
+ spcnt = real trest/real (totpix+(t*totpcnt/100));
+ }
+ else {
+ spcnt = real t / real tt;
+ relu = 0.0;
+ }
+ spix = spcnt;
+ }
+ }
+ x := array[n] of int;
+ tt = 0;
+ for(i = 0; i < n-1; i++) {
+ vr := real dims[i].spec();
+ case dims[i].kind() {
+ B->Dpixels => vr = vr * spix;
+ B->Dpercent => vr = vr * real t * spcnt / 100.0;
+ B->Drelative => vr = vr * relu;
+ B->Dnone => vr = relu;
+ }
+ x[i] = int vr;
+ tt += x[i];
+ }
+ x[n-1] = t - tt;
+ return (n, x);
+}
+
+# Return last item of list of items, or nil if no items
+lastitem(it: ref Item) : ref Item
+{
+ ans : ref Item = it;
+ for( ; it != nil; it = it.next)
+ ans = it;
+ return ans;
+}
+
+# Lay out table if availw changed or tab changed
+checktabsize(f: ref Frame, t: ref Item.Itable, availw: int)
+{
+ tab := t.table;
+ if (dbgtab)
+ sys->print("checktabsize %d, availw %d, tab.availw %d, changed %d\n", tab.tableid, availw, tab.availw, (tab.flags&Lchanged)>byte 0);
+ if(availw != tab.availw || int (tab.flags&Lchanged)) {
+ sizetable(f, tab, availw);
+ t.width = tab.totw + 2*tab.border;
+ t.height = tab.toth + 2*tab.border;
+ t.ascent = t.height;
+ }
+}
+
+widthfromspec(wspec: Dimen, availw: int) : int
+{
+ w := availw;
+ spec := wspec.spec();
+ case wspec.kind() {
+ Dpixels => w = spec;
+ Dpercent => w = spec*w/100;
+ }
+ return w;
+}
+
+# An image may have arrived for an image input field
+checkffsize(f: ref Frame, i: ref Item, ff: ref Formfield)
+{
+ if(ff.ftype == Fimage && ff.image != nil) {
+ pick imi := ff.image {
+ Iimage =>
+ if(imi.ci.mims != nil && ff.ctlid >= 0) {
+ pick b := f.controls[ff.ctlid] {
+ Cbutton =>
+ if(b.pic == nil) {
+ b.pic = imi.ci.mims[0].im;
+ b.picmask = imi.ci.mims[0].mask;
+ w := b.pic.r.dx();
+ h := b.pic.r.dy();
+ b.r.max.x = b.r.min.x + w;
+ b.r.max.y = b.r.min.y + h;
+ i.width = w;
+ i.height = h;
+ i.ascent = h;
+ }
+ }
+ }
+ }
+ }
+ else if(ff.ftype == Fselect) {
+ opts := ff.options;
+ if(ff.ctlid >=0) {
+ pick c := f.controls[ff.ctlid] {
+ Cselect =>
+ if(len opts != len c.options) {
+ nc := Control.newff(f, ff);
+ f.controls[ff.ctlid] = nc;
+ i.width = nc.r.dx();
+ i.height = nc.r.dy();
+ i.ascent = lineascent + SELMARGIN;
+ }
+ }
+ }
+ }
+}
+
+drawall(f: ref Frame)
+{
+ oclipr := f.cim.clipr;
+ origin := f.lptosp(zp);
+ clipr := f.dirtyr.addpt(origin);
+ f.cim.clipr = clipr;
+ fillbg(f, clipr);
+ if(dbg > 1)
+ sys->print("drawall, cr=(%d,%d,%d,%d), viewr=(%d,%d,%d,%d), origin=(%d,%d)\n",
+ f.cr.min.x, f.cr.min.y, f.cr.max.x, f.cr.max.y,
+ f.viewr.min.x, f.viewr.min.y, f.viewr.max.x, f.viewr.max.y,
+ origin.x, origin.y);
+ if(f.layout != nil)
+ drawlay(f, f.layout, origin);
+ f.cim.clipr = oclipr;
+ G->flush(f.cr);
+ f.isdirty = 0;
+}
+
+drawlay(f: ref Frame, lay: ref Lay, origin: Point)
+{
+ for(l := lay.start.next; l != lay.end; l = l.next)
+ drawline(f, origin, l, lay);
+}
+
+# Draw line l in frame f, assuming that content's (0,0)
+# aligns with layorigin in f.cim.
+drawline(f : ref Frame, layorigin : Point, l: ref Line, lay: ref Lay)
+{
+ im := f.cim;
+ o := layorigin.add(l.pos);
+ x := o.x;
+ y := o.y;
+ lr := Rect(zp, Point(l.width, l.height)).addpt(o);
+ isdirty := f.isdirty && lr.Xrect(f.dirtyr.addpt(f.lptosp(zp)));
+ inview := lr.Xrect(f.cr) && isdirty;
+
+ # note: drawimg must always be called to update
+ # draw point of animated images
+ for(it := l.items; it != nil; it = it.next) {
+ pick i := it {
+ Itext =>
+ if (!inview || i.s == nil)
+ break;
+ fnt := fonts[i.fnt];
+ width := i.width;
+ yy := y+l.ascent - fnt.f.ascent + (int i.voff) - Voffbias;
+ if (f.prctxt != nil) {
+ if (yy < f.cr.min.y)
+ continue;
+ endy := yy + fnt.f.height;
+ if (endy > f.cr.max.y) {
+ # do not draw
+ if (yy < f.prctxt.endy)
+ f.prctxt.endy = yy;
+ continue;
+ }
+ }
+ fgi := colorimage(i.fg);
+ im.text(Point(x, yy), fgi, zp, fnt.f, i.s);
+ if(i.ul != ULnone) {
+ if(i.ul == ULmid)
+ yy += 2*i.ascent/3;
+ else
+ yy += i.height - 1;
+ # don't underline leading space
+ # have already adjusted x pos in fixlinegeom()
+ ulx := x;
+ ulw := width;
+ if (i.s[0] == ' ') {
+ ulx += fnt.spw;
+ ulw -= fnt.spw;
+ }
+ if (i.s[len i.s - 1] == ' ')
+ ulw -= fnt.spw;
+ if (ulw < 1)
+ continue;
+ im.drawop(Rect(Point(ulx,yy),Point(ulx+ulw,yy+1)), fgi, nil, zp, Draw->S);
+ }
+ Irule =>
+ if (!inview)
+ break;
+ yy := y + RULESP;
+ im.draw(Rect(Point(x,yy),Point(x+i.width,yy+i.size)),
+ display.black, nil, zp);
+ Iimage =>
+ yy := y;
+ if(i.align == Abottom)
+ # bottom aligns with baseline
+ yy += l.ascent - i.imheight;
+ else if(i.align == Amiddle)
+ yy += l.ascent - (i.imheight/2);
+ drawimg(f, Point(x,yy), i);
+ Iformfield =>
+ ff := i.formfield;
+ if(ff.ctlid >= 0 && ff.ctlid < len f.controls) {
+ ctl := f.controls[ff.ctlid];
+ dims := ctl.r.max.sub(ctl.r.min);
+ # align as text
+ yy := y + l.ascent - i.ascent;
+ p := Point(x,yy);
+ ctl.r = Rect(p, p.add(dims));
+ if (!inview)
+ break;
+ if (f.prctxt != nil) {
+ if (yy < f.cr.min.y)
+ continue;
+ if (ctl.r.max.y > f.cr.max.y) {
+ # do not draw
+ if (yy < f.prctxt.endy)
+ f.prctxt.endy = yy;
+ continue;
+ }
+ }
+ ctl.draw(0);
+ }
+ Itable =>
+ # don't check inview - table can contain images
+ drawtable(f, lay, Point(x,y), i.table);
+ t := i.table;
+ Ifloat =>
+ xx := layorigin.x + lay.margin;
+ if(i.side == Aright) {
+ xx -= i.x;
+# # for main layout of frame, floats hug
+# # right edge of frame, not layout
+# # (other browsers do that)
+# if(f.layout == lay)
+ xx += lay.targetwidth;
+# else
+# xx += lay.width;
+ }
+ else
+ xx += i.x;
+ pick fi := i.item {
+ Iimage =>
+ drawimg(f, Point(xx, layorigin.y + i.y + (int fi.border + int fi.vspace)), fi);
+ Itable =>
+ drawtable(f, lay, Point(xx, layorigin.y + i.y), fi.table);
+ }
+ }
+ x += it.width;
+ }
+}
+
+drawimg(f: ref Frame, iorigin: Point, i: ref Item.Iimage)
+{
+ ci := i.ci;
+ im := f.cim;
+ iorigin.x += int i.hspace + int i.border;
+ # y coord is already adjusted for border and vspace
+ if(ci.mims != nil) {
+ r := Rect(iorigin, iorigin.add(Point(i.imwidth,i.imheight)));
+ inview := r.Xrect(f.cr);
+ if(i.ctlid >= 0) {
+ # animated
+ c := f.controls[i.ctlid];
+ dims := c.r.max.sub(c.r.min);
+ c.r = Rect(iorigin, iorigin.add(dims));
+ if (inview) {
+ pick ac := c {
+ Canimimage =>
+ ac.redraw = 1;
+ ac.bg = f.layout.background;
+ }
+ c.draw(0);
+ }
+ }
+ else if (inview) {
+ mim := ci.mims[0];
+ iorigin = iorigin.add(mim.origin);
+ im.draw(r, mim.im, mim.mask, zp);
+ }
+ if(inview && i.border != byte 0) {
+ if(i.anchorid != 0)
+ bdcol := f.doc.link;
+ else
+ bdcol = Black;
+ drawborder(im, r, int i.border, bdcol);
+ }
+ }
+ else if((CU->config).imagelvl == CU->ImgNone && i.altrep != "") {
+ fnt := fonts[DefFnt].f;
+ yy := iorigin.y+(i.imheight-fnt.height)/2;
+ xx := iorigin.x + (i.width-fnt.width(i.altrep))/2;
+ if(i.anchorid != 0)
+ col := f.doc.link;
+ else
+ col = DarkGrey;
+ fgi := colorimage(col);
+ im.text(Point(xx, yy), fgi, zp, fnt, i.altrep);
+ }
+}
+
+drawtable(f : ref Frame, parentlay: ref Lay, torigin: Point, tab: ref Table)
+{
+ if (dbgtab)
+ sys->print("drawtable %d\n", tab.tableid);
+ if(tab.ncol == 0 || tab.nrow == 0)
+ return;
+ im := f.cim;
+ (hsp, vsp, pad, bd, cbd, hsep, vsep) := tableparams(tab);
+ x := torigin.x;
+ y := torigin.y;
+ capy := y;
+ boxy := y;
+ if(tab.caption_place == Abottom)
+ capy = y+tab.toth-tab.caph+vsp;
+ else
+ boxy = y+tab.caph;
+ if (tab.background.color != -1 && tab.background.color != parentlay.background.color) {
+# if(tab.background.image != parentlay.background.image ||
+# tab.background.color != parentlay.background.color) {
+ bgi := colorimage(tab.background.color);
+ im.draw(((x,boxy),(x+tab.totw,boxy+tab.toth-tab.caph)),
+ bgi, nil, zp);
+ }
+ if(bd != 0)
+ drawborder(im, ((x+bd,boxy+bd),(x+tab.totw-bd,boxy+tab.toth-tab.caph-bd)),
+ 1, Black);
+ for(cl := tab.cells; cl != nil; cl = tl cl) {
+ c := hd cl;
+ if (c.layid == -1 || c.layid >= len f.sublays) {
+ # for some reason (usually scrolling)
+ # we are drawing this cell before it has been layed out
+ continue;
+ }
+ clay := f.sublays[c.layid];
+ if(clay == nil)
+ continue;
+ cx := x + tab.cols[c.col].pos.x;
+ cy := y + tab.rows[c.row].pos.y;
+ wd := cellwidth(tab, c, hsep);
+ ht := cellheight(tab, c, vsep);
+ if(c.background.image != nil && c.background.image.ci != nil && c.background.image.ci.mims != nil) {
+ cellr := Rect((cx-pad,cy-pad),(cx+wd+pad,cy+ht+pad));
+ ci := c.background.image.ci;
+ bgi := ci.mims[0].im;
+ bgmask := ci.mims[0].mask;
+ im.draw(cellr, bgi, bgmask, bgi.r.min);
+ } else if(c.background.color != -1 && c.background.color != tab.background.color) {
+ bgi := colorimage(c.background.color);
+ im.draw(((cx-pad,cy-pad),(cx+wd+pad,cy+ht+pad)),
+ bgi, nil, zp);
+ }
+ if(bd != 0)
+ drawborder(im, ((cx-pad+1,cy-pad+1),(cx+wd+pad-1,cy+ht+pad-1)),
+ 1, Black);
+ if(c.align.valign != Atop && c.align.valign != Abaseline) {
+ n := ht - clay.height;
+ if(c.align.valign == Amiddle)
+ cy += n/2;
+ else if(c.align.valign == Abottom)
+ cy += n;
+ }
+ if(dbgtab)
+ sys->print("drawtable %d cell %d at (%d,%d)\n",
+ tab.tableid, c.cellid, cx, cy);
+ drawlay(f, clay, Point(cx,cy));
+ }
+ if(tab.caption_lay >= 0) {
+ caplay := f.sublays[tab.caption_lay];
+ capx := x;
+ if(caplay.width < tab.totw)
+ capx += (tab.totw-caplay.width) / 2;
+ drawlay(f, caplay, Point(capx,capy));
+ }
+}
+
+# Draw border of width n just outside r, using src color
+drawborder(im: ref Image, r: Rect, n, color: int)
+{
+ x := r.min.x-n;
+ y := r.min.y - n;
+ xr := r.max.x+n;
+ ybi := r.max.y;
+ src := colorimage(color);
+ im.draw((Point(x,y),Point(xr,y+n)), src, nil, zp); # top
+ im.draw((Point(x,ybi),Point(xr,ybi+n)), src, nil, zp); # bottom
+ im.draw((Point(x,y+n),Point(x+n,ybi)), src, nil, zp); # left
+ im.draw((Point(xr-n,y+n),Point(xr,ybi)), src, nil, zp); # right
+}
+
+# Draw relief border just outside r, width 2 border,
+# colors white/lightgrey/darkgrey/black
+# to give raised relief (if raised != 0) or sunken.
+drawrelief(im: ref Image, r: Rect, raised: int)
+{
+ # ((x,y),(xr,yb)) == r
+ x := r.min.x;
+ x1 := x-1;
+ x2 := x-2;
+ xr := r.max.x;
+ xr1 := xr+1;
+ xr2 := xr+2;
+ y := r.min.y;
+ y1 := y-1;
+ y2 := y-2;
+ yb := r.max.y;
+ yb1 := yb+1;
+ yb2 := yb+2;
+
+ # colors for top/left outside, top/left inside, bottom/right outside, bottom/right inside
+ tlo, tli, bro, bri: ref Image;
+ if(raised) {
+ tlo = colorimage(Grey);
+ tli = colorimage(White);
+ bro = colorimage(Black);
+ bri = colorimage(DarkGrey);
+ }
+ else {
+ tlo = colorimage(DarkGrey);
+ tli = colorimage(Black);
+ bro = colorimage(White);
+ bri = colorimage(Grey);
+ }
+
+ im.draw((Point(x2,y2), Point(xr1,y1)), tlo, nil, zp); # top outside
+ im.draw((Point(x1,y1), Point(xr,y)), tli, nil, zp); # top inside
+ im.draw((Point(x2,y1), Point(x1,yb1)), tlo, nil, zp); # left outside
+ im.draw((Point(x1,y), Point(x,yb)), tli, nil, zp); # left inside
+ im.draw((Point(xr,y1),Point(xr1,yb)), bri, nil, zp); # right inside
+ im.draw((Point(xr1,y),Point(xr2,yb1)), bro, nil, zp); # right outside
+ im.draw((Point(x1,yb),Point(xr1,yb1)), bri, nil, zp); # bottom inside
+ im.draw((Point(x,yb1),Point(xr2,yb2)), bro, nil, zp); # bottom outside
+}
+
+# Fill r with color
+drawfill(im: ref Image, r: Rect, color: int)
+{
+ im.draw(r, colorimage(color), nil, zp);
+}
+
+# Draw string in default font at p
+drawstring(im: ref Image, p: Point, s: string)
+{
+ im.text(p, colorimage(Black), zp, fonts[DefFnt].f, s);
+}
+
+# Return (width, height) of string in default font
+measurestring(s: string) : Point
+{
+ f := fonts[DefFnt].f;
+ return (f.width(s), f.height);
+}
+
+# Mark as "changed" everything with change flags on the loc path
+markchanges(loc: ref Loc)
+{
+ lastf : ref Frame = nil;
+ for(i := 0; i < loc.n; i++) {
+ case loc.le[i].kind {
+ LEframe =>
+ lastf = loc.le[i].frame;
+ lastf.layout.flags |= Lchanged;
+ LEline =>
+ loc.le[i].line.flags |= Lchanged;
+ LEitem =>
+ pick it := loc.le[i].item {
+ Itable =>
+ it.table.flags |= Lchanged;
+ Ifloat =>
+ # whole layout will be redone if layout changes
+ # and there are any floats
+ ;
+ }
+ LEtablecell =>
+ if(lastf == nil)
+ CU->raisex("EXInternal: markchanges no lastf");
+ c := loc.le[i].tcell;
+ clay := lastf.sublays[c.layid];
+ if(clay != nil)
+ clay.flags |= Lchanged;
+ }
+ }
+}
+
+# one-item cache for colorimage
+prevrgb := -1;
+prevrgbimage : ref Image = nil;
+
+colorimage(rgb: int) : ref Image
+{
+ if(rgb == prevrgb)
+ return prevrgbimage;
+ prevrgb = rgb;
+ if(rgb == Black)
+ prevrgbimage = display.black;
+ else if(rgb == White)
+ prevrgbimage = display.white;
+ else {
+ hv := rgb % NCOLHASH;
+ if (hv < 0)
+ hv = -hv;
+ xhd := colorhashtab[hv];
+ x := xhd;
+ while(x != nil && x.rgb != rgb)
+ x = x.next;
+ if(x == nil) {
+# pix := I->closest_rgbpix((rgb>>16)&255, (rgb>>8)&255, rgb&255);
+# im := display.color(pix);
+ im := display.rgb((rgb>>16)&255, (rgb>>8)&255, rgb&255);
+ if(im == nil)
+ CU->raisex(sys->sprint("exLayout: can't allocate color #%8.8ux: %r", rgb));
+ x = ref Colornode(rgb, im, xhd);
+ colorhashtab[hv] = x;
+ }
+ prevrgbimage = x.im;
+ }
+ return prevrgbimage;
+}
+
+# Use f.background.image (if not nil) or f.background.color to fill r (in cim coord system)
+# with background color.
+fillbg(f: ref Frame, r: Rect)
+{
+ bgi: ref Image;
+ ii := f.doc.background.image;
+ if (ii != nil && ii.ci != nil && ii.ci.mims != nil)
+ bgi = ii.ci.mims[0].im;
+ if(bgi == nil)
+ bgi = colorimage(f.doc.background.color);
+ f.cim.drawop(r, bgi, nil, f.viewr.min, Draw->S);
+}
+
+TRIup, TRIdown, TRIleft, TRIright: con iota;
+# Assume r is a square
+drawtriangle(im: ref Image, r: Rect, kind, style: int)
+{
+ drawfill(im, r, Grey);
+ b := r.max.x - r.min.x;
+ if(b < 4)
+ return;
+ b2 := b/2;
+ bm2 := b-ReliefBd;
+ p := array[3] of Point;
+ col012, col20 : ref Image;
+ d := colorimage(DarkGrey);
+ l := colorimage(White);
+ case kind {
+ TRIup =>
+ p[0] = Point(b2, ReliefBd);
+ p[1] = Point(bm2,bm2);
+ p[2] = Point(ReliefBd,bm2);
+ col012 = d;
+ col20 = l;
+ TRIdown =>
+ p[0] = Point(b2,bm2);
+ p[1] = Point(ReliefBd,ReliefBd);
+ p[2] = Point(bm2,ReliefBd);
+ col012 = l;
+ col20 = d;
+ TRIleft =>
+ p[0] = Point(bm2, ReliefBd);
+ p[1] = Point(bm2, bm2);
+ p[2] = Point(ReliefBd,b2);
+ col012 = d;
+ col20 = l;
+ TRIright =>
+ p[0] = Point(ReliefBd,bm2);
+ p[1] = Point(ReliefBd,ReliefBd);
+ p[2] = Point(bm2,b2);
+ col012 = l;
+ col20 = d;
+ }
+ if(style == ReliefSunk) {
+ t := col012;
+ col012 = col20;
+ col20 = t;
+ }
+ for(i := 0; i < 3; i++)
+ p[i] = p[i].add(r.min);
+ im.fillpoly(p, ~0, colorimage(Grey), zp);
+ im.line(p[0], p[1], 0, 0, ReliefBd/2, col012, zp);
+ im.line(p[1], p[2], 0, 0, ReliefBd/2, col012, zp);
+ im.line(p[2], p[0], 0, 0, ReliefBd/2, col20, zp);
+}
+
+abs(a: int) : int
+{
+ if(a < 0)
+ return -a;
+ return a;
+}
+
+Frame.new() : ref Frame
+{
+ f := ref Frame;
+ f.parent = nil;
+ f.cim = nil;
+ f.r = Rect(zp, zp);
+ f.animpid = 0;
+ f.reset();
+ return f;
+}
+
+Frame.newkid(parent: ref Frame, ki: ref Kidinfo, r: Rect) : ref Frame
+{
+ f := ref Frame;
+ f.parent = parent;
+ f.cim = parent.cim;
+ f.r = r;
+ f.animpid = 0;
+ f.reset();
+ f.src = ki.src;
+ f.name = ki.name;
+ f.marginw = ki.marginw;
+ f.marginh = ki.marginh;
+ f.framebd = ki.framebd;
+ f.flags = ki.flags;
+ return f;
+}
+
+# Note: f.parent, f.cim and f.r should not be reset
+# And if f.parent is true, don't reset params set in frameset.
+Frame.reset(f: self ref Frame)
+{
+ f.id = ++frameid;
+ f.doc = nil;
+ if(f.parent == nil) {
+ f.src = nil;
+ f.name = "";
+ f.marginw = FRMARGIN;
+ f.marginh = FRMARGIN;
+ f.framebd = 0;
+ f.flags = FRvscrollauto | FRhscrollauto;
+ }
+ f.layout = nil;
+ f.sublays = nil;
+ f.sublayid = 0;
+ f.controls = nil;
+ f.controlid = 0;
+ f.cr = f.r;
+ f.isdirty = 1;
+ f.dirtyr = f.cr;
+ f.viewr = Rect(zp, zp);
+ f.totalr = f.viewr;
+ f.vscr = nil;
+ f.hscr = nil;
+ hadkids := (f.kids != nil);
+ f.kids = nil;
+ if(f.animpid != 0)
+ CU->kill(f.animpid, 0);
+ if(J != nil && hadkids)
+ J->frametreechanged(f);
+ f.animpid = 0;
+}
+
+Frame.dirty(f: self ref Frame, r: Draw->Rect)
+{
+ if (f.isdirty)
+ f.dirtyr= f.dirtyr.combine(r);
+ else {
+ f.dirtyr = r;
+ f.isdirty = 1;
+ }
+}
+
+Frame.addcontrol(f: self ref Frame, c: ref Control) : int
+{
+ if(len f.controls <= f.controlid) {
+ newcontrols := array[len f.controls + 30] of ref Control;
+ newcontrols[0:] = f.controls;
+ f.controls = newcontrols;
+ }
+ f.controls[f.controlid] = c;
+ ans := f.controlid++;
+ return ans;
+}
+
+Frame.xscroll(f: self ref Frame, kind, val: int)
+{
+ newx := f.viewr.min.x;
+ case kind {
+ CAscrollpage =>
+ newx += val*(f.cr.dx()*8/10);
+ CAscrollline =>
+ newx += val*f.cr.dx()/10;
+ CAscrolldelta =>
+ newx += val;
+ CAscrollabs =>
+ newx = val;
+ }
+ f.scrollabs(Point(newx, f.viewr.min.y));
+}
+
+# Don't actually scroll by "page" and "line",
+# But rather, 80% and 10%, which give more
+# context in the first case, and more motion
+# in the second.
+Frame.yscroll(f: self ref Frame, kind, val: int)
+{
+ newy := f.viewr.min.y;
+ case kind {
+ CAscrollpage =>
+ newy += val*(f.cr.dy()*8/10);
+ CAscrollline =>
+ newy += val*f.cr.dy()/20;
+ CAscrolldelta =>
+ newy += val;
+ CAscrollabs =>
+ newy = val;
+ }
+ f.scrollabs(Point(f.viewr.min.x, newy));
+}
+
+Frame.scrollrel(f : self ref Frame, p : Point)
+{
+ (x, y) := p;
+ x += f.viewr.min.x;
+ y += f.viewr.min.y;
+ f.scrollabs(f.viewr.min.add(p));
+}
+
+Frame.scrollabs(f : self ref Frame, p : Point)
+{
+ (x, y) := p;
+ lay := f.layout;
+ margin := 0;
+ if (lay != nil)
+ margin = lay.margin;
+ x = max(0, min(x, f.totalr.max.x));
+ y = max(0, min(y, f.totalr.max.y + margin - f.cr.dy()));
+ (oldx, oldy) := f.viewr.min;
+ if (oldx != x || oldy != y) {
+ f.viewr.min = (x, y);
+ fixframegeom(f);
+ # blit scroll
+ dx := f.viewr.min.x - oldx;
+ dy := f.viewr.min.y - oldy;
+ origin := f.lptosp(zp);
+ destr := f.viewr.addpt(origin);
+ srcpt := destr.min.add((dx, dy));
+ oclipr := f.cim.clipr;
+ f.cim.clipr = f.cr;
+ f.cim.drawop(destr, f.cim, nil, srcpt, Draw->S);
+ if (dx > 0)
+ f.dirty(Rect((f.viewr.max.x - dx, f.viewr.min.y), f.viewr.max));
+ else if (dx < 0)
+ f.dirty(Rect(f.viewr.min, (f.viewr.min.x - dx, f.viewr.max.y)));
+
+ if (dy > 0)
+ f.dirty(Rect((f.viewr.min.x, f.viewr.max.y-dy), f.viewr.max));
+ else if (dy < 0)
+ f.dirty(Rect(f.viewr.min, (f.viewr.max.x, f.viewr.min.y-dy)));
+#f.cim.draw(destr, display.white, nil, zp);
+ drawall(f);
+ f.cim.clipr = oclipr;
+ }
+}
+
+# Convert layout coords (where (0,0) is top left of layout)
+# to screen coords (i.e., coord system of mouse, f.cr, etc.)
+Frame.sptolp(f: self ref Frame, sp: Point) : Point
+{
+ return f.viewr.min.add(sp.sub(f.cr.min));
+}
+
+# Reverse translation of sptolp
+Frame.lptosp(f: self ref Frame, lp: Point) : Point
+{
+ return lp.add(f.cr.min.sub(f.viewr.min));
+}
+
+# Return Loc of Item or Scrollbar containing p (p in screen coords)
+# or item it, if that is not nil.
+Frame.find(f: self ref Frame, p: Point, it: ref Item) : ref Loc
+{
+ return framefind(Loc.new(), f, p, it);
+}
+
+# Find it (if non-nil) or place where p is (known to be inside f's layout).
+framefind(loc: ref Loc, f: ref Frame, p: Point, it: ref Item) : ref Loc
+{
+ loc.add(LEframe, f.r.min);
+ loc.le[loc.n-1].frame = f;
+ if(it == nil) {
+ if(f.vscr != nil && p.in(f.vscr.r)) {
+ loc.add(LEcontrol, f.vscr.r.min);
+ loc.le[loc.n-1].control = f.vscr;
+ loc.pos = p.sub(f.vscr.r.min);
+ return loc;
+ }
+ if(f.hscr != nil && p.in(f.hscr.r)) {
+ loc.add(LEcontrol, f.hscr.r.min);
+ loc.le[loc.n-1].control = f.hscr;
+ loc.pos = p.sub(f.hscr.r.min);
+ return loc;
+ }
+ }
+ if(it != nil || p.in(f.cr)) {
+ lay := f.layout;
+ if(f.kids != nil) {
+ for(fl := f.kids; fl != nil; fl = tl fl) {
+ kf := hd fl;
+ try := framefind(loc, kf, p, it);
+ if(try != nil)
+ return try;
+ }
+ }
+ else if(lay != nil)
+ return layfind(loc, f, lay, f.lptosp(zp), p, it);
+ }
+ return nil;
+}
+
+# Find it (if non-nil) or place where p is (known to be inside f's layout).
+# p (in screen coords), lay offset by origin also in screen coords
+layfind(loc: ref Loc, f: ref Frame, lay: ref Lay, origin, p: Point, it: ref Item) : ref Loc
+{
+ for(flist := lay.floats; flist != nil; flist = tl flist) {
+ fl := hd flist;
+ fymin := fl.y+origin.y;
+ fymax := fymin + fl.item.height;
+ inside := 0;
+ xx : int;
+ if(it != nil || (fymin <= p.y && p.y < fymax)) {
+ xx = origin.x + lay.margin;
+ if(fl.side == Aright) {
+ xx -= fl.x;
+ xx += lay.targetwidth;
+# if(lay == f.layout)
+# xx = origin.x + (f.cr.dx() - lay.margin) - fl.x;
+## xx += f.cr.dx() - fl.x;
+# else
+# xx += lay.width - fl.x;
+ }
+ else
+ xx += fl.x;
+ if(p.x >= xx && p.x < xx+fl.item.width)
+ inside = 1;
+ }
+ fp := Point(xx,fymin);
+ match := 0;
+ if(it != nil) {
+ pick fi := fl.item {
+ Itable =>
+ loc.add(LEitem, fp);
+ loc.le[loc.n-1].item = fl;
+ loc.pos = p.sub(fp);
+ lloc := tablefind(loc, f, fi, fp, p, it);
+ if(lloc != nil)
+ return lloc;
+ Iimage =>
+ match = (it == fl || it == fl.item);
+ }
+ }
+ if(match || inside) {
+ loc.add(LEitem, fp);
+ loc.le[loc.n-1].item = fl;
+ loc.pos = p.sub(fp);
+ if(it == fl.item) {
+ loc.add(LEitem, fp);
+ loc.le[loc.n-1].item = fl.item;
+ }
+ if(inside) {
+ pick fi := fl.item {
+ Itable =>
+ loc = tablefind(loc, f, fi, fp, p, it);
+ }
+ }
+ return loc;
+ }
+ }
+ for(l :=lay.start; l != nil; l = l.next) {
+ o := origin.add(l.pos);
+ if(it != nil || (o.y <= p.y && p.y < o.y+l.height)) {
+ lloc := linefind(loc, f, l, o, p, it);
+ if(lloc != nil)
+ return lloc;
+ if(it == nil && o.y + l.height >= p.y)
+ break;
+ }
+ }
+ return nil;
+}
+
+# p (in screen coords), line at o, also in screen coords
+linefind(loc: ref Loc, f: ref Frame, l: ref Line, o, p: Point, it: ref Item) : ref Loc
+{
+ loc.add(LEline, o);
+ loc.le[loc.n-1].line = l;
+ x := o.x;
+ y := o.y;
+ inside := 0;
+ for(i := l.items; i != nil; i = i.next) {
+ if(it != nil || (x <= p.x && p.x < x+i.width)) {
+ yy := y;
+ h := 0;
+ pick pi := i {
+ Itext =>
+ fnt := fonts[pi.fnt].f;
+ yy += l.ascent - fnt.ascent + (int pi.voff) - Voffbias;
+ h = fnt.height;
+ Irule =>
+ h = pi.size;
+ Iimage =>
+ yy = y;
+ if(pi.align == Abottom)
+ yy += l.ascent - pi.imheight;
+ else if(pi.align == Amiddle)
+ yy += l.ascent - (pi.imheight/2);
+ h = pi.imheight;
+ Iformfield =>
+ h = pi.height;
+ yy += l.ascent - pi.ascent;
+ if(it != nil) {
+ if(it == pi.formfield.image) {
+ loc.add(LEitem, Point(x,yy));
+ loc.le[loc.n-1].item = i;
+ loc.add(LEitem, Point(x,yy));
+ loc.le[loc.n-1].item = it;
+ loc.pos = zp; # doesn't matter, its an 'it' test
+ return loc;
+ }
+ }
+ else if(yy < p.y && p.y < yy+h && pi.formfield.ctlid >= 0) {
+ loc.add(LEcontrol, Point(x,yy));
+ loc.le[loc.n-1].control = f.controls[pi.formfield.ctlid];
+ loc.pos = p.sub(Point(x,yy));
+ return loc;
+ }
+ Itable =>
+ lloc := tablefind(loc, f, pi, Point(x,y), p, it);
+ if(lloc != nil)
+ return lloc;
+ # else leave h==0 so p test will fail
+
+ # floats were handled separately. nulls can be picked by 'it' test
+ # leave h==0, so p test will fail
+ }
+ if(it == i || (it == nil && yy <= p.y && p.y < yy+h)) {
+ loc.add(LEitem, Point(x,yy));
+ loc.le[loc.n-1].item = i;
+ loc.pos = p.sub(Point(x,yy));
+ return loc;
+ }
+ if(it == nil)
+ return nil;
+ }
+ x += i.width;
+ if(it == nil && x >= p.x)
+ break;
+ }
+ loc.n--;
+ return nil;
+}
+
+tablefind(loc: ref Loc, f: ref Frame, ti: ref Item.Itable, torigin: Point, p: Point, it: ref Item) : ref Loc
+{
+ loc.add(LEitem, torigin);
+ loc.le[loc.n-1].item = ti;
+ t := ti.table;
+ (hsp, vsp, pad, bd, cbd, hsep, vsep) := tableparams(t);
+ if(t.caption_lay >= 0) {
+ caplay := f.sublays[t.caption_lay];
+ capy := torigin.y;
+ if(t.caption_place == Abottom)
+ capy += t.toth-t.caph+vsp;
+ lloc := layfind(loc, f, caplay, Point(torigin.x,capy), p, it);
+ if(lloc != nil)
+ return lloc;
+ }
+ for(cl := t.cells; cl != nil; cl = tl cl) {
+ c := hd cl;
+ if(c.layid == -1 || c.layid >= len f.sublays)
+ continue;
+ clay := f.sublays[c.layid];
+ if(clay == nil)
+ continue;
+ cx := torigin.x + t.cols[c.col].pos.x;
+ cy := torigin.y + t.rows[c.row].pos.y;
+ wd := cellwidth(t, c, hsep);
+ ht := cellheight(t, c, vsep);
+ if(it == nil && !p.in(Rect(Point(cx,cy),Point(cx+wd,cy+ht))))
+ continue;
+ if(c.align.valign != Atop && c.align.valign != Abaseline) {
+ n := ht - clay.height;
+ if(c.align.valign == Amiddle)
+ cy += n/2;
+ else if(c.align.valign == Abottom)
+ cy += n;
+ }
+ loc.add(LEtablecell, Point(cx,cy));
+ loc.le[loc.n-1].tcell = c;
+ lloc := layfind(loc, f, clay, Point(cx,cy), p, it);
+ if(lloc != nil)
+ return lloc;
+ loc.n--;
+ if(it == nil)
+ return nil;
+ }
+ loc.n--;
+ return nil;
+}
+
+# (called from jscript)
+# 'it' is an Iimage item in frame f whose image is to be switched
+# to come from the src URL.
+#
+# For now, assume this is called only after the entire build process
+# has finished. Also, only handle the case where the image has
+# been preloaded and is in the cache now. This isn't right (BUG), but will
+# cover most of the cases of extant image swapping, and besides,
+# image swapping is mostly cosmetic anyway.
+#
+# For now, pay no attention to scaling issues or animation issues.
+Frame.swapimage(f: self ref Frame, im: ref Item.Iimage, src: string)
+{
+ u := U->parse(src);
+ if(u.scheme == "")
+ return;
+ u = U->mkabs(u, f.doc.base);
+ # width=height=0 finds u if in cache
+ newci := CImage.new(u, nil, 0, 0);
+ cachedci := (CU->imcache).look(newci);
+ if(cachedci == nil || cachedci.mims == nil)
+ return;
+ im.ci = cachedci;
+
+ # we're assuming image will have same dimensions
+ # as one that is replaced, so no relayout is needed;
+ # otherwise need to call haveimage() instead of drawall()
+ # Netscape scales replacement image to size of replaced image
+
+ f.dirty(f.totalr);
+ drawall(f);
+}
+
+Frame.focus(f : self ref Frame, focus, raisex : int)
+{
+ di := f.doc;
+ if (di == nil || (CU->config).doscripts == 0)
+ return;
+ if (di.evmask && raisex) {
+ kind := E->SEonfocus;
+ if (!focus)
+ kind = E->SEonblur;
+ if(di.evmask & kind)
+ se := ref E->ScriptEvent(kind, f.id, -1, -1, -1, -1, -1, -1, 0, nil, nil, 0);
+ }
+}
+
+Control.newff(f: ref Frame, ff: ref B->Formfield) : ref Control
+{
+ ans : ref Control = nil;
+ case ff.ftype {
+ Ftext or Fpassword or Ftextarea =>
+ nh := ff.size;
+ nv := 1;
+ linewrap := 0;
+ if(ff.ftype == Ftextarea) {
+ nh = ff.cols;
+ nv = ff.rows;
+ linewrap = 1;
+ }
+ ans = Control.newentry(f, nh, nv, linewrap);
+ if(ff.ftype == Fpassword)
+ ans.flags |= CFsecure;
+ ans.entryset(ff.value);
+ Fcheckbox or Fradio =>
+ ans = Control.newcheckbox(f, ff.ftype==Fradio);
+ if((ff.flags&B->FFchecked) != byte 0)
+ ans.flags |= CFactive;
+ Fsubmit or Fimage or Freset or Fbutton =>
+ if(ff.image == nil)
+ ans = Control.newbutton(f, nil, nil, ff.value, nil, 0, 1);
+ else {
+ pick i := ff.image {
+ Iimage =>
+ pic, picmask : ref Image;
+ if(i.ci.mims != nil) {
+ pic = i.ci.mims[0].im;
+ picmask = i.ci.mims[0].mask;
+ }
+ lab := "";
+ if((CU->config).imagelvl == CU->ImgNone) {
+ lab = i.altrep;
+ i = nil;
+ }
+ ans = Control.newbutton(f, pic, picmask, lab, i, 0, 0);
+ }
+ }
+ Fselect =>
+ n := len ff.options;
+ if(n > 0) {
+ ao := array[n] of Option;
+ l := ff.options;
+ for(i := 0; i < n; i++) {
+ o := hd l;
+ # these are copied, so selected can be used for current state
+ ao[i] = *o;
+ l = tl l;
+ }
+ nvis := ff.size;
+ ans = Control.newselect(f, nvis, ao);
+ }
+ Ffile =>
+ if(dbg)
+ sys->print("warning: unimplemented file form field\n");
+ }
+ if(ans != nil)
+ ans.ff = ff;
+ return ans;
+}
+
+Control.newscroll(f: ref Frame, isvert, length, breadth: int) : ref Control
+{
+ # need room for at least two squares and 2 borders of size 2
+ if(length < 12) {
+ breadth = 0;
+ length = 0;
+ }
+ else if(breadth*2 + 4 > length)
+ breadth = (length - 4) / 2;
+ maxpt : Point;
+ flags := CFenabled;
+ if(isvert) {
+ maxpt = Point(breadth, length);
+ flags |= CFscrvert;
+ }
+ else
+ maxpt = Point(length, breadth);
+ return ref Control.Cscrollbar(f, nil, Rect(zp,maxpt), flags, nil, 0, 0, 1, 0, nil, (0, 0));
+}
+
+Control.newentry(f: ref Frame, nh, nv, linewrap: int) : ref Control
+{
+ w := ctlcharspace*nh + 2*ENTHMARGIN;
+ h := ctllinespace*nv + 2*ENTVMARGIN;
+ scr : ref Control;
+ if (linewrap) {
+ scr = Control.newscroll(f, 1, h-4, SCRFBREADTH);
+ scr.r.addpt(Point(w,0));
+ w += SCRFBREADTH;
+ }
+ ans := ref Control.Centry(f, nil, Rect(zp,Point(w,h)), CFenabled, nil, scr, "", (0, 0), 0, linewrap, 0);
+ if (scr != nil) {
+ pick pscr := scr {
+ Cscrollbar =>
+ pscr.ctl = ans;
+ }
+ scr.scrollset(0, 1, 1, 0, 0);
+ }
+ return ans;
+}
+
+Control.newbutton(f: ref Frame, pic, picmask: ref Image, lab: string, it: ref Item.Iimage, candisable, dorelief: int) : ref Control
+{
+ dpic, dpicmask: ref Image;
+ w := 0;
+ h := 0;
+ if(pic != nil) {
+ w = pic.r.dx();
+ h = pic.r.dy();
+ }
+ else if(it != nil) {
+ w = it.imwidth;
+ h = it.imheight;
+ }
+ else {
+ w = fonts[CtlFnt].f.width(lab);
+ h = ctllinespace;
+ }
+ if(dorelief) {
+ # form image buttons are shown without margins in other browsers
+ w += 2*BUTMARGIN;
+ h += 2*BUTMARGIN;
+ }
+ r := Rect(zp, Point(w,h));
+ if(candisable && pic != nil) {
+ # make "greyed out" image:
+ # - convert pic to monochrome (ones where pic is non-white)
+ # - draw pic in White, then DarkGrey shifted (-1,-1) and use
+ # union of those two areas as mask
+ dpicmask = display.newimage(pic.r, Draw->GREY1, 0, D->White);
+ dpic = display.newimage(pic.r, pic.chans, 0, D->White);
+ dpic.draw(dpic.r, colorimage(White), pic, zp);
+ dpicmask.draw(dpicmask.r, display.black, pic, zp);
+ dpic.draw(dpic.r.addpt(Point(-1,-1)), colorimage(DarkGrey), pic, zp);
+ dpicmask.draw(dpicmask.r.addpt(Point(-1,-1)), display.black, pic, zp);
+ }
+ b := ref Control.Cbutton(f, nil, r, CFenabled, nil, pic, picmask, dpic, dpicmask, lab, dorelief);
+ return b;
+}
+
+Control.newcheckbox(f: ref Frame, isradio: int) : ref Control
+{
+ return ref Control.Ccheckbox(f, nil, Rect((0,0),(CBOXWID,CBOXHT)), CFenabled, nil, isradio);
+}
+
+Control.newselect(f: ref Frame, nvis: int, options: array of B->Option) : ref Control
+{
+ nvis = min(5, len options);
+ if (nvis < 1)
+ nvis = 1;
+ fnt := fonts[CtlFnt].f;
+ w := 0;
+ first := -1;
+ for(i := 0; i < len options; i++) {
+ if (first == -1 && options[i].selected)
+ first = i;
+ w = max(w, fnt.width(options[i].display));
+ }
+ if (first == -1)
+ first = 0;
+ if (len options -nvis > 0 && len options - nvis < first)
+ first = len options - nvis;
+ w += 2*SELMARGIN;
+ h := ctllinespace*nvis + 2*SELMARGIN;
+ scr: ref Control;
+ if (nvis > 1 && nvis < len options) {
+ scr = Control.newscroll(f, 1, h, SCRFBREADTH);
+ scr.r.addpt(Point(w,0));
+ }
+ if (nvis < len options)
+ w += SCRFBREADTH;
+ ans := ref Control.Cselect(f, nil, Rect(zp, Point(w,h)), CFenabled, nil, nil, scr, nvis, first, options);
+ if(scr != nil) {
+ pick pscr := scr {
+ Cscrollbar =>
+ pscr.ctl = ans;
+ }
+ scr.scrollset(first, first+nvis, len options, len options, 0);
+ }
+ return ans;
+}
+
+Control.newlistbox(f: ref Frame, nrow, ncol: int, options: array of B->Option) : ref Control
+{
+ fnt := fonts[CtlFnt].f;
+ w := charspace*ncol + 2*SELMARGIN;
+ h := fnt.height*nrow + 2*SELMARGIN;
+
+ vscr: ref Control = nil;
+ #if(nrow < len options) {
+ vscr = Control.newscroll(f, 1, (h-4)+SCRFBREADTH, SCRFBREADTH);
+ vscr.r.addpt(Point(w-SCRFBREADTH,0));
+ w += SCRFBREADTH;
+ #}
+
+ maxw := 0;
+ for(i := 0; i < len options; i++)
+ maxw = max(maxw, fnt.width(options[i].display));
+
+ hscr: ref Control = nil;
+ #if(w < maxw) {
+ # allow for border (inset(2))
+ hscr = Control.newscroll(f, 0, (w-4)-SCRFBREADTH, SCRFBREADTH);
+ hscr.r.addpt(Point(0, h-SCRBREADTH));
+ h += SCRFBREADTH;
+ #}
+
+ ans := ref Control.Clistbox(f, nil, Rect(zp, Point(w,h)), CFenabled, nil, hscr, vscr, nrow, 0, 0, maxw/charspace, options, nil);
+ if(vscr != nil) {
+ pick pscr := vscr {
+ Cscrollbar =>
+ pscr.ctl = ans;
+ }
+ vscr.scrollset(0, nrow, len options, len options, 0);
+ }
+ if(hscr != nil) {
+ pick pscr := hscr {
+ Cscrollbar =>
+ pscr.ctl = ans;
+ }
+ hscr.scrollset(0, w-SCRFBREADTH, maxw, 0, 0);
+ }
+ return ans;
+}
+
+Control.newanimimage(f: ref Frame, cim: ref CU->CImage, bg: Background) : ref Control
+{
+ return ref Control.Canimimage(f, nil, Rect((0,0),(cim.width,cim.height)), 0, nil, cim, 0, 0, big 0, bg);
+}
+
+Control.newlabel(f: ref Frame, s: string) : ref Control
+{
+ w := fonts[DefFnt].f.width(s);
+ h := ctllinespace + 2*ENTVMARGIN; # give it same height as an entry box
+ return ref Control.Clabel(f, nil, Rect(zp,Point(w,h)), 0, nil, s);
+}
+
+Control.disable(c: self ref Control)
+{
+ if(c.flags & CFenabled) {
+ win := c.f.cim;
+ c.flags &= ~CFenabled;
+ if(c.f.cim != nil)
+ c.draw(1);
+ }
+}
+
+Control.enable(c: self ref Control)
+{
+ if(!(c.flags & CFenabled)) {
+ c.flags |= CFenabled;
+ if(c.f.cim != nil)
+ c.draw(1);
+ }
+}
+
+changeevent(c: ref Control)
+{
+ onchange := 0;
+ pick pc := c {
+ Centry =>
+ onchange = pc.onchange;
+ pc.onchange = 0;
+# this code reproduced Navigator 2 bug
+# changes to Select Formfield selection only resulted in onchange event upon
+# loss of focus. Now handled by domouse() code so event can be raised
+# immediately
+# Cselect =>
+# onchange = pc.onchange;
+# pc.onchange = 0;
+ }
+ if(onchange && (c.ff.evmask & E->SEonchange)) {
+ se := ref E->ScriptEvent(E->SEonchange, c.f.id, c.ff.form.formid, c.ff.fieldid, -1, -1, -1, -1, 1, nil, nil, 0);
+ J->jevchan <-= se;
+ }
+}
+
+blurfocusevent(c: ref Control, kind, raisex: int)
+{
+ if((CU->config).doscripts && c.ff != nil && c.ff.evmask) {
+ if(kind == E->SEonblur)
+ changeevent(c);
+ if (!raisex || !(c.ff.evmask & kind))
+ return;
+ se := ref E->ScriptEvent(kind, c.f.id, c.ff.form.formid, c.ff.fieldid, -1, -1, -1, -1, 0, nil, nil, 0);
+ J->jevchan <-= se;
+ }
+}
+
+Control.losefocus(c: self ref Control, raisex: int)
+{
+ if(c.flags & CFhasfocus) {
+ c.flags &= ~CFhasfocus;
+ if(c.f.cim != nil) {
+ blurfocusevent(c, E->SEonblur, raisex);
+ c.draw(1);
+ }
+ }
+}
+
+Control.gainfocus(c: self ref Control, raisex: int)
+{
+ if(!(c.flags & CFhasfocus)) {
+ c.flags |= CFhasfocus;
+ if(c.f.cim != nil) {
+ blurfocusevent(c, E->SEonfocus, raisex);
+ c.draw(1);
+ }
+ G->clientfocus();
+ }
+}
+
+Control.scrollset(c: self ref Control, v1, v2, vmax, nsteps, draw: int)
+{
+ pick sc := c {
+ Cscrollbar =>
+ if(v1 < 0)
+ v1 = 0;
+ if(v2 > vmax)
+ v2 = vmax;
+ if(v1 > v2)
+ v1 = v2;
+ if(v1 == 0 && v2 == vmax) {
+ sc.mindelta = 1;
+ sc.deltaval = 0;
+ sc.top = 0;
+ sc.bot = 0;
+ }
+ else {
+ length, breadth: int;
+ if(sc.flags&CFscrvert) {
+ length = sc.r.max.y - sc.r.min.y;
+ breadth = sc.r.max.x - sc.r.min.x;
+ }
+ else {
+ length = sc.r.max.x - sc.r.min.x;
+ breadth = sc.r.max.y - sc.r.min.y;
+ }
+ l := length - (2*breadth + MINSCR);
+ if(l < 0)
+ CU->raisex("EXInternal: negative scrollbar trough");
+ sc.top = l*v1/vmax;
+ sc.bot = l*(vmax-v2)/vmax;
+ if (nsteps == 0)
+ sc.mindelta = 1;
+ else
+ sc.mindelta = max(1, length/nsteps);
+ sc.deltaval = max(1, vmax/(l/sc.mindelta))*SCRDELTASF;
+ }
+ if(sc.f.cim != nil && draw)
+ sc.draw(1);
+ }
+}
+
+SPECMASK : con 16rf000;
+CTRLMASK : con 16r1f;
+DEL : con 16r7f;
+TAB : con '\t';
+CR: con '\n';
+
+Control.dokey(ctl: self ref Control, keychar: int) : int
+{
+ if(!(ctl.flags&CFenabled))
+ return CAnone;
+ ans := CAnone;
+ pick c := ctl {
+ Centry =>
+ olds := c.s;
+ slen := len c.s;
+ (sels, sele) := normalsel(c.sel);
+ modified := 0;
+ (osels, osele) := (sels, sele);
+ case keychar {
+ ('a' & CTRLMASK) or Keyboard->Home =>
+ (sels, sele) = (0, 0);
+ ('e' & CTRLMASK) or Keyboard->End =>
+ (sels, sele) = (slen, slen);
+ 'f' & CTRLMASK or Keyboard->Right =>
+ if(sele < slen)
+ (sels, sele) = (sele+1, sele+1);
+ 'b' & CTRLMASK or Keyboard->Left =>
+ if(sels > 0)
+ (sels, sele) = (sels-1, sels-1);
+ Keyboard->Up =>
+ if (c.linewrap)
+ sels = sele = entryupdown(c, sels, -1);
+ Keyboard->Down =>
+ if (c.linewrap)
+ sels = sele = entryupdown(c, sele, 1);
+ 'u' & CTRLMASK =>
+ entrydelrange(c, 0, slen);
+ modified = 1;
+ (sels, sele) = c.sel;
+ 'c' & CTRLMASK =>
+ entrysetsnarf(c);
+ 'v' & CTRLMASK =>
+ entryinsertsnarf(c);
+ modified = 1;
+ (sels, sele) = c.sel;
+ 'h' & CTRLMASK or DEL=>
+ if (sels != sele) {
+ entrydelrange(c, sels, sele);
+ modified = 1;
+ } else if(sels > 0) {
+ entrydelrange(c, sels-1, sels);
+ modified = 1;
+ }
+ (sels, sele) = c.sel;
+ Keyboard->Del =>
+ if (sels != sele) {
+ entrydelrange(c, sels, sele);
+ modified = 1;
+ } else if(sels < len c.s) {
+ entrydelrange(c, sels, sels+1);
+ modified = 1;
+ }
+ (sels, sele) = c.sel;
+ TAB =>
+ ans = CAtabkey;
+ * =>
+ if ((keychar & SPECMASK) == Keyboard->Spec)
+ # ignore all other special keys
+ break;
+ if(keychar == CR) {
+ if(c.linewrap)
+ keychar = '\n';
+ else
+ ans = CAreturnkey;
+ }
+ if(keychar > CTRLMASK || (keychar == '\n' && c.linewrap)) {
+ if (sels != sele) {
+ entrydelrange(c, sels, sele);
+ (sels, sele) = c.sel;
+ }
+ slen = len c.s;
+ c.s[slen] = 0; # expand string by 1 char
+ for(k := slen; k > sels; k--)
+ c.s[k] = c.s[k-1];
+ c.s[sels] = keychar;
+ (sels, sele) = (sels+1, sels+1);
+ modified = 1;
+ }
+ }
+ c.sel = (sels, sele);
+ if(osels != sels || osele != sele || modified) {
+ entryscroll(c);
+ c.draw(1);
+ }
+ if (c.s != olds)
+ c.onchange = 1;
+ }
+ return ans;
+}
+
+Control.domouse(ctl: self ref Control, p: Point, mtype: int, oldgrab : ref Control) : (int, ref Control)
+{
+ up := (mtype == E->Mlbuttonup || mtype == E->Mldrop);
+ down := (mtype == E->Mlbuttondown);
+ drag := (mtype == E->Mldrag);
+ hold := (mtype == E->Mhold);
+ move := (mtype == E->Mmove);
+
+ # any button actions stop auto-repeat
+ # it's up to the individual controls to re-instate it
+ if (!move)
+ E->autorepeat(nil, 0, 0);
+
+ if(!(ctl.flags&CFenabled))
+ return (CAnone, nil);
+ ans := CAnone;
+ changed := 0;
+ newgrab : ref Control;
+ grabbed := oldgrab != nil;
+ pick c := ctl {
+ Cbutton =>
+ if(down) {
+ c.flags |= CFactive;
+ newgrab = c;
+ changed = 1;
+ }
+ else if(move && c.ff == nil) {
+ ans = CAflyover;
+ }
+ else if (drag && grabbed) {
+ newgrab = c;
+ active := 0;
+ if (p.in(c.r))
+ active = CFactive;
+ if ((c.flags & CFactive) != active)
+ changed = 1;
+ c.flags = (c.flags & ~CFactive) | active;
+ }
+ else if(up) {
+ if (c.flags & CFactive)
+ ans = CAbuttonpush;
+ c.flags &= ~CFactive;
+ changed = 1;
+ }
+ Centry =>
+ if(c.scr != nil && !grabbed && p.x >= c.r.max.x-SCRFBREADTH) {
+ pick scr := c.scr {
+ Cscrollbar =>
+ return scr.domouse(p, mtype, oldgrab);
+ }
+ }
+ (sels, sele) := c.sel;
+ if(mtype == E->Mlbuttonup && grabbed) {
+ if (sels != sele)
+ ans = CAselected;
+ }
+ if(down || (drag && grabbed)) {
+ newgrab = c;
+ x := c.r.min.x+ENTHMARGIN;
+ fnt := fonts[CtlFnt].f;
+ s := c.s;
+ if(c.flags&CFsecure) {
+ for(i := 0; i < len s; i++)
+ s[i] = '*';
+ }
+ (osels, osele) := c.sel;
+ s1 := " ";
+ i := 0;
+ iend := len s - 1;
+ if(c.linewrap) {
+ (lines, linestarts, topline, cursline) := entrywrapcalc(c);
+ if(len lines > 1) {
+ lineno := topline + (p.y - (c.r.min.y+ENTVMARGIN)) / ctllinespace;
+ lineno = min(lineno, len lines -1);
+ lineno = max(lineno, 0);
+
+ i = linestarts[lineno];
+ iend = i + len lines[lineno] -1;
+ }
+ } else
+ x -= fnt.width(s[:c.left]);
+ for(; i <= iend; i++) {
+ s1[0] = s[i];
+ cx := fnt.width(s1);
+ if(p.x < x + cx)
+ break;
+ x += cx;
+ }
+ sele = i;
+
+ if (down)
+ sels = sele;
+ c.sel = (sels, sele);
+
+ if (sels != osels || sele != osele) {
+ changed = 1;
+ entryscroll(c);
+ if (p.x < c.r.min.x + ENTHMARGIN || p.x > c.r.max.x - ENTHMARGIN
+ || p.y < c.r.min.y + ENTVMARGIN || p.y > c.r.max.y - ENTVMARGIN) {
+ E->autorepeat(ref (Event.Emouse)(p, mtype), ARTICK, ARTICK);
+ }
+ }
+
+ if(!(c.flags&CFhasfocus))
+ ans = CAkeyfocus;
+ }
+ Ccheckbox=>
+ if(up) {
+ if(c.isradio) {
+ if(!(c.flags&CFactive)) {
+ c.flags |= CFactive;
+ changed = 1;
+ ans = CAbuttonpush;
+ # turn off other radio button
+ frm := c.ff.form;
+ for(lf := frm.fields; lf != nil; lf = tl lf) {
+ ff := hd lf;
+ if(ff == c.ff)
+ continue;
+ if(ff.ftype == Fradio && ff.name==c.ff.name && ff.ctlid >= 0) {
+ d := c.f.controls[ff.ctlid];
+ if(d.flags&CFactive) {
+ d.flags &= ~CFactive;
+ d.draw(1);
+ break; # at most one other should be on
+ }
+ }
+ }
+ }
+ }
+ else {
+ c.flags ^= CFactive;
+ changed = 1;
+ }
+ }
+ Cselect =>
+ if (c.nvis == 1 && up && c.popup == nil && c.r.contains(p))
+ return (CAdopopup, nil);
+ if(c.scr != nil && (grabbed || p.x >= c.r.max.x-SCRFBREADTH)) {
+ pick scr := c.scr {
+ Cscrollbar =>
+ (a, grab) := scr.domouse(p, mtype, oldgrab);
+ if (grab != nil)
+ grab = c;
+ return (a, grab);
+ }
+ return (ans, nil);
+ }
+ n := (p.y - (c.r.min.y+SELMARGIN))/ctllinespace + c.first;
+ if (n >= c.first && n < c.first+c.nvis) {
+ if ((c.ff.flags&B->FFmultiple) != byte 0) {
+ if (down) {
+ c.options[n].selected ^= 1;
+ changed = 1;
+ }
+ } else if (up || drag) {
+ changed = c.options[n].selected == 0;
+ c.options[n].selected = 1;
+ for(i := 0; i < len c.options; i++) {
+ if(i != n)
+ c.options[i].selected = 0;
+ }
+ }
+ }
+ if (up) {
+ if (c.popup != nil)
+ ans = CAdonepopup;
+ else
+ ans = CAchanged;
+ }
+ Clistbox =>
+ if(c.vscr != nil && (c.grab == c.vscr || (!grabbed && p.x >= c.r.max.x-SCRFBREADTH))) {
+ c.grab = nil;
+ pick vscr := c.vscr {
+ Cscrollbar =>
+ (a, grab) := vscr.domouse(p, mtype, oldgrab);
+ if (grab != nil) {
+ c.grab = c.vscr;
+ grab = c;
+ }
+ return (a, grab);
+ }
+ }
+ else if(c.hscr != nil && (c.grab == c.hscr || (!grabbed && p.y >= c.r.max.y-SCRFBREADTH))) {
+ c.grab = nil;
+ pick hscr := c.hscr {
+ Cscrollbar =>
+ (a, grab) := hscr.domouse(p, mtype, oldgrab);
+ if (grab != nil) {
+ c.grab = c.hscr;
+ grab = c;
+ }
+ return (a, grab);
+ }
+ }
+ else if(up) {
+ fnt := fonts[CtlFnt].f;
+ n := (p.y - (c.r.min.y+SELMARGIN))/fnt.height + c.first;
+ if(n >= 0 && n < len c.options) {
+ c.options[n].selected ^= 1;
+ # turn off other selections
+ for(i := 0; i < len c.options; i++) {
+ if(i != n)
+ c.options[i].selected = 0;
+ }
+ ans = CAchanged;
+ changed = 1;
+ }
+ }
+ Cscrollbar =>
+ val := 0;
+ v, vmin, vmax, b: int;
+ if(c.flags&CFscrvert) {
+ v = p.y;
+ vmin = c.r.min.y;
+ vmax = c.r.max.y;
+ b = c.r.dx();
+ }
+ else {
+ v = p.x;
+ vmin = c.r.min.x;
+ vmax = c.r.max.x;
+ b = c.r.dy();
+ }
+ vsltop := vmin+b+c.top;
+ vslbot := vmax-b-c.bot;
+ actflags := 0;
+ oldactflags := c.flags&CFscrallact;
+
+ if ((down || drag) && !up && !hold)
+ newgrab = c;
+
+ if (down) {
+ newgrab = c;
+ holdval := 0;
+ repeat := 1;
+ if (v >= vsltop && v < vslbot) {
+ holdval = v - vsltop;
+ actflags = CFactive;
+ repeat = 0;
+ }
+ if(v < vmin+b) {
+ holdval = -1;
+ actflags = CFscracta1;
+ }
+ else if(v < vsltop) {
+ holdval = -1;
+ actflags = CFscracttr1;
+ }
+ else if(v >= vmax-b) {
+ holdval = 1;
+ actflags = CFscracta2;
+ }
+ else if(v >= vslbot) {
+ holdval = 1;
+ actflags = CFscracttr2;
+ }
+ c.holdstate = (actflags, holdval);
+ if (repeat) {
+ E->autorepeat(ref (Event.Emouse)(p, E->Mhold), ARPAUSE, ARTICK);
+ }
+ }
+ if (drag) {
+ (actflags, val) = c.holdstate;
+ if (actflags == CFactive) {
+ # dragging main scroll widget (relative to top of drag block)
+ val = (v - vsltop) - val;
+ if(abs(val) >= c.mindelta) {
+ ans = CAscrolldelta;
+ val = (c.deltaval * (val / c.mindelta))/SCRDELTASF;
+ }
+ } else {
+ E->autorepeat(ref (Event.Emouse)(p, E->Mhold), ARTICK, ARTICK);
+ }
+ }
+ if (up || hold) {
+ # set the action according to the hold state
+ # Note: main widget (case CFactive) handled by drag
+ act := 0;
+ (act, val) = c.holdstate;
+ case act {
+ CFscracta1 or
+ CFscracta2 =>
+ ans = CAscrollline;
+ CFscracttr1 or
+ CFscracttr2 =>
+ ans = CAscrollpage;
+ }
+ if (up) {
+ c.holdstate = (0, 0);
+ } else { # hold
+ (actflags, nil) = c.holdstate;
+ if (ans != CAnone) {
+ E->autorepeat(ref (Event.Emouse)(p, E->Mhold), ARTICK, ARTICK);
+ newgrab = c;
+ }
+ }
+ }
+ c.flags = (c.flags & ~CFscrallact) | actflags;
+ if(ans != CAnone) {
+ ftoscroll := c.f;
+ if(c.ctl != nil) {
+ pick cff := c.ctl {
+ Centry =>
+ ny := (cff.r.dy() - 2 * ENTVMARGIN) / ctllinespace;
+ (nil, linestarts, topline, nil) := entrywrapcalc(cff);
+ nlines := len linestarts;
+ case ans {
+ CAscrollpage =>
+ topline += val*ny;
+ CAscrollline =>
+ topline += val;
+ CAscrolldelta =>
+# # insufficient for large number of lines
+ topline += val;
+# if(val > 0)
+# topline++;
+# else
+# topline--;
+ }
+ if (topline+ny >= nlines)
+ topline = (nlines-1) - ny;
+ if (topline < 0)
+ topline = 0;
+ cff.left = linestarts[topline];
+ c.scrollset(topline, topline+ny, nlines - 1, nlines, 1);
+ cff.draw(1);
+ return (ans, newgrab);
+ Cselect =>
+ newfirst := cff.first;
+ case ans {
+ CAscrollpage =>
+ newfirst += val*cff.nvis;
+ CAscrollline =>
+ newfirst += val;
+ CAscrolldelta =>
+# # insufficient for very long select lists
+ newfirst += val;
+# if(val > 0)
+# newfirst++;
+# else
+# newfirst--;
+ }
+ newfirst = max(0, min(newfirst, len cff.options - cff.nvis));
+ cff.first = newfirst;
+ nopt := len cff.options;
+ c.scrollset(newfirst, newfirst+cff.nvis, nopt, nopt, 0);
+ cff.draw(1);
+ return (ans, newgrab);
+ Clistbox =>
+ if(c.flags&CFscrvert) {
+ newfirst := cff.first;
+ case ans {
+ CAscrollpage =>
+ newfirst += val*cff.nvis;
+ CAscrollline =>
+ newfirst += val;
+ CAscrolldelta =>
+ newfirst += val;
+# if(val > 0)
+# newfirst++;
+# else
+# newfirst--;
+ }
+ newfirst = max(0, min(newfirst, len cff.options - cff.nvis));
+ cff.first = newfirst;
+ c.scrollset(newfirst, newfirst+cff.nvis, len cff.options, 0, 1);
+ # TODO: need redraw only vscr and content
+ }
+ else {
+ hw := cff.maxcol;
+ w := (c.r.max.x - c.r.min.x - SCRFBREADTH)/charspace;
+ newstart := cff.start;
+ case ans {
+ CAscrollpage =>
+ newstart += val*hw;
+ CAscrollline =>
+ newstart += val;
+ CAscrolldelta =>
+ if(val > 0)
+ newstart++;
+ else
+ newstart--;
+ }
+ if(hw < w)
+ newstart = 0;
+ else
+ newstart = max(0, min(newstart, hw - w));
+ cff.start = newstart;
+ c.scrollset(newstart, w+newstart, hw, 0, 1);
+ # TODO: need redraw only hscr and content
+ }
+ cff.draw(1);
+ return (ans, newgrab);
+ }
+ }
+ else {
+ if(c.flags&CFscrvert)
+ c.f.yscroll(ans, val);
+ else
+ c.f.xscroll(ans, val);
+ }
+ changed = 1;
+ }
+ else if(actflags != oldactflags) {
+ changed = 1;
+ }
+ }
+ if(changed)
+ ctl.draw(1);
+ return (ans, newgrab);
+}
+
+# returns a new popup control
+Control.dopopup(ctl: self ref Control): ref Control
+{
+ sel : ref Control.Cselect;
+ pick c := ctl {
+ Cselect =>
+ if (c.nvis > 1)
+ return nil;
+ sel = c;
+ * =>
+ return nil;
+ }
+
+ w := sel.r.dx();
+ nopt := len sel.options;
+ nvis := min(nopt, POPUPLINES);
+ first := sel.first;
+ if (first + nvis > nopt)
+ first = nopt - nvis;
+ h := ctllinespace*nvis + 2*SELMARGIN;
+ r := Rect(sel.r.min, sel.r.min.add(Point(w, h)));
+ popup := G->getpopup(r);
+ if (popup == nil)
+ return nil;
+ scr : ref Control;
+ if (nvis < nopt) {
+ scr = Control.newscroll(sel.f, 1, h, SCRFBREADTH);
+ scr.r.addpt(Point(w,0));
+ }
+ newsel := ref Control.Cselect(sel.f, sel.ff, r, sel.flags, popup, sel, scr, nvis, first, sel.options);
+ if(scr != nil) {
+ pick pscr := scr {
+ Cscrollbar =>
+ pscr.ctl = newsel;
+ }
+ scr.popup = popup;
+ scr.scrollset(first, first+nvis, nopt, nopt, 0);
+ }
+ newsel.draw(1);
+ return newsel;
+}
+
+# returns original control for which this was a popup
+Control.donepopup(ctl: self ref Control): ref Control
+{
+ owner: ref Control;
+ pick c := ctl {
+ Cselect =>
+ if (c.owner == nil)
+ return nil;
+ owner = c.owner;
+ * =>
+ return nil;
+ }
+ G->cancelpopup();
+ pick c := owner {
+ Cselect =>
+ for (first := 0; first < len c.options; first++)
+ if (c.options[first].selected)
+ break;
+ if (first == len c.options)
+ first = 0;
+ c.first = first;
+ }
+ owner.draw(1);
+ return owner;
+}
+
+
+Control.reset(ctl: self ref Control)
+{
+ pick c := ctl {
+ Cbutton =>
+ c.flags &= ~CFactive;
+ Centry =>
+ c.s = "";
+ c.sel = (0, 0);
+ c.left = 0;
+ if(c.ff != nil && c.ff.value != "")
+ c.s = c.ff.value;
+ if (c.scr != nil)
+ c.scr.scrollset(0, 1, 1, 0, 0);
+ Ccheckbox=>
+ c.flags &= ~CFactive;
+ if(c.ff != nil && (c.ff.flags&B->FFchecked) != byte 0)
+ c.flags |= CFactive;
+ Cselect =>
+ nopt := len c.options;
+ if(c.ff != nil) {
+ l := c.ff.options;
+ for(i := 0; i < nopt; i++) {
+ o := hd l;
+ c.options[i].selected = o.selected;
+ l = tl l;
+ }
+ }
+ c.first = 0;
+ if(c.scr != nil) {
+ c.scr.scrollset(0, c.nvis, nopt, nopt, 0);
+ }
+ Clistbox =>
+ c.first = 0;
+ nopt := len c.options;
+ if(c.vscr != nil) {
+ c.vscr.scrollset(0, c.nvis, nopt, nopt, 0);
+ }
+ hw := 0;
+ for(i := 0; i < len c.options; i++)
+ hw = max(hw, fonts[DefFnt].f.width(c.options[i].display));
+ if(c.hscr != nil) {
+ c.hscr.scrollset(0, c.r.max.x, hw, 0, 0);
+ }
+ Canimimage =>
+ c.cur = 0;
+ }
+ ctl.draw(0);
+}
+
+Control.draw(ctl: self ref Control, flush: int)
+{
+ win := ctl.f.cim;
+ if (ctl.popup != nil)
+ win = ctl.popup.image;
+ if (win == nil)
+ return;
+ oclipr := win.clipr;
+ clipr := oclipr;
+ any: int;
+ if (ctl.popup == nil) {
+ (clipr, any) = ctl.r.clip(ctl.f.cr);
+ if(!any && ctl != ctl.f.vscr && ctl != ctl.f.hscr)
+ return;
+ win.clipr = clipr;
+ }
+ pick c := ctl {
+ Cbutton =>
+ if(c.ff != nil && c.ff.image != nil && c.pic == nil) {
+ # check to see if image arrived
+ # (dimensions will have been set by checkffsize, if needed;
+ # this code is only for when the HTML specified the dimensions)
+ pick imi := c.ff.image {
+ Iimage =>
+ if(imi.ci.mims != nil) {
+ c.pic = imi.ci.mims[0].im;
+ c.picmask = imi.ci.mims[0].mask;
+ }
+ }
+ }
+ if(c.dorelief || c.pic == nil)
+ win.draw(c.r, colorimage(Grey), nil, zp);
+ if(c.pic != nil) {
+ p, m: ref Image;
+ if(c.flags & CFenabled) {
+ p = c.pic;
+ m = c.picmask;
+ }
+ else {
+ p = c.dpic;
+ m = c.dpicmask;
+ }
+ w := p.r.dx();
+ h := p.r.dy();
+ x := c.r.min.x + (c.r.dx() - w) / 2;
+ y := c.r.min.y + (c.r.dy() - h) / 2;
+ if((c.flags & CFactive) && c.dorelief) {
+ x++;
+ y++;
+ }
+ win.draw(Rect((x,y),(x+w,y+h)), p, m, zp);
+ }
+ else if(c.label != "") {
+ p := c.r.min.add(Point(BUTMARGIN, BUTMARGIN));
+ if(c.flags & CFactive)
+ p = p.add(Point(1,1));
+ win.text(p, colorimage(Black), zp, fonts[CtlFnt].f, c.label);
+ }
+ if(c.dorelief) {
+ relief := ReliefRaised;
+ if(c.flags & CFactive)
+ relief = ReliefSunk;
+ drawrelief(win, c.r.inset(2), relief);
+ }
+ Centry =>
+ win.draw(c.r, colorimage(White), nil, zp);
+ insetr := c.r.inset(2);
+ drawrelief(win,insetr, ReliefSunk);
+ eclipr := c.r;
+ eclipr.min.x += ENTHMARGIN;
+ eclipr.max.x -= ENTHMARGIN;
+ eclipr.min.y += ENTVMARGIN;
+ eclipr.max.y -= ENTVMARGIN;
+# if (c.scr != nil)
+# eclipr.max.x -= SCRFBREADTH;
+ (eclipr, any) = clipr.clip(eclipr);
+ win.clipr = eclipr;
+ p := c.r.min.add(Point(ENTHMARGIN,ENTVMARGIN));
+ s := c.s;
+ fnt := fonts[CtlFnt].f;
+ if(c.left > 0)
+ s = s[c.left:];
+ if(c.flags&CFsecure) {
+ for(i := 0; i < len s; i++)
+ s[i] = '*';
+ }
+
+ (sels, sele) := normalsel(c.sel);
+ (sels, sele) = (sels-c.left, sele-c.left);
+
+ lines : array of string;
+ linestarts : array of int;
+ textw := c.r.dx()-2*ENTHMARGIN;
+ if (c.scr != nil) {
+ textw -= SCRFBREADTH;
+ c.scr.r = c.scr.r.subpt(c.scr.r.min);
+ c.scr.r = c.scr.r.addpt(Point(insetr.max.x-SCRFBREADTH,insetr.min.y));
+ c.scr.draw(0);
+ }
+ if (c.linewrap)
+ (lines, linestarts) = wrapstring(fnt, s, textw);
+ else
+ (lines, linestarts) = (array [] of {s}, array [] of {0});
+
+ q := p;
+ black := colorimage(Black);
+ white := colorimage(White);
+ navy := colorimage(Navy);
+ nlines := len lines;
+ for (n := 0; n < nlines; n++) {
+ segs : list of (int, int, int);
+ # only show cursor or selection if we have focus
+ if (c.flags & CFhasfocus)
+ segs = selsegs(len lines[n], sels-linestarts[n], sele-linestarts[n]);
+ else
+ segs = (0, len lines[n], 0) :: nil;
+ for (; segs != nil; segs = tl segs) {
+ (ss, se, sel) := hd segs;
+ txt := lines[n][ss:se];
+ w := fnt.width(txt);
+ txtcol : ref Image;
+ if (!sel)
+ txtcol = black;
+ else {
+ txtcol = white;
+ bgcol := navy;
+ if (n < nlines-1 && sele >= linestarts[n+1])
+ w = (p.x-q.x) + textw;
+ selr := Rect((q.x, q.y-1), (q.x+w, q.y+ctllinespace+1));
+ if (selr.dx() == 0) {
+ # empty selection - assume cursor
+ bgcol = black;
+ selr.max.x = selr.min.x + 2;
+ }
+ win.draw(selr, bgcol, nil, zp);
+ }
+ if (se > ss)
+ win.text(q, txtcol, zp, fnt, txt);
+ q.x += w;
+ }
+ q = (p.x, q.y + ctllinespace);
+ }
+ Ccheckbox=>
+ win.draw(c.r, colorimage(White), nil, zp);
+ if(c.isradio) {
+ a := CBOXHT/2;
+ a1 := a-1;
+ cen := Point(c.r.min.x+a,c.r.min.y+a);
+ win.ellipse(cen, a1, a1, 1, colorimage(DarkGrey), zp);
+ win.arc(cen, a, a, 0, colorimage(Black), zp, 45, 180);
+ win.arc(cen, a, a, 0, colorimage(Grey), zp, 225, 180);
+ if(c.flags&CFactive)
+ win.fillellipse(cen, 2, 2, colorimage(Black), zp);
+ }
+ else {
+ ir := c.r.inset(2);
+ ir.min.x += CBOXWID-CBOXHT;
+ ir.max.x -= CBOXWID-CBOXHT;
+ drawrelief(win, ir, ReliefSunk);
+ if(c.flags&CFactive) {
+ p1 := Point(ir.min.x, ir.min.y);
+ p2 := Point(ir.max.x, ir.max.y);
+ p3 := Point(ir.max.x, ir.min.y);
+ p4 := Point(ir.min.x, ir.max.y);
+ win.line(p1, p2, D->Endsquare, D->Endsquare, 0, colorimage(Black), zp);
+ win.line(p3, p4, D->Endsquare, D->Endsquare, 0, colorimage(Black), zp);
+ }
+ }
+ Cselect =>
+ black := colorimage(Black);
+ white := colorimage(White);
+ navy := colorimage(Navy);
+ win.draw(c.r, white, nil, zp);
+ drawrelief(win, c.r.inset(2), ReliefSunk);
+ ir := c.r.inset(SELMARGIN);
+ p := ir.min;
+ fnt := fonts[CtlFnt].f;
+ drawsel := c.nvis > 1;
+ for(i := c.first; i < len c.options && i < c.first+c.nvis; i++) {
+ if(drawsel && c.options[i].selected) {
+ maxx := ir.max.x;
+ if (c.scr != nil)
+ maxx -= SCRFBREADTH;
+ r := Rect((p.x-SELMARGIN,p.y),(maxx,p.y+ctllinespace));
+ win.draw(r, navy, nil, zp);
+ win.text(p, white, zp, fnt, c.options[i].display);
+ }
+ else {
+ win.text(p, black, zp, fnt, c.options[i].display);
+ }
+ p.y += ctllinespace;
+ }
+ if (c.nvis == 1 && len c.options > 1) {
+ # drop down select list - draw marker (must be same width as scroll bar)
+ r := Rect((ir.max.x - SCRFBREADTH, ir.min.y), ir.max);
+ drawtriangle(win, r, TRIdown, ReliefRaised);
+ }
+ if(c.scr != nil) {
+ c.scr.r = Rect((ir.max.x - SCRFBREADTH, ir.min.y), ir.max);
+ c.scr.draw(0);
+ }
+ Clistbox =>
+ black := colorimage(Black);
+ white := colorimage(White);
+ navy := colorimage(Navy);
+ win.draw(c.r, white, nil, zp);
+ insetr := c.r.inset(2);
+ #drawrelief(win, c.r.inset(2), ReliefSunk);
+ ir := c.r.inset(SELMARGIN);
+ p := ir.min;
+ fnt := fonts[CtlFnt].f;
+ for(i := c.first; i < len c.options && i < c.first+c.nvis; i++) {
+ txt := "";
+ if (c.start < len c.options[i].display)
+ txt = c.options[i].display[c.start:];
+ if(c.options[i].selected) {
+ r := Rect((p.x-SELMARGIN,p.y),(c.r.max.x-SCRFBREADTH,p.y+fnt.height));
+ win.draw(r, navy, nil, zp);
+ win.text(p, white, zp, fnt, txt);
+ }
+ else {
+ win.text(p, black, zp, fnt, txt);
+ }
+ p.y +=fnt.height;
+ }
+ if(c.vscr != nil) {
+ c.vscr.r = c.vscr.r.subpt(c.vscr.r.min);
+ c.vscr.r = c.vscr.r.addpt(Point(insetr.max.x-SCRFBREADTH,insetr.min.y));
+ c.vscr.draw(0);
+ }
+ if(c.hscr != nil) {
+ c.hscr.r = c.hscr.r.subpt(c.hscr.r.min);
+ c.hscr.r = c.hscr.r.addpt(Point(insetr.min.x, insetr.max.y-SCRFBREADTH));
+ c.hscr.draw(0);
+ }
+ drawrelief(win, insetr, ReliefSunk);
+
+ Cscrollbar =>
+ # Scrollbar components: arrow 1 (a1), trough 1 (t1), slider (s), trough 2 (t2), arrow 2 (a2)
+ x := c.r.min.x;
+ y := c.r.min.y;
+ ra1, rt1, rs, rt2, ra2: Rect;
+ b, l, a1kind, a2kind: int;
+ if(c.flags&CFscrvert) {
+ l = c.r.max.y - c.r.min.y;
+ b = c.r.max.x - c.r.min.x;
+ xr := x+b;
+ yt1 := y+b;
+ ys := yt1+c.top;
+ yb := y+l;
+ ya2 := yb-b;
+ yt2 := ya2-c.bot;
+ ra1 = Rect(Point(x,y),Point(xr,yt1));
+ rt1 = Rect(Point(x,yt1),Point(xr,ys));
+ rs = Rect(Point(x,ys),Point(xr,yt2));
+ rt2 = Rect(Point(x,yt2),Point(xr,ya2));
+ ra2 = Rect(Point(x,ya2),Point(xr,yb));
+ a1kind = TRIup;
+ a2kind = TRIdown;
+ }
+ else {
+ l = c.r.max.x - c.r.min.x;
+ b = c.r.max.y - c.r.min.y;
+ yb := y+b;
+ xt1 := x+b;
+ xs := xt1+c.top;
+ xr := x+l;
+ xa2 := xr-b;
+ xt2 := xa2-c.bot;
+ ra1 = Rect(Point(x,y),Point(xt1,yb));
+ rt1 = Rect(Point(xt1,y),Point(xs,yb));
+ rs = Rect(Point(xs,y),Point(xt2,yb));
+ rt2 = Rect(Point(xt2,y),Point(xa2,yb));
+ ra2 = Rect(Point(xa2,y),Point(xr,yb));
+ a1kind = TRIleft;
+ a2kind = TRIright;
+ }
+ a1relief := ReliefRaised;
+ if(c.flags&CFscracta1)
+ a1relief = ReliefSunk;
+ a2relief := ReliefRaised;
+ if(c.flags&CFscracta2)
+ a2relief = ReliefSunk;
+ drawtriangle(win, ra1, a1kind, a1relief);
+ drawtriangle(win, ra2, a2kind, a2relief);
+ drawfill(win, rt1, Grey);
+ rs = rs.inset(2);
+ drawfill(win, rs, Grey);
+ rsrelief := ReliefRaised;
+ if(c.flags&CFactive)
+ rsrelief = ReliefSunk;
+ drawrelief(win, rs, rsrelief);
+ drawfill(win, rt2, Grey);
+ Canimimage =>
+ i := c.cur;
+ if(c.redraw)
+ i = 0;
+ else if(i > 0) {
+ iprev := i-1;
+ if(c.cim.mims[iprev].bgcolor != -1) {
+ i = iprev;
+ # get i back to before all "reset to previous"
+ # images (which will be skipped in following
+ # image drawing loop)
+ while(i > 0 && c.cim.mims[i].bgcolor == -2)
+ i--;
+ }
+ }
+ bgi := colorimage(c.bg.color);
+ if(c.bg.image != nil && c.bg.image.ci != nil && len c.bg.image.ci.mims > 0)
+ bgi = c.bg.image.ci.mims[0].im;
+ for( ; i <= c.cur; i++) {
+ mim := c.cim.mims[i];
+ if(i > 0 && i < c.cur && mim.bgcolor == -2)
+ continue;
+ p := c.r.min.add(mim.origin);
+ r := mim.im.r;
+ r = Rect(p, p.add(Point(r.dx(), r.dy())));
+
+ # IE takes "clear-to-background" disposal method to mean
+ # clear to background of HTML page, ignoring any background
+ # color specified in the GIF.
+ # IE clears to background before frame 0
+ if(i == 0)
+ win.draw(c.r, bgi, nil, zp);
+
+ if(i != c.cur && mim.bgcolor >= 0)
+ win.draw(r, bgi, nil, zp);
+ else
+ win.draw(r, mim.im, mim.mask, zp);
+ }
+ Clabel =>
+ p := c.r.min.add(Point(0,ENTVMARGIN));
+ win.text(p, colorimage(Black), zp, fonts[DefFnt].f, c.s);
+ }
+ if(flush) {
+ if (ctl.popup != nil)
+ ctl.popup.flush(ctl.r);
+ else
+ G->flush(ctl.r);
+ }
+ win.clipr = oclipr;
+}
+
+# Break s up into substrings that fit in width availw
+# when printing with font fnt.
+# The second returned array contains the indexes into the original
+# string where the corresponding line starts (which might not be simply
+# the sum of the preceding lines because of cr/lf's in the original string
+# which are omitted from the lines array.
+# Empty lines (ending in cr) get put into the array as empty strings.
+# The start indices array has an entry for the phantom next line, to avoid
+# the need for special cases in the rest of the code.
+wrapstring(fnt: ref Font, s: string, availw: int) : (array of string, array of int)
+{
+ sl : list of (string, int) = nil;
+ sw := fnt.width(s);
+ n := 0;
+ k := 0; # index into original s where current s starts
+ origlen := len s;
+ done := 0;
+ while(!done) {
+ kincr := 0;
+ s1, s2: string;
+ if(s == "") {
+ s1 = s;
+ done = 1;
+ }
+ else {
+ # if any newlines in s1, it's a forced break
+ # (and newlines aren't to appear in result)
+ (s1, s2) = S->splitl(s, "\n");
+ if(s2 != nil && fnt.width(s1) <= availw) {
+ s = s2[1:];
+ sw = fnt.width(s);
+ kincr = (len s1) + 1;
+ }
+ else if(sw <= availw) {
+ s1 = s;
+ done = 1;
+ }
+ else {
+ (s1, nil, s, sw) = breakstring(s, sw, fnt, availw, 0);
+ kincr = len s1;
+ if(s == "")
+ done = 1;
+ }
+ }
+ sl = (s1, k) :: sl;
+ k += kincr;
+ n++;
+ }
+ # reverse sl back to original order
+ lines := array[n] of string;
+ linestarts := array[n+1] of int;
+ linestarts[n] = origlen;
+ while(sl != nil) {
+ (ss, nn) := hd sl;
+ lines[--n] = ss;
+ linestarts[n] = nn;
+ sl = tl sl;
+ }
+ return (lines, linestarts);
+}
+
+normalsel(sel : (int, int)) : (int, int)
+{
+ (s, e) := sel;
+ if (s > e)
+ (e, s) = sel;
+ return (s, e);
+}
+
+selsegs(n, s, e : int) : list of (int, int, int)
+{
+ if (e < 0 || s > n)
+ # selection is not in 0..n
+ return (0, n, 0) :: nil;
+
+ if (e > n) {
+ # second half of string is selected
+ if (s <= 0)
+ return (0, n, 1) :: nil;
+ return (0, s, 0) :: (s, n, 1) :: nil;
+ }
+
+ if (s < 0) {
+ # first half of string is selected
+ if (e >= n)
+ return (0, n, 1) :: nil;
+ return (0, e, 1) :: (e, n, 0) :: nil;
+ }
+ # middle section of string is selected
+ return (0, s, 0) :: (s, e, 1) :: (e, n, 0) :: nil;
+}
+
+# Figure out in which area of scrollbar, if any, p lies.
+# Then use p and mtype from mouse event to return desired action.
+Control.entryset(c: self ref Control, s: string)
+{
+ pick e := c {
+ Centry =>
+ e.s = s;
+ e.sel = (0, 0);
+ e.left = 0;
+ # calculate scroll bar settings
+ if (e.linewrap && e.scr != nil) {
+ (lines, nil, nil, nil) := entrywrapcalc(e);
+ nlines := len lines;
+ ny := (e.r.dy() - 2 * ENTVMARGIN)/ctllinespace;
+ e.scr.scrollset(0, ny, (nlines - 1), nlines, 0);
+ }
+ }
+}
+
+entryupdown(e: ref Control.Centry, cur : int, delta : int) : int
+{
+ e.sel = (cur, cur);
+ (lines, linestarts, topline, cursline) := entrywrapcalc(e);
+ newl := cursline + delta;
+ if (newl < 0 || newl >= len lines)
+ return cur;
+
+ fnt := fonts[CtlFnt].f;
+ x := cur - linestarts[cursline];
+ w := fnt.width(lines[cursline][0:x]);
+ l := lines[newl];
+ if (len l == 0)
+ return linestarts[newl];
+ prevw := fnt.width(l);
+ curw := prevw;
+ for (ix := len l - 1; ix > 0 ; ix--) {
+ prevw = curw;
+ curw = fnt.width(l[:ix]);
+ if (curw < w)
+ break;
+ }
+ # decide on closest (curw <= w <= prevw)
+ if (prevw-w <= w - curw)
+ # closer to rhs
+ ix++;
+ return linestarts[newl]+ix;
+}
+
+# delete given range of characters, and redraw
+entrydelrange(e: ref Control.Centry, istart, iend: int)
+{
+ n := iend - istart;
+ (sels, sele) := normalsel(e.sel);
+ if(n > 0) {
+ e.s = e.s[0:istart] + e.s[iend:];
+
+ if(sels > istart) {
+ if(sels < iend)
+ sels = istart;
+ else
+ sels -= n;
+ }
+ if (sele > istart) {
+ if (sele < iend)
+ sele = istart;
+ else
+ sele -= n;
+ }
+
+ if(e.left > istart)
+ e.left = max(istart-1, 0);
+ e.sel = (sels, sele);
+ entryscroll(e);
+ }
+}
+
+snarf : string;
+entrysetsnarf(e: ref Control.Centry)
+{
+ if (e.s == nil)
+ return;
+ s := e.s;
+ (sels, sele) := normalsel(e.sel);
+ if (sels != sele)
+ s = e.s[sels:sele];
+
+ f := sys->open("/chan/snarf", sys->OWRITE);
+ if (f == nil)
+ snarf = s;
+ else {
+ data := array of byte s;
+ sys->write(f, data, len data);
+ }
+}
+
+entryinsertsnarf(e: ref Control.Centry)
+{
+ f := sys->open("/chan/snarf", sys->OREAD);
+ if(f != nil) {
+ buf := array[sys->ATOMICIO] of byte;
+ n := sys->read(f, buf, len buf);
+ if(n > 0) {
+ # trim a trailing newline, as a service...
+ if(buf[n-1] == byte '\n')
+ n--;
+ }
+ snarf = "";
+ if (n > 0)
+ snarf = string buf[:n];
+ }
+
+ if (snarf != nil) {
+ (sels, sele) := normalsel(e.sel);
+ if (sels != sele) {
+ entrydelrange(e, sels, sele);
+ (sels, sele) = e.sel;
+ }
+ lhs, rhs : string;
+ if (sels > 0)
+ lhs = e.s[:sels];
+ if (sels < len e.s)
+ rhs = e.s[sels:];
+ e.entryset(lhs + snarf + rhs);
+ e.sel = (len lhs, len lhs + len snarf);
+ }
+}
+
+# make sure can see cursor and following char or two
+entryscroll(e: ref Control.Centry)
+{
+ s := e.s;
+ slen := len s;
+ if(e.flags&CFsecure) {
+ for(i := 0; i < slen; i++)
+ s[i] = '*';
+ }
+ if(e.linewrap) {
+ # For multiple line entries, c.left is the char
+ # at the beginning of the topmost visible line,
+ # and we just want to scroll to make sure that
+ # the line with the cursor is visible
+ (lines, linestarts, topline, cursline) := entrywrapcalc(e);
+ vislines := (e.r.dy()-2*ENTVMARGIN) / ctllinespace;
+ nlines := len linestarts;
+ if(cursline < topline)
+ topline = cursline;
+ else {
+ if(cursline >= topline+vislines)
+ topline = cursline-vislines+1;
+ if (topline + vislines >= nlines)
+ topline = max(0, (nlines-1) - vislines);
+ }
+ e.left = linestarts[topline];
+ if (e.scr != nil)
+ e.scr.scrollset(topline, topline+vislines, nlines-1, nlines, 1);
+ }
+ else {
+ (nil, sele) := e.sel;
+ # sele is always the drag point
+ if(sele < e.left)
+ e.left = sele;
+ else if(sele > e.left) {
+ fnt := fonts[CtlFnt].f;
+ wantw := e.r.dx() -2*ENTHMARGIN; # - 2*ctlspspace;
+ while(e.left < sele-1) {
+ w := fnt.width(e.s[e.left:sele]);
+ if(w < wantw)
+ break;
+ e.left++;
+ }
+ }
+ }
+}
+
+# Given e, a Centry with line wrapping,
+# return (wrapped lines, line start indices, line# of top displayed line, line# containing cursor).
+entrywrapcalc(e: ref Control.Centry) : (array of string, array of int, int, int)
+{
+ s := e.s;
+ if(e.flags&CFsecure) {
+ for(i := 0; i < len s; i++)
+ s[i] = '*';
+ }
+ (nil, sele) := e.sel;
+ textw := e.r.dx()-2*ENTHMARGIN;
+ if (e.scr != nil)
+ textw -= SCRFBREADTH;
+ (lines, linestarts) := wrapstring(fonts[CtlFnt].f, s, textw);
+ topline := 0;
+ cursline := 0;
+ for(i := 0; i < len lines; i++) {
+ s = lines[i];
+ i1 := linestarts[i];
+ i2 := linestarts[i+1];
+ if(e.left >= i1 && e.left < i2)
+ topline = i;
+ if(sele >= i1 && sele < i2)
+ cursline = i;
+ }
+ if(sele == linestarts[len lines])
+ cursline = len lines - 1;
+ return (lines, linestarts, topline, cursline);
+}
+
+Lay.new(targwidth: int, just: byte, margin: int, bg: Background) : ref Lay
+{
+ ans := ref Lay(Line.new(), Line.new(),
+ targwidth, 0, 0, margin, nil, bg, just, byte 0);
+ if(ans.targetwidth < 0)
+ ans.targetwidth = 0;
+ ans.start.pos = Point(margin, margin);
+ ans.start.next = ans.end;
+ ans.end.prev = ans.start;
+ # dummy item at end so ans.end will have correct y coord
+ it := Item.newspacer(ISPnull, 0);
+ it.state = IFbrk|IFcleft|IFcright;
+ ans.end.items = it;
+ return ans;
+}
+
+Line.new() : ref Line
+{
+ return ref Line(
+ nil, nil, nil, # items, next, prev
+ zp, # pos
+ 0, 0, 0, # width, height, ascent
+ byte 0); # flags
+}
+
+Loc.new() : ref Loc
+{
+ return ref Loc(array[10] of Locelem, 0, zp); # le, n, pos
+}
+
+Loc.add(loc: self ref Loc, kind: int, pos: Point)
+{
+ if(loc.n == len loc.le) {
+ newa := array[len loc.le + 10] of Locelem;
+ newa[0:] = loc.le;
+ loc.le = newa;
+ }
+ loc.le[loc.n].kind = kind;
+ loc.le[loc.n].pos = pos;
+ loc.n++;
+}
+
+# return last frame in loc's path
+Loc.lastframe(loc: self ref Loc) : ref Frame
+{
+ if (loc == nil)
+ return nil;
+ for(i := loc.n-1; i >=0; i--)
+ if(loc.le[i].kind == LEframe)
+ return loc.le[i].frame;
+ return nil;
+}
+
+Loc.print(loc: self ref Loc, msg: string)
+{
+ sys->print("%s: Loc with %d components, pos=(%d,%d)\n", msg, loc.n, loc.pos.x, loc.pos.y);
+ for(i := 0; i < loc.n; i++) {
+ case loc.le[i].kind {
+ LEframe =>
+ sys->print("frame %x\n", loc.le[i].frame);
+ LEline =>
+ sys->print("line %x\n", loc.le[i].line);
+ LEitem =>
+ sys->print("item: %x", loc.le[i].item);
+ loc.le[i].item.print();
+ LEtablecell =>
+ sys->print("tablecell: %x, cellid=%d\n", loc.le[i].tcell, loc.le[i].tcell.cellid);
+ LEcontrol =>
+ sys->print("control %x\n", loc.le[i].control);
+ }
+ }
+}
+
+Sources.new(m : ref Source) : ref Sources
+{
+ srcs := ref Sources;
+ srcs.main = m;
+ return srcs;
+}
+
+Sources.add(srcs: self ref Sources, s: ref Source, required: int)
+{
+ if (required) {
+ CU->assert(srcs.reqd == nil);
+ srcs.reqd = s;
+ } else
+ srcs.srcs = s :: srcs.srcs;
+}
+
+Sources.done(srcs: self ref Sources, s: ref Source)
+{
+ if (s == srcs.main) {
+ if (srcs.reqd != nil) {
+ sys->print("FREEING MAIN WHEN REQD != nil\n");
+ if (s.bs == nil)
+ sys->print("s.bs == nil\n");
+ else
+ sys->print("main.eof = %d main.lim = %d, main.edata = %d\n", s.bs.eof, s.bs.lim, s.bs.edata);
+ }
+ srcs.main = nil;
+ }
+ else if (s == srcs.reqd)
+ srcs.reqd = nil;
+ else {
+ new : list of ref Source;
+ for (old := srcs.srcs; old != nil; old = tl old) {
+ src := hd old;
+ if (src == s)
+ continue;
+ new = src :: new;
+ }
+ srcs.srcs = new;
+ }
+}
+
+Sources.waitsrc(srcs: self ref Sources) : ref Source
+{
+ if (srcs == nil)
+ return nil;
+
+ bsl : list of ref ByteSource;
+
+ if (srcs.reqd == nil && srcs.main != nil) {
+ pick s := srcs.main {
+ Shtml =>
+ if (s.itsrc.toks != nil || s.itsrc.reqddata != nil)
+ return s;
+ }
+ }
+
+ # always check for subordinates
+ for (sl := srcs.srcs; sl != nil; sl = tl sl)
+ bsl = (hd sl).bs :: bsl;
+ # reqd is taken in preference to main source as main
+ # cannot be processed until we have the whole of reqd
+ if (srcs.reqd != nil)
+ bsl = srcs.reqd.bs :: bsl;
+ else if (srcs.main != nil)
+ bsl = srcs.main.bs :: bsl;
+ if (bsl == nil)
+ return nil;
+ bs : ref ByteSource;
+ for (;;) {
+ bs = CU->waitreq(bsl);
+ if (srcs.reqd == nil || srcs.reqd.bs != bs)
+ break;
+ # only interested in reqd if we have got it all
+ if (bs.err != "" || bs.eof)
+ return srcs.reqd;
+ }
+ if (srcs.main != nil && srcs.main.bs == bs)
+ return srcs.main;
+ found : ref Source;
+ for(sl = srcs.srcs; sl != nil; sl = tl sl) {
+ s := hd sl;
+ if(s.bs == bs) {
+ found = s;
+ break;
+ }
+ }
+ CU->assert(found != nil);
+ return found;
+}
+
+# spawned to animate images in frame f
+animproc(f: ref Frame)
+{
+ f.animpid = sys->pctl(0, nil);
+ aits : list of ref Item = nil;
+ # let del be millisecs to sleep before next frame change
+ del := 10000000;
+ d : int;
+ for(il := f.doc.images; il != nil; il = tl il) {
+ it := hd il;
+ pick i := it {
+ Iimage =>
+ ms := i.ci.mims;
+ if(len ms > 1) {
+ loc := f.find(zp, it);
+ if(loc == nil) {
+ # could be background, I suppose -- don't animate it
+ if(dbg)
+ sys->print("couldn't find item for animated image\n");
+ continue;
+ }
+ p := loc.le[loc.n-1].pos;
+ p.x += int i.hspace + int i.border;
+ # BUG: should get background from least enclosing layout
+ ctl := Control.newanimimage(f, i.ci, f.layout.background);
+ ctl.r = ctl.r.addpt(p);
+ i.ctlid = f.addcontrol(ctl);
+ d = ms[0].delay;
+ if(dbg)
+ sys->print("added anim ctl %d for image %s, initial delay %d\n",
+ i.ctlid, i.ci.src.tostring(), d);
+ aits = it :: aits;
+ if(d < del)
+ del = d;
+ }
+ }
+ }
+ if(aits == nil)
+ return;
+ tot := big 0;
+ for(;;) {
+ sys->sleep(del);
+ tot = tot + big del;
+ newdel := 10000000;
+ for(al := aits; al != nil; al = tl al) {
+ it := hd al;
+ pick i := hd al {
+ Iimage =>
+ ms := i.ci.mims;
+ pick c := f.controls[i.ctlid] {
+ Canimimage =>
+ m := ms[c.cur];
+ d = m.delay;
+ if(d > 0)
+ d -= int (tot - c.ts);
+ if(d == 0) {
+ # advance to next frame and show it
+ c.cur++;
+ if(c.cur == len ms)
+ c.cur = 0;
+ d = ms[c.cur].delay;
+ c.ts = tot;
+ c.draw(1);
+ }
+ if(d < newdel)
+ newdel = d;
+ }
+ }
+ }
+ del = newdel;
+ }
+}