ref: 606901dc5da9cb09acb5593c5cf74ce1b52ca6e2
dir: /appl/charon/layout.b/
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) raise "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) raise "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) raise "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) raise 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; if(c.col >= len tab.cols) 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) raise "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) raise 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) l = 1; if(l < 0) raise "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; } }