1implement Layout; 2 3include "common.m"; 4include "keyboard.m"; 5 6sys: Sys; 7CU: CharonUtils; 8 ByteSource, MaskedImage, CImage, ImageCache, max, min, 9 White, Black, Grey, DarkGrey, LightGrey, Blue, Navy, Red, Green, DarkRed: import CU; 10 11D: Draw; 12 Point, Rect, Font, Image, Display: import D; 13S: String; 14T: StringIntTab; 15U: Url; 16 Parsedurl: import U; 17I: Img; 18 ImageSource: import I; 19J: Script; 20E: Events; 21 Event: import E; 22G: Gui; 23 Popup: import G; 24B: Build; 25 26# B : Build, declared in layout.m so main program can use it 27 Item, ItemSource, 28 IFbrk, IFbrksp, IFnobrk, IFcleft, IFcright, IFwrap, IFhang, 29 IFrjust, IFcjust, IFsmap, IFindentshift, IFindentmask, 30 IFhangmask, 31 Voffbias, 32 ISPnull, ISPvline, ISPhspace, ISPgeneral, 33 Align, Dimen, Formfield, Option, Form, 34 Table, Tablecol, Tablerow, Tablecell, 35 Anchor, DestAnchor, Map, Area, Kidinfo, Docinfo, 36 Anone, Aleft, Acenter, Aright, Ajustify, Achar, Atop, Amiddle, 37 Abottom, Abaseline, 38 Dnone, Dpixels, Dpercent, Drelative, 39 Ftext, Fpassword, Fcheckbox, Fradio, Fsubmit, Fhidden, Fimage, 40 Freset, Ffile, Fbutton, Fselect, Ftextarea, 41 Background, 42 FntR, FntI, FntB, FntT, NumStyle, 43 Tiny, Small, Normal, Large, Verylarge, NumSize, NumFnt, DefFnt, 44 ULnone, ULunder, ULmid, 45 FRnoresize, FRnoscroll, FRhscroll, FRvscroll, 46 FRhscrollauto, FRvscrollauto 47 : import B; 48 49# font stuff 50Fontinfo : adt { 51 name: string; 52 f: ref Font; 53 spw: int; # width of a space in this font 54}; 55 56fonts := array[NumFnt] of { 57 FntR*NumSize+Tiny => Fontinfo("/fonts/charon/plain.tiny.font", nil, 0), 58 FntR*NumSize+Small => ("/fonts/charon/plain.small.font", nil, 0), 59 FntR*NumSize+Normal => ("/fonts/charon/plain.normal.font", nil, 0), 60 FntR*NumSize+Large => ("/fonts/charon/plain.large.font", nil, 0), 61 FntR*NumSize+Verylarge => ("/fonts/charon/plain.vlarge.font", nil, 0), 62 63 FntI*NumSize+Tiny => ("/fonts/charon/italic.tiny.font", nil, 0), 64 FntI*NumSize+Small => ("/fonts/charon/italic.small.font", nil, 0), 65 FntI*NumSize+Normal => ("/fonts/charon/italic.normal.font", nil, 0), 66 FntI*NumSize+Large => ("/fonts/charon/italic.large.font", nil, 0), 67 FntI*NumSize+Verylarge => ("/fonts/charon/italic.vlarge.font", nil, 0), 68 69 FntB*NumSize+Tiny => ("/fonts/charon/bold.tiny.font", nil, 0), 70 FntB*NumSize+Small => ("/fonts/charon/bold.small.font", nil, 0), 71 FntB*NumSize+Normal => ("/fonts/charon/bold.normal.font", nil, 0), 72 FntB*NumSize+Large => ("/fonts/charon/bold.large.font", nil, 0), 73 FntB*NumSize+Verylarge => ("/fonts/charon/bold.vlarge.font", nil, 0), 74 75 FntT*NumSize+Tiny => ("/fonts/charon/cw.tiny.font", nil, 0), 76 FntT*NumSize+Small => ("/fonts/charon/cw.small.font", nil, 0), 77 FntT*NumSize+Normal => ("/fonts/charon/cw.normal.font", nil, 0), 78 FntT*NumSize+Large => ("/fonts/charon/cw.large.font", nil, 0), 79 FntT*NumSize+Verylarge => ("/fonts/charon/cw.vlarge.font", nil, 0) 80}; 81 82# Seems better to use a slightly smaller font in Controls, to match other browsers 83CtlFnt: con (FntR*NumSize+Small); 84 85# color stuff. have hash table mapping RGB values to D->Image for that color 86Colornode : adt { 87 rgb: int; 88 im: ref Image; 89 next: ref Colornode; 90}; 91 92# Source of info for page (html, image, etc.) 93Source: adt { 94 bs: ref ByteSource; 95 redirects: int; 96 pick { 97 Srequired or 98 Shtml => 99 itsrc: ref ItemSource; 100 Simage => 101 ci: ref CImage; 102 itl: list of ref Item; 103 imsrc: ref ImageSource; 104 } 105}; 106 107Sources: adt { 108 main: ref Source; 109 reqd: ref Source; 110 srcs: list of ref Source; 111 112 new: fn(m : ref Source) : ref Sources; 113 add: fn(srcs: self ref Sources, s: ref Source, required: int); 114 done: fn(srcs: self ref Sources, s: ref Source); 115 waitsrc: fn(srcs : self ref Sources) : ref Source; 116}; 117 118NCOLHASH : con 19; # 19 checked for standard colors: only 1 collision 119colorhashtab := array[NCOLHASH] of ref Colornode; 120 121# No line break should happen between adjacent characters if 122# they are 'wordchars' : set in this array, or outside the array range. 123# We include certain punctuation characters that are not traditionally 124# regarded as 'word' characters. 125wordchar := array[16rA0] of { 126 '!' => byte 1, 127 '0'=>byte 1, '1'=>byte 1, '2'=>byte 1, '3'=>byte 1, '4'=>byte 1, 128 '5'=>byte 1, '6'=>byte 1, '7'=>byte 1, '8'=>byte 1, '9'=>byte 1, 129 ':'=>byte 1, ';' => byte 1, 130 '?' => byte 1, 131 'A'=>byte 1, 'B'=>byte 1, 'C'=>byte 1, 'D'=>byte 1, 'E'=>byte 1, 'F'=>byte 1, 132 'G'=>byte 1, 'H'=>byte 1, 'I'=>byte 1, 'J'=>byte 1, 'K'=>byte 1, 'L'=>byte 1, 133 'M'=>byte 1, 'N'=>byte 1, 'O'=>byte 1, 'P'=>byte 1, 'Q'=>byte 1, 'R'=>byte 1, 134 'S'=>byte 1, 'T'=>byte 1, 'U'=>byte 1, 'V'=>byte 1, 'W'=>byte 1, 'X'=>byte 1, 135 'Y'=>byte 1, 'Z'=>byte 1, 136 'a'=>byte 1, 'b'=>byte 1, 'c'=>byte 1, 'd'=>byte 1, 'e'=>byte 1, 'f'=>byte 1, 137 'g'=>byte 1, 'h'=>byte 1, 'i'=>byte 1, 'j'=>byte 1, 'k'=>byte 1, 'l'=>byte 1, 138 'm'=>byte 1, 'n'=>byte 1, 'o'=>byte 1, 'p'=>byte 1, 'q'=>byte 1, 'r'=>byte 1, 139 's'=>byte 1, 't'=>byte 1, 'u'=>byte 1, 'v'=>byte 1, 'w'=>byte 1, 'x'=>byte 1, 140 'y'=>byte 1, 'z'=>byte 1, 141 '_'=>byte 1, 142 '\''=>byte 1, '"'=>byte 1, '.'=>byte 1, ','=>byte 1, '('=>byte 1, ')'=>byte 1, 143 * => byte 0 144}; 145 146TABPIX: con 30; # number of pixels in a tab 147CAPSEP: con 5; # number of pixels separating tab from caption 148SCRBREADTH: con 14; # scrollbar breadth (normal) 149SCRFBREADTH: con 14; # scrollbar breadth (inside child frame or select control) 150FRMARGIN: con 0; # default margin around frames 151RULESP: con 7; # extra space before and after rules 152POPUPLINES: con 12; # number of lines in popup select list 153MINSCR: con 6; # min size in pixels of scrollbar drag widget 154SCRDELTASF: con 10000; # fixed-point scale factor for scrollbar per-pixel step 155 156# all of the following include room for relief 157CBOXWID: con 14; # check box width 158CBOXHT: con 12; # check box height 159ENTVMARGIN : con 4; # vertical margin inside entry box 160ENTHMARGIN : con 6; # horizontal margin inside entry box 161SELMARGIN : con 4; # margin inside select control 162BUTMARGIN: con 4; # margin inside button control 163PBOXWID: con 10; # progress box width 164PBOXHT: con 16; # progress box height 165PBOXBD: con 2; # progress box border width 166 167TABLEMAXTARGET: con 2000; # targetwidth to get max width of table cell 168TABLEFLOATTARGET: con 1; # targetwidth for floating tables 169 170SELBG: con 16r00FFFF; # aqua 171 172ARPAUSE : con 500; # autorepeat initial delay (ms) 173ARTICK : con 100; # autorepeat tick delay (ms) 174 175display: ref D->Display; 176 177dbg := 0; 178dbgtab := 0; 179dbgev := 0; 180linespace := 0; 181lineascent := 0; 182charspace := 0; 183spspace := 0; 184ctllinespace := 0; 185ctllineascent := 0; 186ctlcharspace := 0; 187ctlspspace := 0; 188frameid := 0; 189zp := Point(0,0); 190 191init(cu: CharonUtils) 192{ 193 CU = cu; 194 sys = load Sys Sys->PATH; 195 D = load Draw Draw->PATH; 196 S = load String String->PATH; 197 T = load StringIntTab StringIntTab->PATH; 198 U = load Url Url->PATH; 199 if (U != nil) 200 U->init(); 201 E = cu->E; 202 G = cu->G; 203 I = cu->I; 204 J = cu->J; 205 B = cu->B; 206 display = G->display; 207 208 # make sure default and control fonts are loaded 209 getfont(DefFnt); 210 fnt := fonts[DefFnt].f; 211 linespace = fnt.height; 212 lineascent = fnt.ascent; 213 charspace = fnt.width("a"); # a kind of average char width 214 spspace = fonts[DefFnt].spw; 215 getfont(CtlFnt); 216 fnt = fonts[CtlFnt].f; 217 ctllinespace = fnt.height; 218 ctllineascent = fnt.ascent; 219 ctlcharspace = fnt.width("a"); 220 ctlspspace = fonts[CtlFnt].spw; 221} 222 223stringwidth(s: string): int 224{ 225 return fonts[DefFnt].f.width(s)/charspace; 226} 227 228# Use bsmain to fill frame f. 229# Return buffer containing source when done. 230layout(f: ref Frame, bsmain: ref ByteSource, linkclick: int) : array of byte 231{ 232 dbg = int (CU->config).dbg['l']; 233 dbgtab = int (CU->config).dbg['t']; 234 dbgev = int (CU->config).dbg['e']; 235 if(dbgev) 236 CU->event("LAYOUT", 0); 237 sources : ref Sources; 238 hdr := bsmain.hdr; 239 auth := ""; 240 url : ref Parsedurl; 241 if (bsmain.req != nil) { 242 auth = bsmain.req.auth; 243 url = bsmain.req.url; 244 } 245# auth := bsmain.req.auth; 246 ans : array of byte = nil; 247 di := Docinfo.new(); 248 if(linkclick && f.doc != nil) 249 di.referrer = f.doc.src; 250 f.reset(); 251 f.doc = di; 252 di.frameid = f.id; 253 di.src = hdr.actual; 254 di.base = hdr.base; 255 di.refresh = hdr.refresh; 256 if (hdr.chset != nil) 257 di.chset = hdr.chset; 258 di.lastModified = hdr.lastModified; 259 if(J != nil) 260 J->havenewdoc(f); 261 oclipr := f.cim.clipr; 262 f.cim.clipr = f.cr; 263 if(f.framebd != 0) { 264 f.cr = f.r.inset(2); 265 drawborder(f.cim, f.cr, 2, DarkGrey); 266 } 267 fillbg(f, f.cr); 268 G->flush(f.cr); 269 f.cim.clipr = oclipr; 270 if(f.flags&FRvscroll) 271 createvscroll(f); 272 if(f.flags&FRhscroll) 273 createhscroll(f); 274 l := Lay.new(f.cr.dx(), Aleft, f.marginw, di.background); 275 f.layout = l; 276 anyanim := 0; 277 if(hdr.mtype == CU->TextHtml || hdr.mtype == CU->TextPlain) { 278 itsrc := ItemSource.new(bsmain, f, hdr.mtype); 279 sources = Sources.new(ref Source.Shtml(bsmain, 0, itsrc)); 280 } 281 else { 282 # for now, must be supported image type 283 if(!I->supported(hdr.mtype)) { 284 sys->print("Need to implement something: source isn't supported image type\n"); 285 return nil; 286 } 287 imsrc := I->ImageSource.new(bsmain, 0, 0); 288 ci := CImage.new(url, nil, 0, 0); 289 simage := ref Source.Simage(bsmain, 0, ci, nil, imsrc); 290 sources = Sources.new(simage); 291 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); 292 di.images = it :: nil; 293 appenditems(f, l, it); 294 simage.itl = it :: nil; 295 } 296 while ((src := sources.waitsrc()) != nil) { 297 if(dbgev) 298 CU->event("LAYOUT GETSOMETHING", 0); 299 bs := src.bs; 300 freeit := 0; 301 if(bs.err != "") { 302 if(dbg) 303 sys->print("error getting %s: %s\n", bs.req.url.tostring(), bs.err); 304 pick s := src { 305 Srequired => 306 s.itsrc.reqddata = array [0] of byte; 307 sources.done(src); 308 CU->freebs(bs); 309 src.bs = nil; 310 continue; 311 } 312 freeit = 1; 313 } 314 else { 315 if(bs.hdr != nil && !bs.seenhdr) { 316 (use, error, challenge, newurl) := CU->hdraction(bs, 0, src.redirects); 317 if(challenge != nil) { 318 sys->print("Need to implement authorization credential dialog\n"); 319 error = "Need authorization"; 320 use = 0; 321 } 322 if(error != "" && dbg) 323 sys->print("subordinate error: %s\n", error); 324 if(newurl != nil) { 325 s := ref *src; 326 freeit = 1; 327 pick ps := src { 328 Shtml or Srequired => 329 sys->print("unexpected redirect of subord\n"); 330 Simage => 331 newci := CImage.new(newurl, nil, ps.ci.width, ps.ci.height); 332 for(itl := ps.itl; itl != nil ; itl = tl itl) { 333 pick imi := hd itl { 334 Iimage => 335 imi.ci = newci; 336 } 337 } 338 news := ref Source.Simage(nil, 0, newci, ps.itl, nil); 339 sources.add(news, 0); 340 startimreq(news, auth); 341 } 342 } 343 if(!use) 344 freeit = 1; 345 } 346 if(!freeit) { 347 pick s := src { 348 Srequired or 349 Shtml => 350 if (tagof src == tagof Source.Srequired) { 351 s.itsrc.reqddata = bs.data; 352 sources.done(src); 353 CU->freebs(bs); 354 src.bs = nil; 355 continue; 356# src = sources.main; 357# CU->assert(src != nil); 358 } 359 itl := s.itsrc.getitems(); 360 if(di.kidinfo != nil) { 361 if(s.itsrc.kidstk == nil) { 362 layframeset(f, di.kidinfo); 363 G->flush(f.r); 364 freeit = 1; 365 } 366 } 367 else { 368 l.background = di.background; 369 anyanim |= addsubords(sources, di, auth); 370 if(itl != nil) { 371 appenditems(f, l, itl); 372 fixframegeom(f); 373 if(dbgev) 374 CU->event("LAYOUT_DRAWALL", 0); 375 f.dirty(f.totalr); 376 drawall(f); 377 } 378 } 379 if (s.itsrc.reqdurl != nil) { 380 news := ref Source.Srequired(nil, 0, s.itsrc); 381 sources.add(news, 1); 382 rbs := CU->startreq(ref CU->ReqInfo(s.itsrc.reqdurl, CU->HGet, nil, "", "")); 383 news.bs = rbs; 384 } else { 385 if (bs.eof && bs.lim == bs.edata && s.itsrc.toks == nil) 386 freeit = 1; 387 } 388 Simage => 389 (ret, mim) := s.imsrc.getmim(); 390 # mark it done even if error 391 s.ci.complete = ret; 392 if(ret == I->Mimerror) { 393 bs.err = s.imsrc.err; 394 freeit = 1; 395 } 396 else if(ret != I->Mimnone) { 397 if(s.ci.mims == nil) { 398 s.ci.mims = array[1] of { mim }; 399 s.ci.width = s.imsrc.width; 400 s.ci.height = s.imsrc.height; 401 if(ret == I->Mimdone && (CU->config).imagelvl <= CU->ImgNoAnim) 402 freeit = 1; 403 } 404 else { 405 n := len s.ci.mims; 406 if(mim != s.ci.mims[n-1]) { 407 newmims := array[n + 1] of ref MaskedImage; 408 newmims[0:] = s.ci.mims; 409 newmims[n] = mim; 410 s.ci.mims = newmims; 411 anyanim = 1; 412 } 413 } 414 if(s.ci.mims[0] == mim) 415 haveimage(f, s.ci, s.itl); 416 if(bs.eof && bs.lim == bs.edata) 417 (CU->imcache).add(s.ci); 418 } 419 if(!freeit && bs.eof && bs.lim == bs.edata) 420 freeit = 1; 421 } 422 } 423 } 424 if(freeit) { 425 if(bs == bsmain) 426 ans = bs.data[0:bs.edata]; 427 CU->freebs(bs); 428 src.bs = nil; 429 sources.done(src); 430 } 431 } 432 if(anyanim && (CU->config).imagelvl > CU->ImgNoAnim) 433 spawn animproc(f); 434 if(dbgev) 435 CU->event("LAYOUT_END", 0); 436 return ans; 437} 438 439# return value is 1 if found any existing images needed animation 440addsubords(sources: ref Sources, di: ref Docinfo, auth: string) : int 441{ 442 anyanim := 0; 443 if((CU->config).imagelvl == CU->ImgNone) 444 return anyanim; 445 newsims: list of ref Source.Simage = nil; 446 for(il := di.images; il != nil; il = tl il) { 447 it := hd il; 448 pick i := it { 449 Iimage => 450 if(i.ci.mims == nil) { 451 cachedci := (CU->imcache).look(i.ci); 452 if(cachedci != nil) { 453 i.ci = cachedci; 454 if(i.imwidth == 0) 455 i.imwidth = i.ci.width; 456 if(i.imheight == 0) 457 i.imheight = i.ci.height; 458 anyanim |= (len cachedci.mims > 1); 459 } 460 else { 461 sloop: 462 for(sl := sources.srcs; sl != nil; sl = tl sl) { 463 pick s := hd sl { 464 Simage => 465 if(s.ci.match(i.ci)) { 466 s.itl = it :: s.itl; 467 # want all items on list to share same ci; 468 # want most-specific dimension specs 469 iciw := i.ci.width; 470 icih := i.ci.height; 471 i.ci = s.ci; 472 if(s.ci.width == 0 && s.ci.height == 0) { 473 s.ci.width = iciw; 474 s.ci.height = icih; 475 } 476 break sloop; 477 } 478 } 479 } 480 if(sl == nil) { 481 # didn't find existing Source for this image 482 s := ref Source.Simage(nil, 0, i.ci, it:: nil, nil); 483 newsims = s :: newsims; 484 sources.add(s, 0); 485 } 486 } 487 } 488 } 489 } 490 # Start requests for new newsources. 491 # di.images are in last-in-document-first order, 492 # so newsources is in first-in-document-first order (good order to load in). 493 for(sl := newsims; sl != nil; sl = tl sl) 494 startimreq(hd sl, auth); 495 return anyanim; 496} 497 498startimreq(s: ref Source.Simage, auth: string) 499{ 500 if(dbgev) 501 CU->event(sys->sprint("LAYOUT STARTREQ %s", s.ci.src.tostring()), 0); 502 bs := CU->startreq(ref CU->ReqInfo(s.ci.src, CU->HGet, nil, auth, "")); 503 s.bs = bs; 504 s.imsrc = I->ImageSource.new(bs, s.ci.width, s.ci.height); 505} 506 507createvscroll(f: ref Frame) 508{ 509 breadth := SCRBREADTH; 510 if(f.parent != nil) 511 breadth = SCRFBREADTH; 512 length := f.cr.dy(); 513 if(f.flags&FRhscroll) 514 length -= breadth; 515 f.vscr = Control.newscroll(f, 1, length, breadth); 516 f.vscr.r = f.vscr.r.addpt(Point(f.cr.max.x-breadth, f.cr.min.y)); 517 f.cr.max.x -= breadth; 518 if(f.cr.dx() <= 2*f.marginw) 519 raise "EXInternal: frame too small for layout"; 520 f.vscr.draw(1); 521} 522 523createhscroll(f: ref Frame) 524{ 525 breadth := SCRBREADTH; 526 if(f.parent != nil) 527 breadth = SCRFBREADTH; 528 length := f.cr.dx(); 529 x := f.cr.min.x; 530 f.hscr = Control.newscroll(f, 0, length, breadth); 531 f.hscr.r = f.hscr.r.addpt(Point(x,f.cr.max.y-breadth)); 532 f.cr.max.y -= breadth; 533 if(f.cr.dy() <= 2*f.marginh) 534 raise "EXInternal: frame too small for layout"; 535 f.hscr.draw(1); 536} 537 538# Call after a change to f.layout or f.viewr.min to fix totalr and viewr 539# (We are to leave viewr.min unchanged, if possible, as 540# user might be scrolling). 541fixframegeom(f: ref Frame) 542{ 543 l := f.layout; 544 if(dbg) 545 sys->print("fixframegeom, layout width=%d, height=%d\n", l.width, l.height); 546 crwidth := f.cr.dx(); 547 crheight := f.cr.dy(); 548 layw := max(l.width, crwidth); 549 layh := max(l.height, crheight); 550 f.totalr.max = Point(layw, layh); 551 crchanged := 0; 552 n := l.height+l.margin-crheight; 553 if(n > 0 && f.vscr == nil && (f.flags&FRvscrollauto)) { 554 createvscroll(f); 555 crchanged = 1; 556 crwidth = f.cr.dx(); 557 } 558 if(f.viewr.min.y > n) 559 f.viewr.min.y = max(0, n); 560 n = l.width+l.margin-crwidth; 561 if(!crchanged && n > 0 && f.hscr == nil && (f.flags&FRhscrollauto)) { 562 createhscroll(f); 563 crchanged = 1; 564 crheight = f.cr.dy(); 565 } 566 if(crchanged) { 567 relayout(f, l, crwidth, l.just); 568 fixframegeom(f); 569 return; 570 } 571 if(f.viewr.min.x > n) 572 f.viewr.min.x = max(0, n); 573 f.viewr.max.x = min(f.viewr.min.x+crwidth, layw); 574 f.viewr.max.y = min(f.viewr.min.y+crheight, layh); 575 if(f.vscr != nil) 576 f.vscr.scrollset(f.viewr.min.y, f.viewr.max.y, f.totalr.max.y, 0, 1); 577 if(f.hscr != nil) 578 f.hscr.scrollset(f.viewr.min.x, f.viewr.max.x, f.totalr.max.x, f.viewr.dx()/5, 1); 579} 580 581# The items its within f are Iimage items, 582# and its image, ci, now has at least a ci.mims[0], which may be partially 583# or fully filled. 584haveimage(f: ref Frame, ci: ref CImage, itl: list of ref Item) 585{ 586 if(dbgev) 587 CU->event("HAVEIMAGE", 0); 588 if(dbg) 589 sys->print("\nHAVEIMAGE src=%s w=%d h=%d\n", ci.src.tostring(), ci.width, ci.height); 590 # make all base images repl'd - makes handling backgrounds much easier 591 im := ci.mims[0].im; 592 im.repl = 1; 593 im.clipr = Rect((-16rFFFFFFF, -16r3FFFFFFF), (16r3FFFFFFF, 16r3FFFFFFF)); 594 dorelayout := 0; 595 for( ; itl != nil; itl = tl itl) { 596 it := hd itl; 597 pick i := it { 598 Iimage => 599 if (!(it.state & B->IFbkg)) { 600 # If i.imwidth and i.imheight are not both 0, the HTML specified the dimens. 601 # If one of them is 0, the other is to be scaled by the same factor; 602 # we have to relay the line in that case too. 603 if(i.imwidth == 0 || i.imheight == 0) { 604 i.imwidth = ci.width; 605 i.imheight = ci.height; 606 setimagedims(i); 607 loc := f.find(zp, it); 608 # sometimes the image was added to doc image list, but 609 # never made it to layout (e.g., because html bug prevented 610 # a table from being added). 611 # also, script-created images won't have items 612 if(loc != nil) { 613 f.layout.flags |= Lchanged; 614 markchanges(loc); 615 dorelayout = 1; 616 # Floats are assumed to be premeasured, so if there 617 # are any floats in the loc list, remeasure them 618 for(k := loc.n-1; k > 0; k--) { 619 if(loc.le[k].kind == LEitem) { 620 locit := loc.le[k].item; 621 pick fit := locit { 622 Ifloat => 623 pick xi := fit.item { 624 Iimage => 625 fit.height = fit.item.height; 626 Itable => 627 checktabsize(f, xi, TABLEFLOATTARGET); 628 } 629 } 630 } 631 } 632 } 633 } 634 } 635 if(dbg > 1) { 636 sys->print("\nhaveimage item: "); 637 it.print(); 638 } 639 } 640 } 641 if(dorelayout) { 642 relayout(f, f.layout, f.layout.targetwidth, f.layout.just); 643 fixframegeom(f); 644 } 645 f.dirty(f.totalr); 646 drawall(f); 647 if(dbgev) 648 CU->event("HAVEIMAGE_END", 0); 649} 650# For first layout of subelements, such as table cells. 651# After this, content items will be dispersed throughout resulting lay. 652# Return index into f.sublays. 653# (This roundabout way of storing sublayouts avoids pointers to Lay 654# in Build, so that all of the layout-related stuff can be in Layout 655# where it belongs.) 656sublayout(f: ref Frame, targetwidth: int, just: byte, bg: Background, content: ref Item) : int 657{ 658 if(dbg) 659 sys->print("sublayout, targetwidth=%d\n", targetwidth); 660 l := Lay.new(targetwidth, just, 0, bg); 661 if(f.sublayid >= len f.sublays) { 662 newsublays := array[len f.sublays + 30] of ref Lay; 663 newsublays[0:] = f.sublays; 664 f.sublays = newsublays; 665 } 666 id := f.sublayid; 667 f.sublays[id] = l; 668 f.sublayid++; 669 appenditems(f, l, content); 670 l.flags &= ~Lchanged; 671 if(dbg) 672 sys->print("after sublayout, width=%d\n", l.width); 673 return id; 674} 675 676# Relayout of lay, given a new target width or if something changed inside 677# or if the global justification for the layout changed. 678# Floats are hard: for now, just relay everything with floats temporarily 679# moved way down, if there are any floats. 680relayout(f: ref Frame, lay: ref Lay, targetwidth: int, just: byte) 681{ 682 if(dbg) 683 sys->print("relayout, targetwidth=%d, old target=%d, changed=%d\n", 684 targetwidth, lay.targetwidth, (lay.flags&Lchanged) != byte 0); 685 changeall := (lay.targetwidth != targetwidth || lay.just != just); 686 if(!changeall && !int(lay.flags&Lchanged)) 687 return; 688 if(lay.floats != nil) { 689 # move the current y positions of floats to a big value, 690 # so they don't contribute to floatw until after they've 691 # been encountered in current fixgeom 692 for(flist := lay.floats; flist != nil; flist = tl flist) { 693 ff := hd flist; 694 ff.y = 16r6fffffff; 695 } 696 changeall = 1; 697 } 698 lay.targetwidth = targetwidth; 699 lay.just = just; 700 lay.height = 0; 701 lay.width = 0; 702 if(changeall) 703 changelines(lay.start.next, lay.end); 704 fixgeom(f, lay, lay.start.next); 705 lay.flags &= ~Lchanged; 706 if(dbg) 707 sys->print("after relayout, width=%d\n", lay.width); 708} 709 710# Measure and append the items to the end of layout lay, 711# and fix the geometry. 712appenditems(f: ref Frame, lay: ref Lay, items: ref Item) 713{ 714 measure(f, items); 715 if(dbg) 716 items.printlist("appenditems, after measure"); 717 it := items; 718 if(it == nil) 719 return; 720 lprev := lay.end.prev; 721 l : ref Line; 722 lit := lastitem(lprev.items); 723 if(lit == nil || (it.state&IFbrk)) { 724 # start a new line after existing last line 725 l = Line.new(); 726 appendline(lprev, l); 727 l.items = it; 728 } 729 else { 730 # start appending items to existing last line 731 l = lprev; 732 lit.next = it; 733 } 734 l.flags |= Lchanged; 735 while(it != nil) { 736 nexti := it.next; 737 if(nexti == nil || (nexti.state&IFbrk)) { 738 it.next = nil; 739 fixgeom(f, lay, l); 740 if(nexti == nil) 741 break; 742 # now there may be multiple lines containing the 743 # items from l, but the one after the last is lay.end 744 l = Line.new(); 745 appendline(lay.end.prev, l); 746 l.flags |= Lchanged; 747 it = nexti; 748 l.items = it; 749 } 750 else 751 it = nexti; 752 } 753} 754 755# Fix up the geometry of line l and successors. 756# Assume geometry of previous line is correct. 757fixgeom(f: ref Frame, lay: ref Lay, l: ref Line) 758{ 759 while(l != nil) { 760 fixlinegeom(f, lay, l); 761 mergetext(l); 762 l = l.next; 763 } 764 lay.height = max(lay.height, lay.end.pos.y); 765} 766 767mergetext(l: ref Line) 768{ 769 lastit : ref Item; 770 for (it := l.items; it != nil; it = it.next) { 771 pick i := it { 772 Itext => 773 if (lastit == nil) 774 break; #pick 775 pick pi := lastit { 776 Itext => 777 # ignore item state flags as fixlinegeom() 778 # will have taken account of them. 779 if (pi.anchorid == i.anchorid && 780 pi.fnt == i.fnt && pi.fg == i.fg && pi.voff == i.voff && pi.ul == i.ul) { 781 # compatible - merge 782 pi.s += i.s; 783 pi.width += i.width; 784 pi.next = i.next; 785 continue; 786 } 787 } 788 } 789 lastit = it; 790 } 791} 792 793# Fix geom for one line. 794# This may change the overall lay.width, if there is no way 795# to fit the line into the target width. 796fixlinegeom(f: ref Frame, lay: ref Lay, l: ref Line) 797{ 798 lprev := l.prev; 799 y := lprev.pos.y + lprev.height; 800 it := l.items; 801 state := it.state; 802 if(dbg > 1) { 803 sys->print("\nfixlinegeom start, y=prev.y+prev.height=%d+%d=%d, changed=%d\n", 804 l.prev.pos.y, lprev.height, y, int (l.flags&Lchanged)); 805 if(dbg > 2) 806 it.printlist("items"); 807 else { 808 sys->print("first item: "); 809 it.print(); 810 } 811 } 812 if(state&IFbrk) { 813 y = pastbrk(lay, y, state); 814 if(dbg > 1 && y != lprev.pos.y + lprev.height) 815 sys->print("after pastbrk, line y is now %d\n", y); 816 } 817 l.pos.y = y; 818 lineh := max(l.height, linespace); 819 lfloatw := floatw(y, y+lineh, lay.floats, Aleft); 820 rfloatw := floatw(y, y+lineh, lay.floats, Aright); 821 if((l.flags&Lchanged) == byte 0) { 822 # possibly adjust lay.width 823 n := (lay.width-rfloatw)-(l.pos.x-lay.margin+l.width); 824 if(n < 0) 825 lay.width += -n; 826 return; 827 } 828 hang := (state&IFhangmask)*TABPIX/10; 829 linehang := hang; 830 hangtogo := hang; 831 indent := ((state&IFindentmask)>>IFindentshift)*TABPIX; 832 just := (state&(IFcjust|IFrjust)); 833 if(just == 0 && lay.just != Aleft) { 834 if(lay.just == byte Acenter) 835 just = IFcjust; 836 else if(lay.just == Aright) 837 just = IFrjust; 838 } 839 right := lay.targetwidth - lay.margin; 840 lwid := right - (lfloatw+rfloatw+indent+lay.margin); 841 if(lwid < 0) { 842 if (right - lwid > lay.width) 843 lay.width = right - lwid; 844 right += -lwid; 845 lwid = 0; 846 } 847 lwid += hang; 848 if(dbg > 1) { 849 sys->print("fixlinegeom, now y=%d, lfloatw=%d, rfloatw=%d, indent=%d, hang=%d, lwid=%d\n", 850 y, lfloatw, rfloatw, indent, hang, lwid); 851 } 852 w := 0; 853 lineh = 0; 854 linea := 0; 855 lastit: ref Item = nil; 856 nextfloats: list of ref Item.Ifloat = nil; 857 anystuff := 0; 858 eol := 0; 859 while(it != nil && !eol) { 860 if(dbg > 2) { 861 sys->print("fixlinegeom loop head, w=%d, loop item:\n", w); 862 it.print(); 863 } 864 state = it.state; 865 wrapping := int (state&IFwrap); 866 if(anystuff && (state&IFbrk)) 867 break; 868 checkw := 1; 869 if(hang && !(state&IFhangmask)) { 870 lwid -= hang; 871 hang = 0; 872 if(hangtogo > 0) { 873 # insert a null spacer item 874 spaceit := Item.newspacer(ISPgeneral, 0); 875 spaceit.width = hangtogo; 876 if(lastit != nil) { 877 spaceit.state = lastit.state & ~(IFbrk|IFbrksp|IFnobrk|IFcleft|IFcright); 878 lastit.next = spaceit; 879 } 880 else 881 lastit = spaceit; 882 spaceit.next = it; 883 } 884 } 885 pick i := it { 886 Ifloat => 887 if(anystuff) { 888 # float will go after this line 889 nextfloats = i :: nextfloats; 890 } 891 else { 892 # add float beside current line, adjust widths 893 fixfloatxy(lay, y, i); 894 # TODO: only do following if y and/or height changed 895 changelines(l.next, lay.end); 896 newlfloatw := floatw(y, y+lineh, lay.floats, Aleft); 897 newrfloatw := floatw(y, y+lineh, lay.floats, Aright); 898 lwid -= (newlfloatw-lfloatw) + (newrfloatw-rfloatw); 899 if (lwid < 0) { 900 right += -lwid; 901 lwid = 0; 902 } 903 lfloatw = newlfloatw; 904 rfloatw = newrfloatw; 905 } 906 checkw = 0; 907 Itable => 908 # When just doing layout for cell dimensions, don't 909 # want a "100%" spec to make the table really wide 910 kindspec := 0; 911 if(lay.targetwidth == TABLEMAXTARGET && i.table.width.kind() == Dpercent) { 912 kindspec = i.table.width.kindspec; 913 i.table.width = Dimen.make(Dnone, 0); 914 } 915 checktabsize(f, i, lwid-w); 916 if(kindspec != 0) 917 i.table.width.kindspec = kindspec; 918 Irule => 919 avail := lwid-w; 920 # When just doing layout for cell dimensions, don't 921 # want a "100%" spec to make the rule really wide 922 if(lay.targetwidth == TABLEMAXTARGET) 923 avail = min(10, avail); 924 i.width = widthfromspec(i.wspec, avail); 925 Iformfield => 926 checkffsize(f, i, i.formfield); 927 } 928 if(checkw) { 929 iw := it.width; 930 if(wrapping && w + iw > lwid) { 931 # it doesn't fit; see if it can be broken 932 takeit: int; 933 noneok := (anystuff || lfloatw != 0 || rfloatw != 0) && !(state&IFnobrk); 934 (takeit, iw) = trybreak(it, lwid-w, iw, noneok); 935 eol = 1; 936 if(!takeit) { 937 if(lastit == nil) { 938 # Nothing added because one of the float widths 939 # is nonzero, and not enough room for anything else. 940 # Move y down until there's more room and try again. 941 CU->assert(lfloatw != 0 || rfloatw != 0); 942 oldy := y; 943 y = pastbrk(lay, y, IFcleft|IFcright); 944 if(dbg > 1) 945 sys->print("moved y past %d, now y=%d\n", oldy, y); 946 CU->assert(y > oldy); # else infinite recurse 947 # Do the move down by artificially increasing the 948 # height of the previous line 949 lprev.height += y-oldy; 950 fixlinegeom(f, lay, l); 951 return; 952 } else 953 break; 954 } 955 } 956 w += iw; 957 if(hang) 958 hangtogo -= w; 959 (lineh, linea) = lgeom(lineh, linea, it); 960 if(!anystuff) { 961 anystuff = 1; 962 # don't count an ordinary space as 'stuff' if wrapping 963 pick t := it { 964 Itext => 965 if(wrapping && t.s == " ") 966 anystuff = 0; 967 } 968 } 969 } 970 lastit = it; 971 it = it.next; 972 if(it == nil && !eol) { 973 # perhaps next lines items can now fit on this line 974 nextl := l.next; 975 nit := nextl.items; 976 if(nextl != lay.end && !(nit.state&IFbrk)) { 977 lastit.next = nit; 978 # remove nextl 979 l.next = nextl.next; 980 l.next.prev = l; 981 it = nit; 982 } 983 } 984 } 985 # line is complete, next line will start with it (or it is nil) 986 rest := it; 987 if(lastit == nil) 988 raise "EXInternal: no items on line"; 989 lastit.next = nil; 990 991 l.width = w; 992 x := lfloatw + lay.margin + indent - linehang; 993 # shift line if it begins with a space or a rule 994 pick pi := l.items { 995 Itext => 996 if(pi.s != nil && pi.s[0] == ' ') 997 x -= fonts[pi.fnt].spw; 998 Irule => 999 # note: build ensures that rules appear on lines 1000 # by themselves 1001 if(pi.align == Acenter) 1002 just = IFcjust; 1003 else if(pi.align == Aright) 1004 just = IFrjust; 1005 Ifloat => 1006 if(pi.next != nil) { 1007 pick qi := pi.next { 1008 Itext => 1009 if(qi.s != nil && qi.s[0] == ' ') 1010 x -= fonts[qi.fnt].spw; 1011 } 1012 } 1013 } 1014 xright := x+w; 1015 if (xright + rfloatw > lay.width) 1016 lay.width = xright+rfloatw; 1017 n := lay.targetwidth-(lay.margin+rfloatw+xright); 1018 if(n > 0 && just) { 1019 if(just&IFcjust) 1020 x += n/2; 1021 else 1022 x += n; 1023 } 1024 if(dbg > 1) { 1025 sys->print("line geometry fixed, (x,y)=(%d,%d), w=%d, h=%d, a=%d, lfloatw=%d, rfloatw=%d, lay.width=%d\n", 1026 x, l.pos.y, w, lineh, linea, lfloatw, rfloatw, lay.width); 1027 if(dbg > 2) 1028 l.items.printlist("final line items"); 1029 } 1030 l.pos.x = x; 1031 l.height = lineh; 1032 l.ascent = linea; 1033 l.flags &= ~Lchanged; 1034 1035 if(nextfloats != nil) 1036 fixfloatsafter(lay, l, nextfloats); 1037 1038 if(rest != nil) { 1039 nextl := l.next; 1040 if(nextl == lay.end || (nextl.items.state&IFbrk)) { 1041 nextl = Line.new(); 1042 appendline(l, nextl); 1043 } 1044 li := lastitem(rest); 1045 li.next = nextl.items; 1046 nextl.items = rest; 1047 nextl.flags |= Lchanged; 1048 } 1049} 1050 1051# Return y coord after y due to a break. 1052pastbrk(lay: ref Lay, y, state: int) : int 1053{ 1054 nextralines := 0; 1055 if(state&IFbrksp) 1056 nextralines = 1; 1057 ynext := y; 1058 if(state&IFcleft) 1059 ynext = floatclry(lay.floats, Aleft, ynext); 1060 if(state&IFcright) 1061 ynext = max(ynext, floatclry(lay.floats, Aright, ynext)); 1062 ynext += nextralines*linespace; 1063 return ynext; 1064} 1065 1066# Add line l after lprev (and before lprev's current successor) 1067appendline(lprev, l: ref Line) 1068{ 1069 l.next = lprev.next; 1070 l.prev = lprev; 1071 l.next.prev = l; 1072 lprev.next = l; 1073} 1074 1075# Mark lines l up to but not including lend as changed 1076changelines(l, lend: ref Line) 1077{ 1078 for( ; l != lend; l = l.next) 1079 l.flags |= Lchanged; 1080} 1081 1082# Return a ref Font for font number num = (style*NumSize + size) 1083getfont(num: int) : ref Font 1084{ 1085 f := fonts[num].f; 1086 if(f == nil) { 1087 f = Font.open(display, fonts[num].name); 1088 if(f == nil) { 1089 if(num == DefFnt) 1090 raise sys->sprint("exLayout: can't open default font %s: %r", fonts[num].name); 1091 else { 1092 if(int (CU->config).dbg['w']) 1093 sys->print("warning: substituting default for font %s\n", 1094 fonts[num].name); 1095 f = fonts[DefFnt].f; 1096 } 1097 } 1098 fonts[num].f = f; 1099 fonts[num].spw = f.width(" "); 1100 } 1101 return f; 1102} 1103 1104# Set the width, height and ascent fields of all items, getting any necessary fonts. 1105# Some widths and heights depend on the available width on the line, and may be 1106# wrong until checked during fixlinegeom. 1107# Don't do tables here at all (except floating tables). 1108# Configure Controls for form fields. 1109measure(fr: ref Frame, items: ref Item) 1110{ 1111 for(it := items; it != nil; it = it.next) { 1112 pick t := it { 1113 Itext => 1114 f := getfont(t.fnt); 1115 it.width = f.width(t.s); 1116 a := f.ascent; 1117 h := f.height; 1118 if(t.voff != byte Voffbias) { 1119 a -= (int t.voff) - Voffbias; 1120 if(a > h) 1121 h = a; 1122 } 1123 it.height = h; 1124 it.ascent = a; 1125 Irule => 1126 it.height = t.size + 2*RULESP; 1127 it.ascent = t.size + RULESP; 1128 Iimage => 1129 setimagedims(t); 1130 Iformfield => 1131 c := Control.newff(fr, t.formfield); 1132 if(c != nil) { 1133 t.formfield.ctlid = fr.addcontrol(c); 1134 it.width = c.r.dx(); 1135 it.height = c.r.dy(); 1136 it.ascent = it.height; 1137 pick pc := c { 1138 Centry => 1139 it.ascent = lineascent + ENTVMARGIN; 1140 Cselect => 1141 it.ascent = lineascent + SELMARGIN; 1142 Cbutton => 1143 if(pc.dorelief) 1144 it.ascent -= BUTMARGIN; 1145 } 1146 } 1147 Ifloat => 1148 # Leave w at zero, so it doesn't contribute to line width in normal way 1149 # (Can find its width in t.item.width). 1150 pick i := t.item { 1151 Iimage => 1152 setimagedims(i); 1153 it.height = t.item.height; 1154 Itable => 1155 checktabsize(fr, i, TABLEFLOATTARGET); 1156 * => 1157 CU->assert(0); 1158 } 1159 it.ascent = it.height; 1160 Ispacer => 1161 case t.spkind { 1162 ISPvline => 1163 f := getfont(t.fnt); 1164 it.height = f.height; 1165 it.ascent = f.ascent; 1166 ISPhspace => 1167 getfont(t.fnt); 1168 it.width = fonts[t.fnt].spw; 1169 } 1170 } 1171 } 1172} 1173 1174# Set the dimensions of an image item 1175setimagedims(i: ref Item.Iimage) 1176{ 1177 i.width = i.imwidth + 2*(int i.hspace + int i.border); 1178 i.height = i.imheight + 2*(int i.vspace + int i.border); 1179 i.ascent = i.height - (int i.vspace + int i.border); 1180 if((CU->config).imagelvl == CU->ImgNone && i.altrep != "") { 1181 f := fonts[DefFnt].f; 1182 i.width = max(i.width, f.width(i.altrep)); 1183 i.height = max(i.height, f.height); 1184 i.ascent = f.ascent; 1185 } 1186} 1187 1188# Line geometry function: 1189# Given current line height (H) and ascent (distance from top to baseline) (A), 1190# and an item, see if that item changes height and ascent. 1191# Return (H', A'), the updated line height and ascent. 1192lgeom(H, A: int, it: ref Item) : (int, int) 1193{ 1194 h := it.height; 1195 a := it.ascent; 1196 atype := Abaseline; 1197 pick i := it { 1198 Iimage => 1199 atype = i.align; 1200 Itable => 1201 atype = Atop; 1202 Ifloat => 1203 return (H, A); 1204 } 1205 d := h-a; 1206 Hnew := H; 1207 Anew := A; 1208 case int atype { 1209 int Abaseline or int Abottom => 1210 if(a > A) { 1211 Anew = a; 1212 Hnew += (Anew - A); 1213 } 1214 if(d > Hnew - Anew) 1215 Hnew = Anew + d; 1216 int Atop => 1217 # OK to ignore what comes after in the line 1218 if(h > H) 1219 Hnew = h; 1220 int Amiddle or int Acenter => 1221 # supposed to align middle with baseline 1222 hhalf := h/2; 1223 if(hhalf > A) 1224 Anew = hhalf; 1225 if(hhalf > H-Anew) 1226 Hnew = Anew + hhalf; 1227 } 1228 return (Hnew, Anew); 1229} 1230 1231# Try breaking item bit to make it fit in availw. 1232# If that is possible, change bit to be the part that fits 1233# and insert the rest between bit and bit.next. 1234# iw is the current width of bit. 1235# If noneok is 0, break off the minimum size word 1236# even if it exceeds availw. 1237# Return (1 if supposed to take bit, iw' = new width of bit) 1238trybreak(bit: ref Item, availw, iw, noneok: int) : (int, int) 1239{ 1240 if(iw <= 0) 1241 return (1, iw); 1242 if(availw < 0) { 1243 if(noneok) 1244 return (0, iw); 1245 else 1246 availw = 0; 1247 } 1248 pick t := bit { 1249 Itext => 1250 if(len t.s < 2) 1251 return (!noneok, iw); 1252 (s1, w1, s2, w2) := breakstring(t.s, iw, fonts[t.fnt].f, availw, noneok); 1253 if(w1 == 0) 1254 return (0, iw); 1255 itn := Item.newtext(s2, t.fnt, t.fg, int t.voff, t.ul); 1256 itn.width = w2; 1257 itn.height = t.height; 1258 itn.ascent = t.ascent; 1259 itn.anchorid = t.anchorid; 1260 itn.state = t.state & ~(IFbrk|IFbrksp|IFnobrk|IFcleft|IFcright); 1261 itn.next = t.next; 1262 t.next = itn; 1263 t.s = s1; 1264 t.width = w1; 1265 return (1, w1); 1266 } 1267 return (!noneok, iw); 1268} 1269 1270# s has width sw when drawn in fnt. 1271# Break s into s1 and s2 so that s1 fits in availw. 1272# If noneok is true, it is ok for s1 to be nil, otherwise might 1273# have to return an s1 that overflows availw somewhat. 1274# Return (s1, w1, s2, w2) where w1 and w2 are widths of s1 and s2. 1275# Assume caller has already checked that sw > availw. 1276breakstring(s: string, sw: int, fnt: ref Font, availw, noneok: int) : (string, int, string, int) 1277{ 1278 slen := len s; 1279 if(slen < 2) { 1280 if(noneok) 1281 return (nil, 0, s, sw); 1282 else 1283 return (s, sw, nil, 0); 1284 } 1285 1286 # Use linear interpolation to guess break point. 1287 # We know avail < iw by conditions of trybreak call. 1288 i := slen*availw / sw - 1; 1289 if(i < 0) 1290 i = 0; 1291 i = breakpoint(s, i, -1); 1292 (ss, ww) := tryw(fnt, s, i); 1293 if(ww > availw) { 1294 while(ww > availw) { 1295 i = breakpoint(s, i-1, -1); 1296 if(i <= 0) 1297 break; 1298 (ss, ww) = tryw(fnt, s, i); 1299 } 1300 } 1301 else { 1302 oldi := i; 1303 oldss := ss; 1304 oldww := ww; 1305 while(ww < availw) { 1306 oldi = i; 1307 oldss = ss; 1308 oldww = ww; 1309 i = breakpoint(s, i+1, 1); 1310 if(i >= slen) 1311 break; 1312 (ss, ww) = tryw(fnt, s, i); 1313 } 1314 i = oldi; 1315 ss = oldss; 1316 ww = oldww; 1317 } 1318 if(i <= 0 || i >= slen) { 1319 if(noneok) 1320 return (nil, 0, s, sw); 1321 i = breakpoint(s, 1, 1); 1322 (ss,ww) = tryw(fnt, s, i); 1323 } 1324 return (ss, ww, s[i:slen], sw-ww); 1325} 1326 1327# If can break between s[i-1] and s[i], return i. 1328# Else move i in direction incr until this is true. 1329# (Might end up returning 0 or len s). 1330breakpoint(s: string, i, incr: int) : int 1331{ 1332 slen := len s; 1333 ans := 0; 1334 while(i > 0 && i < slen) { 1335 ci := s[i]; 1336 di := s[i-1]; 1337 1338 # ASCII rules 1339 if ((ci < 16rA0 && !int wordchar[ci]) || (di < 16rA0 && !int wordchar[di])) { 1340 ans = i; 1341 break; 1342 } 1343 1344 # Treat all ideographs as breakable. 1345 # The following range includes unassigned unicode code points. 1346 # All assigned code points in the range are class ID (ideograph) as defined 1347 # by the Unicode consortium's LineBreak data. 1348 # There are many other class ID code points outside of this range. 1349 # For details on how to do unicode line breaking properly see: 1350 # Unicode Standard Annex #14 (http://www.unicode.org/unicode/reports/tr14/) 1351 1352 if ((ci >= 16r30E && ci <= 16r9FA5) || (di >= 16r30E && di <= 16r9FA5)) { 1353 ans = i; 1354 break; 1355 } 1356 1357 # consider all other characters as unbreakable 1358 i += incr; 1359 } 1360 if(i == slen) 1361 ans = slen; 1362 return ans; 1363} 1364 1365# Return (s[0:i], width of that slice in font fnt) 1366tryw(fnt: ref Font, s: string, i: int) : (string, int) 1367{ 1368 if(i == 0) 1369 return ("", 0); 1370 ss := s[0:i]; 1371 return (ss, fnt.width(ss)); 1372} 1373 1374# Return max width of a float that overlaps [ymin, ymax) on given side. 1375# Floats are in reverse order of addition, so each float's y is <= that of 1376# preceding floats in list. Floats from both sides are intermixed. 1377floatw(ymin, ymax: int, flist: list of ref Item.Ifloat, side: byte) : int 1378{ 1379 ans := 0; 1380 for( ; flist != nil; flist = tl flist) { 1381 fl := hd flist; 1382 if(fl.side != side) 1383 continue; 1384 fymin := fl.y; 1385 fymax := fymin + fl.item.height; 1386 if((fymin <= ymin && ymin < fymax) || 1387 (ymin <= fymin && fymin < ymax)) { 1388 w := fl.x; 1389 if(side == Aleft) 1390 w += fl.item.width; 1391 if(ans < w) 1392 ans = w; 1393 } 1394 } 1395 return ans; 1396} 1397 1398# Float f is to be at vertical position >= y. 1399# Fix its (x,y) pos and add it to lay.floats, if not already there. 1400fixfloatxy(lay: ref Lay, y: int, f: ref Item.Ifloat) 1401{ 1402 height := f.item.height; 1403 width := f.item.width; 1404 f.y = y; 1405 flist := lay.floats; 1406 if(f.infloats != byte 0) { 1407 # only take previous floats into account for width 1408 while(flist != nil) { 1409 x := hd flist; 1410 flist = tl flist; 1411 if(x == f) 1412 break; 1413 } 1414 } 1415 f.x = floatw(y, y+height, flist, f.side); 1416 endx := f.x + width + lay.margin; 1417 if (endx > lay.width) 1418 lay.width = endx; 1419 if (f.side == Aright) 1420 f.x += width; 1421 endy := f.y + height + lay.margin; 1422 if (endy > lay.height) 1423 lay.height = endy; 1424 if(f.infloats == byte 0) { 1425 lay.floats = f :: lay.floats; 1426 f.infloats = byte 1; 1427 } 1428} 1429 1430# Floats in flist are to go after line l. 1431fixfloatsafter(lay: ref Lay, l: ref Line, flist: list of ref Item.Ifloat) 1432{ 1433 change := 0; 1434 y := l.pos.y + l.height; 1435 for(itl := Item.revlist(flist); itl != nil; itl = tl itl) { 1436 pick fl := hd itl { 1437 Ifloat => 1438 oldy := fl.y; 1439 fixfloatxy(lay, y, fl); 1440 if(fl.y != oldy) 1441 change = 1; 1442 y += fl.item.height; 1443 } 1444 } 1445# if(change) 1446# TODO only change if y and/or height changed 1447 changelines(l.next, lay.end); 1448} 1449 1450# If there's a float on given side that starts on or before y and 1451# ends after y, return ending y of that float, else return original y. 1452# Assume float list is bottom up. 1453floatclry(flist: list of ref Item.Ifloat, side: byte, y: int) : int 1454{ 1455 ymax := y; 1456 for( ; flist != nil; flist = tl flist) { 1457 fl := hd flist; 1458 if(fl.side == side) { 1459 if(fl.y <= y) { 1460 flymax := fl.y + fl.item.height; 1461 if (fl.item.height == 0) 1462 # assume it will have some height later 1463 flymax++; 1464 if(flymax > ymax) 1465 ymax = flymax; 1466 } 1467 } 1468 } 1469 return ymax; 1470} 1471 1472# Do preliminaries to laying out table tab in target width linewidth, 1473# setting total height and width. 1474sizetable(f: ref Frame, tab: ref Table, availwidth: int) 1475{ 1476 if(dbgtab) 1477 sys->print("sizetable %d, availwidth=%d, nrow=%d, ncol=%d, changed=%x, tab.availw=%d\n", 1478 tab.tableid, availwidth, tab.nrow, tab.ncol, int (tab.flags&Lchanged), tab.availw); 1479 if(tab.ncol == 0 || tab.nrow == 0) 1480 return; 1481 if(tab.availw == availwidth && (tab.flags&Lchanged) == byte 0) 1482 return; 1483 (hsp, vsp, pad, bd, cbd, hsep, vsep) := tableparams(tab); 1484 totw := widthfromspec(tab.width, availwidth); 1485 # reduce totw by spacing, padding, and rule widths 1486 # to leave amount left for contents 1487 totw -= (tab.ncol-1)*hsep+ 2*(hsp+bd+pad+cbd); 1488 if(totw <= 0) 1489 totw = 1; 1490 if(dbgtab) 1491 sys->print("\nsizetable %d, totw=%d, hsp=%d, vsp=%d, pad=%d, bd=%d, cbd=%d, hsep=%d, vsep=%d\n", 1492 tab.tableid, totw, hsp, vsp, pad, bd, cbd, hsep, vsep); 1493 for(cl := tab.cells; cl != nil; cl = tl cl) { 1494 c := hd cl; 1495 clay : ref Lay = nil; 1496 if(c.layid >= 0) 1497 clay = f.sublays[c.layid]; 1498 if(clay == nil || (clay.flags&Lchanged) != byte 0) { 1499 c.minw = -1; 1500 tw := TABLEMAXTARGET; 1501 if(c.wspec.kind() != Dnone) 1502 tw = widthfromspec(c.wspec, totw); 1503 1504 # When finding max widths, want to lay out using Aleft alignment, 1505 # because we don't yet know final width for proper justification. 1506 # If the max widths are accepted, we'll redo those needing other justification. 1507 if(clay == nil) { 1508 if(dbg) 1509 sys->print("Initial layout for cell %d.%d\n", tab.tableid, c.cellid); 1510 c.layid = sublayout(f, tw, Aleft, c.background, c.content); 1511 clay = f.sublays[c.layid]; 1512 c.content = nil; 1513 } 1514 else { 1515 if(dbg) 1516 sys->print("Relayout (for max) for cell %d.%d\n", tab.tableid, c.cellid); 1517 relayout(f, clay, tw, Aleft); 1518 } 1519 clay.flags |= Lchanged; # for min test, below 1520 c.maxw = clay.width; 1521 if(dbgtab) 1522 sys->print("sizetable %d for cell %d max layout done, targw=%d, c.maxw=%d\n", 1523 tab.tableid, c.cellid, tw, c.maxw); 1524 if(c.wspec.kind() == Dpixels) { 1525 # Other browsers don't make the following adjustment for 1526 # percentage and relative widths 1527 if(c.maxw <= tw) 1528 c.maxw = tw; 1529 if(dbgtab) 1530 sys->print("after spec adjustment, c.maxw=%d\n", c.maxw); 1531 } 1532 } 1533 } 1534 1535 # calc max column widths 1536 colmaxw := array[tab.ncol] of { * => 0}; 1537 maxw := widthcalc(tab, colmaxw, hsep, 1); 1538 1539 if(dbgtab) 1540 sys->print("sizetable %d maxw=%d, totw=%d\n", tab.tableid, maxw, totw); 1541 ci: int; 1542 if(maxw <= totw) { 1543 # trial layouts are fine, 1544 # but if table width was specified, add more space 1545 d := 0; 1546 adjust := (totw > maxw && tab.width.kind() != Dnone); 1547 for(ci = 0; ci < tab.ncol; ci++) { 1548 if (adjust) { 1549 delta := (totw-maxw); 1550 d = delta / (tab.ncol - ci); 1551 if (d <= 0) { 1552 d = delta; 1553 adjust = 0; 1554 } 1555 maxw += d; 1556 } 1557 tab.cols[ci].width = colmaxw[ci] + d; 1558 } 1559 } 1560 else { 1561 # calc min column widths and apportion out 1562 # differences 1563 if(dbgtab) 1564 sys->print("sizetable %d, availwidth %d, need min widths too\n", tab.tableid, availwidth); 1565 for(cl = tab.cells; cl != nil; cl = tl cl) { 1566 c := hd cl; 1567 clay := f.sublays[c.layid]; 1568 if(c.minw == -1 || (clay.flags&Lchanged) != byte 0) { 1569 if(dbg) 1570 sys->print("Relayout (for min) for cell %d.%d\n", tab.tableid, c.cellid); 1571 relayout(f, clay, 1, Aleft); 1572 c.minw = clay.width; 1573 if(dbgtab) 1574 sys->print("sizetable %d for cell %d min layout done, c.min=%d\n", 1575 tab.tableid, c.cellid, clay.width); 1576 } 1577 } 1578 colminw := array[tab.ncol] of { * => 0}; 1579 minw := widthcalc(tab, colminw, hsep, 0); 1580 w := totw - minw; 1581 d := maxw - minw; 1582 if(dbgtab) 1583 sys->print("sizetable %d minw=%d, w=%d, d=%d\n", tab.tableid, minw, w, d); 1584 for(ci = 0; ci < tab.ncol; ci++) { 1585 wd : int; 1586 if(w < 0 || d < 0) 1587 wd = colminw[ci]; 1588 else 1589 wd = colminw[ci] + (colmaxw[ci] - colminw[ci])*w/d; 1590 if(dbgtab) 1591 sys->print("sizetable %d col[%d].width = %d\n", tab.tableid, ci, wd); 1592 tab.cols[ci].width = wd; 1593 } 1594 1595 if(dbgtab) 1596 sys->print("sizetable %d, availwidth %d, doing final layouts\n", tab.tableid, availwidth); 1597 } 1598 1599 # now have col widths; set actual cell dimensions 1600 # and relayout (note: relayout will do no work if the target width 1601 # and just haven't changed from last layout) 1602 for(cl = tab.cells; cl != nil; cl = tl cl) { 1603 c := hd cl; 1604 clay := f.sublays[c.layid]; 1605 wd := cellwidth(tab, c, hsep); 1606 if(dbgtab) 1607 sys->print("sizetable %d for cell %d, clay.width=%d, cellwidth=%d\n", 1608 tab.tableid, c.cellid, clay.width, wd); 1609 if(dbg) 1610 sys->print("Relayout (final) for cell %d.%d\n", tab.tableid, c.cellid); 1611 relayout(f, clay, wd, c.align.halign); 1612 if(dbgtab) 1613 sys->print("sizetable %d for cell %d, final width %d, got width %d, height %d\n", 1614 tab.tableid, c.cellid, wd, clay.width, clay.height); 1615 } 1616 1617 # set row heights and ascents 1618 # first pass: ignore cells with rowspan > 1 1619 for(ri := 0; ri < tab.nrow; ri++) { 1620 row := tab.rows[ri]; 1621 h := 0; 1622 a := 0; 1623 n : int; 1624 for(rcl := row.cells; rcl != nil; rcl = tl rcl) { 1625 c := hd rcl; 1626 if(c.rowspan > 1 || c.layid < 0) 1627 continue; 1628 al := c.align.valign; 1629 if(al == Anone) 1630 al = tab.rows[c.row].align.valign; 1631 clay := f.sublays[c.layid]; 1632 if(al == Abaseline) { 1633 n = c.ascent; 1634 if(n > a) { 1635 h += (n - a); 1636 a = n; 1637 } 1638 n = clay.height - c.ascent; 1639 if(n > h-a) 1640 h = a + n; 1641 } 1642 else { 1643 n = clay.height; 1644 if(n > h) 1645 h = n; 1646 } 1647 } 1648 row.height = h; 1649 row.ascent = a; 1650 } 1651 # second pass: take care of rowspan > 1 1652 # (this algorithm isn't quite right -- it might add more space 1653 # than is needed in the presence of multiple overlapping rowspans) 1654 for(cl = tab.cells; cl != nil; cl = tl cl) { 1655 c := hd cl; 1656 if(c.rowspan > 1) { 1657 spanht := 0; 1658 for(i := 0; i < c.rowspan && c.row+i < tab.nrow; i++) 1659 spanht += tab.rows[c.row+i].height; 1660 if(c.layid < 0) 1661 continue; 1662 clay := f.sublays[c.layid]; 1663 ht := clay.height - (c.rowspan-1)*vsep; 1664 if(ht > spanht) { 1665 # add extra space to last spanned row 1666 i = c.row+c.rowspan-1; 1667 if(i >= tab.nrow) 1668 i = tab.nrow - 1; 1669 tab.rows[i].height += ht - spanht; 1670 if(dbgtab) 1671 sys->print("sizetable %d, row %d height %d\n", tab.tableid, i, tab.rows[i].height); 1672 } 1673 } 1674 } 1675 # get total width, heights, and col x / row y positions 1676 totw = bd + hsp + cbd + pad; 1677 for(ci = 0; ci < tab.ncol; ci++) { 1678 tab.cols[ci].pos.x = totw; 1679 if(dbgtab) 1680 sys->print("sizetable %d, col %d at x=%d\n", tab.tableid, ci, totw); 1681 totw += tab.cols[ci].width + hsep; 1682 } 1683 totw = totw - (cbd+pad) + bd; 1684 toth := bd + vsp + cbd + pad; 1685 # first time: move tab.caption items into layout 1686 if(tab.caption != nil) { 1687 # lay caption with Aleft; drawing will center it over the table width 1688 tab.caption_lay = sublayout(f, availwidth, Aleft, f.layout.background, tab.caption); 1689 caplay := f.sublays[tab.caption_lay]; 1690 tab.caph = caplay.height + CAPSEP; 1691 tab.caption = nil; 1692 } 1693 else if(tab.caption_lay >= 0) { 1694 caplay := f.sublays[tab.caption_lay]; 1695 if(tab.availw != availwidth || (caplay.flags&Lchanged) != byte 0) { 1696 relayout(f, caplay, availwidth, Aleft); 1697 tab.caph = caplay.height + CAPSEP; 1698 } 1699 } 1700 if(tab.caption_place == Atop) 1701 toth += tab.caph; 1702 for(ri = 0; ri < tab.nrow; ri++) { 1703 tab.rows[ri].pos.y = toth; 1704 if(dbgtab) 1705 sys->print("sizetable %d, row %d at y=%d\n", tab.tableid, ri, toth); 1706 toth += tab.rows[ri].height + vsep; 1707 } 1708 toth = toth - (cbd+pad) + bd; 1709 if(tab.caption_place == Abottom) 1710 toth += tab.caph; 1711 tab.totw = totw; 1712 tab.toth = toth; 1713 tab.availw = availwidth; 1714 tab.flags &= ~Lchanged; 1715 if(dbgtab) 1716 sys->print("\ndone sizetable %d, availwidth %d, totw=%d, toth=%d\n\n", 1717 tab.tableid, availwidth, totw, toth); 1718} 1719 1720# Calculate various table spacing parameters 1721tableparams(tab: ref Table) : (int, int, int, int, int, int, int) 1722{ 1723 bd := tab.border; 1724 hsp := tab.cellspacing; 1725 vsp := hsp; 1726 pad := tab.cellpadding; 1727 if(bd != 0) 1728 cbd := 1; 1729 else 1730 cbd = 0; 1731 hsep := 2*(cbd+pad)+hsp; 1732 vsep := 2*(cbd+pad)+vsp; 1733 return (hsp, vsp, pad, bd, cbd, hsep, vsep); 1734} 1735 1736# return cell width, taking multicol spanning into account 1737cellwidth(tab: ref Table, c: ref Tablecell, hsep: int) : int 1738{ 1739 if(c.colspan == 1) 1740 return tab.cols[c.col].width; 1741 wd := (c.colspan-1)*hsep; 1742 for(i := 0; i < c.colspan && c.col + i < tab.ncol; i++) 1743 wd += tab.cols[c.col + i].width; 1744 return wd; 1745} 1746 1747# return cell height, taking multirow spanning into account 1748cellheight(tab: ref Table, c: ref Tablecell, vsep: int) : int 1749{ 1750 if(c.rowspan == 1) 1751 return tab.rows[c.row].height; 1752 ht := (c.rowspan-1)*vsep; 1753 for(i := 0; i < c.rowspan && c.row + i < tab.nrow; i++) 1754 ht += tab.rows[c.row + i].height; 1755 return ht; 1756} 1757 1758# Calculate the column widths w as the max of the cells 1759# maxw or minw (as domax is 1 or 0). 1760# Return the total of all w. 1761# (hseps were accounted for by the adjustment that got 1762# totw from availwidth). 1763# hsep is amount of free space available between columns 1764# where there is multicolumn spanning. 1765# This is a two-pass algorithm. The first pass ignores 1766# cells that span multiple columns. The second pass 1767# sees if those multispanners need still more space, and 1768# if so, apportions the space out. 1769widthcalc(tab: ref Table, w: array of int, hsep, domax: int) : int 1770{ 1771 anyspan := 0; 1772 totw := 0; 1773 for(pass := 1; pass <= 2; pass++) { 1774 if(pass==2 && !anyspan) 1775 break; 1776 totw = 0; 1777 for(ci := 0; ci < tab.ncol; ci++) { 1778 for(ri := 0; ri < tab.nrow; ri++) { 1779 c := tab.grid[ri][ci]; 1780 if(c == nil) 1781 continue; 1782 if(domax) 1783 cwd := c.maxw; 1784 else 1785 cwd = c.minw; 1786 if(pass == 1) { 1787 if(c.colspan > 1) { 1788 anyspan = 1; 1789 continue; 1790 } 1791 if(cwd > w[ci]) 1792 w[ci] = cwd; 1793 } 1794 else { 1795 if(c.colspan == 1 || !(ci==c.col && ri==c.row)) 1796 continue; 1797 curw := 0; 1798 iend := ci+c.colspan; 1799 if(iend > tab.ncol) 1800 iend = tab.ncol; 1801 for(i:=ci; i < iend; i++) 1802 curw += w[i]; 1803 1804 # padding between spanned cols is free 1805 cwd -= hsep*(c.colspan-1); 1806 diff := cwd-curw; 1807 if(diff <= 0) 1808 continue; 1809 # doesn't fit: apportion diff among cols 1810 # in proportion to their current w 1811 for(i = ci; i < iend; i++) { 1812 if(curw == 0) 1813 w[i] = diff/c.colspan; 1814 else 1815 w[i] += diff*w[i]/curw; 1816 } 1817 } 1818 } 1819 totw += w[ci]; 1820 } 1821 } 1822 return totw; 1823} 1824 1825layframeset(f: ref Frame, ki: ref Kidinfo) 1826{ 1827 fwid := f.cr.dx(); 1828 fht := f.cr.dy(); 1829 if(dbg) 1830 sys->print("layframeset, configuring frame %d wide by %d high\n", fwid, fht); 1831 (nrow, rowh) := frdimens(ki.rows, fht); 1832 (ncol, colw) := frdimens(ki.cols, fwid); 1833 l := ki.kidinfos; 1834 y := f.cr.min.y; 1835 for(i := 0; i < nrow; i++) { 1836 x := f.cr.min.x; 1837 for(j := 0; j < ncol; j++) { 1838 if(l == nil) 1839 return; 1840 r := Rect(Point(x,y), Point(x+colw[j],y+rowh[i])); 1841 if(dbg) 1842 sys->print("kid gets rect (%d,%d)(%d,%d)\n", r.min.x, r.min.y, r.max.x, r.max.y); 1843 kidki := hd l; 1844 l = tl l; 1845 kidf := Frame.newkid(f, kidki, r); 1846 if(!kidki.isframeset) 1847 f.kids = kidf :: f.kids; 1848 if(kidf.framebd != 0) { 1849 kidf.cr = kidf.r.inset(2); 1850 drawborder(kidf.cim, kidf.cr, 2, DarkGrey); 1851 } 1852 if(kidki.isframeset) { 1853 layframeset(kidf, kidki); 1854 for(al := kidf.kids; al != nil; al = tl al) 1855 f.kids = (hd al) :: f.kids; 1856 } 1857 x += colw[j]; 1858 } 1859 y += rowh[i]; 1860 } 1861} 1862 1863# Use the dimension specs in dims to allocate total space t. 1864# Return (number of dimens, array of allocated space) 1865frdimens(dims: array of B->Dimen, t: int): (int, array of int) 1866{ 1867 n := len dims; 1868 if(n == 1) 1869 return (1, array[] of {t}); 1870 totpix := 0; 1871 totpcnt := 0; 1872 totrel := 0; 1873 for(i := 0; i < n; i++) { 1874 v := dims[i].spec(); 1875 kind := dims[i].kind(); 1876 if(v < 0) { 1877 v = 0; 1878 dims[i] = Dimen.make(kind, v); 1879 } 1880 case kind { 1881 B->Dpixels => totpix += v; 1882 B->Dpercent => totpcnt += v; 1883 B->Drelative => totrel += v; 1884 B->Dnone => totrel++; 1885 } 1886 } 1887 spix := 1.0; 1888 spcnt := 1.0; 1889 min_relu := 0; 1890 if(totrel > 0) 1891 min_relu = 30; # allow for scrollbar (14) and a bit 1892 relu := real min_relu; 1893 tt := totpix + (t*totpcnt/100) + totrel*min_relu; 1894 # want 1895 # t == totpix*spix + (totpcnt/100)*spcnt*t + totrel*relu 1896 if(tt < t) { 1897 # need to expand one of spix, spcnt, relu 1898 if(totrel == 0) { 1899 if(totpcnt != 0) 1900 # spix==1.0, relu==0, solve for spcnt 1901 spcnt = real ((t-totpix) * 100)/ real (t*totpcnt); 1902 else 1903 # relu==0, totpcnt==0, solve for spix 1904 spix = real t/ real totpix; 1905 } 1906 else 1907 # spix=1.0, spcnt=1.0, solve for relu 1908 relu += real (t-tt)/ real totrel; 1909 } 1910 else { 1911 # need to contract one or more of spix, spcnt, and have relu==min_relu 1912 totpixrel := totpix+totrel*min_relu; 1913 if(totpixrel < t) { 1914 # spix==1.0, solve for spcnt 1915 spcnt = real ((t-totpixrel) * 100)/ real (t*totpcnt); 1916 } 1917 else { 1918 # let spix==spcnt, solve 1919 trest := t - totrel*min_relu; 1920 if(trest > 0) { 1921 spcnt = real trest/real (totpix+(t*totpcnt/100)); 1922 } 1923 else { 1924 spcnt = real t / real tt; 1925 relu = 0.0; 1926 } 1927 spix = spcnt; 1928 } 1929 } 1930 x := array[n] of int; 1931 tt = 0; 1932 for(i = 0; i < n-1; i++) { 1933 vr := real dims[i].spec(); 1934 case dims[i].kind() { 1935 B->Dpixels => vr = vr * spix; 1936 B->Dpercent => vr = vr * real t * spcnt / 100.0; 1937 B->Drelative => vr = vr * relu; 1938 B->Dnone => vr = relu; 1939 } 1940 x[i] = int vr; 1941 tt += x[i]; 1942 } 1943 x[n-1] = t - tt; 1944 return (n, x); 1945} 1946 1947# Return last item of list of items, or nil if no items 1948lastitem(it: ref Item) : ref Item 1949{ 1950 ans : ref Item = it; 1951 for( ; it != nil; it = it.next) 1952 ans = it; 1953 return ans; 1954} 1955 1956# Lay out table if availw changed or tab changed 1957checktabsize(f: ref Frame, t: ref Item.Itable, availw: int) 1958{ 1959 tab := t.table; 1960 if (dbgtab) 1961 sys->print("checktabsize %d, availw %d, tab.availw %d, changed %d\n", tab.tableid, availw, tab.availw, (tab.flags&Lchanged)>byte 0); 1962 if(availw != tab.availw || int (tab.flags&Lchanged)) { 1963 sizetable(f, tab, availw); 1964 t.width = tab.totw + 2*tab.border; 1965 t.height = tab.toth + 2*tab.border; 1966 t.ascent = t.height; 1967 } 1968} 1969 1970widthfromspec(wspec: Dimen, availw: int) : int 1971{ 1972 w := availw; 1973 spec := wspec.spec(); 1974 case wspec.kind() { 1975 Dpixels => w = spec; 1976 Dpercent => w = spec*w/100; 1977 } 1978 return w; 1979} 1980 1981# An image may have arrived for an image input field 1982checkffsize(f: ref Frame, i: ref Item, ff: ref Formfield) 1983{ 1984 if(ff.ftype == Fimage && ff.image != nil) { 1985 pick imi := ff.image { 1986 Iimage => 1987 if(imi.ci.mims != nil && ff.ctlid >= 0) { 1988 pick b := f.controls[ff.ctlid] { 1989 Cbutton => 1990 if(b.pic == nil) { 1991 b.pic = imi.ci.mims[0].im; 1992 b.picmask = imi.ci.mims[0].mask; 1993 w := b.pic.r.dx(); 1994 h := b.pic.r.dy(); 1995 b.r.max.x = b.r.min.x + w; 1996 b.r.max.y = b.r.min.y + h; 1997 i.width = w; 1998 i.height = h; 1999 i.ascent = h; 2000 } 2001 } 2002 } 2003 } 2004 } 2005 else if(ff.ftype == Fselect) { 2006 opts := ff.options; 2007 if(ff.ctlid >=0) { 2008 pick c := f.controls[ff.ctlid] { 2009 Cselect => 2010 if(len opts != len c.options) { 2011 nc := Control.newff(f, ff); 2012 f.controls[ff.ctlid] = nc; 2013 i.width = nc.r.dx(); 2014 i.height = nc.r.dy(); 2015 i.ascent = lineascent + SELMARGIN; 2016 } 2017 } 2018 } 2019 } 2020} 2021 2022drawall(f: ref Frame) 2023{ 2024 oclipr := f.cim.clipr; 2025 origin := f.lptosp(zp); 2026 clipr := f.dirtyr.addpt(origin); 2027 f.cim.clipr = clipr; 2028 fillbg(f, clipr); 2029 if(dbg > 1) 2030 sys->print("drawall, cr=(%d,%d,%d,%d), viewr=(%d,%d,%d,%d), origin=(%d,%d)\n", 2031 f.cr.min.x, f.cr.min.y, f.cr.max.x, f.cr.max.y, 2032 f.viewr.min.x, f.viewr.min.y, f.viewr.max.x, f.viewr.max.y, 2033 origin.x, origin.y); 2034 if(f.layout != nil) 2035 drawlay(f, f.layout, origin); 2036 f.cim.clipr = oclipr; 2037 G->flush(f.cr); 2038 f.isdirty = 0; 2039} 2040 2041drawlay(f: ref Frame, lay: ref Lay, origin: Point) 2042{ 2043 for(l := lay.start.next; l != lay.end; l = l.next) 2044 drawline(f, origin, l, lay); 2045} 2046 2047# Draw line l in frame f, assuming that content's (0,0) 2048# aligns with layorigin in f.cim. 2049drawline(f : ref Frame, layorigin : Point, l: ref Line, lay: ref Lay) 2050{ 2051 im := f.cim; 2052 o := layorigin.add(l.pos); 2053 x := o.x; 2054 y := o.y; 2055 lr := Rect(zp, Point(l.width, l.height)).addpt(o); 2056 isdirty := f.isdirty && lr.Xrect(f.dirtyr.addpt(f.lptosp(zp))); 2057 inview := lr.Xrect(f.cr) && isdirty; 2058 2059 # note: drawimg must always be called to update 2060 # draw point of animated images 2061 for(it := l.items; it != nil; it = it.next) { 2062 pick i := it { 2063 Itext => 2064 if (!inview || i.s == nil) 2065 break; 2066 fnt := fonts[i.fnt]; 2067 width := i.width; 2068 yy := y+l.ascent - fnt.f.ascent + (int i.voff) - Voffbias; 2069 if (f.prctxt != nil) { 2070 if (yy < f.cr.min.y) 2071 continue; 2072 endy := yy + fnt.f.height; 2073 if (endy > f.cr.max.y) { 2074 # do not draw 2075 if (yy < f.prctxt.endy) 2076 f.prctxt.endy = yy; 2077 continue; 2078 } 2079 } 2080 fgi := colorimage(i.fg); 2081 im.text(Point(x, yy), fgi, zp, fnt.f, i.s); 2082 if(i.ul != ULnone) { 2083 if(i.ul == ULmid) 2084 yy += 2*i.ascent/3; 2085 else 2086 yy += i.height - 1; 2087 # don't underline leading space 2088 # have already adjusted x pos in fixlinegeom() 2089 ulx := x; 2090 ulw := width; 2091 if (i.s[0] == ' ') { 2092 ulx += fnt.spw; 2093 ulw -= fnt.spw; 2094 } 2095 if (i.s[len i.s - 1] == ' ') 2096 ulw -= fnt.spw; 2097 if (ulw < 1) 2098 continue; 2099 im.drawop(Rect(Point(ulx,yy),Point(ulx+ulw,yy+1)), fgi, nil, zp, Draw->S); 2100 } 2101 Irule => 2102 if (!inview) 2103 break; 2104 yy := y + RULESP; 2105 im.draw(Rect(Point(x,yy),Point(x+i.width,yy+i.size)), 2106 display.black, nil, zp); 2107 Iimage => 2108 yy := y; 2109 if(i.align == Abottom) 2110 # bottom aligns with baseline 2111 yy += l.ascent - i.imheight; 2112 else if(i.align == Amiddle) 2113 yy += l.ascent - (i.imheight/2); 2114 drawimg(f, Point(x,yy), i); 2115 Iformfield => 2116 ff := i.formfield; 2117 if(ff.ctlid >= 0 && ff.ctlid < len f.controls) { 2118 ctl := f.controls[ff.ctlid]; 2119 dims := ctl.r.max.sub(ctl.r.min); 2120 # align as text 2121 yy := y + l.ascent - i.ascent; 2122 p := Point(x,yy); 2123 ctl.r = Rect(p, p.add(dims)); 2124 if (!inview) 2125 break; 2126 if (f.prctxt != nil) { 2127 if (yy < f.cr.min.y) 2128 continue; 2129 if (ctl.r.max.y > f.cr.max.y) { 2130 # do not draw 2131 if (yy < f.prctxt.endy) 2132 f.prctxt.endy = yy; 2133 continue; 2134 } 2135 } 2136 ctl.draw(0); 2137 } 2138 Itable => 2139 # don't check inview - table can contain images 2140 drawtable(f, lay, Point(x,y), i.table); 2141 t := i.table; 2142 Ifloat => 2143 xx := layorigin.x + lay.margin; 2144 if(i.side == Aright) { 2145 xx -= i.x; 2146# # for main layout of frame, floats hug 2147# # right edge of frame, not layout 2148# # (other browsers do that) 2149# if(f.layout == lay) 2150 xx += lay.targetwidth; 2151# else 2152# xx += lay.width; 2153 } 2154 else 2155 xx += i.x; 2156 pick fi := i.item { 2157 Iimage => 2158 drawimg(f, Point(xx, layorigin.y + i.y + (int fi.border + int fi.vspace)), fi); 2159 Itable => 2160 drawtable(f, lay, Point(xx, layorigin.y + i.y), fi.table); 2161 } 2162 } 2163 x += it.width; 2164 } 2165} 2166 2167drawimg(f: ref Frame, iorigin: Point, i: ref Item.Iimage) 2168{ 2169 ci := i.ci; 2170 im := f.cim; 2171 iorigin.x += int i.hspace + int i.border; 2172 # y coord is already adjusted for border and vspace 2173 if(ci.mims != nil) { 2174 r := Rect(iorigin, iorigin.add(Point(i.imwidth,i.imheight))); 2175 inview := r.Xrect(f.cr); 2176 if(i.ctlid >= 0) { 2177 # animated 2178 c := f.controls[i.ctlid]; 2179 dims := c.r.max.sub(c.r.min); 2180 c.r = Rect(iorigin, iorigin.add(dims)); 2181 if (inview) { 2182 pick ac := c { 2183 Canimimage => 2184 ac.redraw = 1; 2185 ac.bg = f.layout.background; 2186 } 2187 c.draw(0); 2188 } 2189 } 2190 else if (inview) { 2191 mim := ci.mims[0]; 2192 iorigin = iorigin.add(mim.origin); 2193 im.draw(r, mim.im, mim.mask, zp); 2194 } 2195 if(inview && i.border != byte 0) { 2196 if(i.anchorid != 0) 2197 bdcol := f.doc.link; 2198 else 2199 bdcol = Black; 2200 drawborder(im, r, int i.border, bdcol); 2201 } 2202 } 2203 else if((CU->config).imagelvl == CU->ImgNone && i.altrep != "") { 2204 fnt := fonts[DefFnt].f; 2205 yy := iorigin.y+(i.imheight-fnt.height)/2; 2206 xx := iorigin.x + (i.width-fnt.width(i.altrep))/2; 2207 if(i.anchorid != 0) 2208 col := f.doc.link; 2209 else 2210 col = DarkGrey; 2211 fgi := colorimage(col); 2212 im.text(Point(xx, yy), fgi, zp, fnt, i.altrep); 2213 } 2214} 2215 2216drawtable(f : ref Frame, parentlay: ref Lay, torigin: Point, tab: ref Table) 2217{ 2218 if (dbgtab) 2219 sys->print("drawtable %d\n", tab.tableid); 2220 if(tab.ncol == 0 || tab.nrow == 0) 2221 return; 2222 im := f.cim; 2223 (hsp, vsp, pad, bd, cbd, hsep, vsep) := tableparams(tab); 2224 x := torigin.x; 2225 y := torigin.y; 2226 capy := y; 2227 boxy := y; 2228 if(tab.caption_place == Abottom) 2229 capy = y+tab.toth-tab.caph+vsp; 2230 else 2231 boxy = y+tab.caph; 2232 if (tab.background.color != -1 && tab.background.color != parentlay.background.color) { 2233# if(tab.background.image != parentlay.background.image || 2234# tab.background.color != parentlay.background.color) { 2235 bgi := colorimage(tab.background.color); 2236 im.draw(((x,boxy),(x+tab.totw,boxy+tab.toth-tab.caph)), 2237 bgi, nil, zp); 2238 } 2239 if(bd != 0) 2240 drawborder(im, ((x+bd,boxy+bd),(x+tab.totw-bd,boxy+tab.toth-tab.caph-bd)), 2241 1, Black); 2242 for(cl := tab.cells; cl != nil; cl = tl cl) { 2243 c := hd cl; 2244 if (c.layid == -1 || c.layid >= len f.sublays) { 2245 # for some reason (usually scrolling) 2246 # we are drawing this cell before it has been layed out 2247 continue; 2248 } 2249 clay := f.sublays[c.layid]; 2250 if(clay == nil) 2251 continue; 2252 if(c.col >= len tab.cols) 2253 continue; 2254 cx := x + tab.cols[c.col].pos.x; 2255 cy := y + tab.rows[c.row].pos.y; 2256 wd := cellwidth(tab, c, hsep); 2257 ht := cellheight(tab, c, vsep); 2258 if(c.background.image != nil && c.background.image.ci != nil && c.background.image.ci.mims != nil) { 2259 cellr := Rect((cx-pad,cy-pad),(cx+wd+pad,cy+ht+pad)); 2260 ci := c.background.image.ci; 2261 bgi := ci.mims[0].im; 2262 bgmask := ci.mims[0].mask; 2263 im.draw(cellr, bgi, bgmask, bgi.r.min); 2264 } else if(c.background.color != -1 && c.background.color != tab.background.color) { 2265 bgi := colorimage(c.background.color); 2266 im.draw(((cx-pad,cy-pad),(cx+wd+pad,cy+ht+pad)), 2267 bgi, nil, zp); 2268 } 2269 if(bd != 0) 2270 drawborder(im, ((cx-pad+1,cy-pad+1),(cx+wd+pad-1,cy+ht+pad-1)), 2271 1, Black); 2272 if(c.align.valign != Atop && c.align.valign != Abaseline) { 2273 n := ht - clay.height; 2274 if(c.align.valign == Amiddle) 2275 cy += n/2; 2276 else if(c.align.valign == Abottom) 2277 cy += n; 2278 } 2279 if(dbgtab) 2280 sys->print("drawtable %d cell %d at (%d,%d)\n", 2281 tab.tableid, c.cellid, cx, cy); 2282 drawlay(f, clay, Point(cx,cy)); 2283 } 2284 if(tab.caption_lay >= 0) { 2285 caplay := f.sublays[tab.caption_lay]; 2286 capx := x; 2287 if(caplay.width < tab.totw) 2288 capx += (tab.totw-caplay.width) / 2; 2289 drawlay(f, caplay, Point(capx,capy)); 2290 } 2291} 2292 2293# Draw border of width n just outside r, using src color 2294drawborder(im: ref Image, r: Rect, n, color: int) 2295{ 2296 x := r.min.x-n; 2297 y := r.min.y - n; 2298 xr := r.max.x+n; 2299 ybi := r.max.y; 2300 src := colorimage(color); 2301 im.draw((Point(x,y),Point(xr,y+n)), src, nil, zp); # top 2302 im.draw((Point(x,ybi),Point(xr,ybi+n)), src, nil, zp); # bottom 2303 im.draw((Point(x,y+n),Point(x+n,ybi)), src, nil, zp); # left 2304 im.draw((Point(xr-n,y+n),Point(xr,ybi)), src, nil, zp); # right 2305} 2306 2307# Draw relief border just outside r, width 2 border, 2308# colors white/lightgrey/darkgrey/black 2309# to give raised relief (if raised != 0) or sunken. 2310drawrelief(im: ref Image, r: Rect, raised: int) 2311{ 2312 # ((x,y),(xr,yb)) == r 2313 x := r.min.x; 2314 x1 := x-1; 2315 x2 := x-2; 2316 xr := r.max.x; 2317 xr1 := xr+1; 2318 xr2 := xr+2; 2319 y := r.min.y; 2320 y1 := y-1; 2321 y2 := y-2; 2322 yb := r.max.y; 2323 yb1 := yb+1; 2324 yb2 := yb+2; 2325 2326 # colors for top/left outside, top/left inside, bottom/right outside, bottom/right inside 2327 tlo, tli, bro, bri: ref Image; 2328 if(raised) { 2329 tlo = colorimage(Grey); 2330 tli = colorimage(White); 2331 bro = colorimage(Black); 2332 bri = colorimage(DarkGrey); 2333 } 2334 else { 2335 tlo = colorimage(DarkGrey); 2336 tli = colorimage(Black); 2337 bro = colorimage(White); 2338 bri = colorimage(Grey); 2339 } 2340 2341 im.draw((Point(x2,y2), Point(xr1,y1)), tlo, nil, zp); # top outside 2342 im.draw((Point(x1,y1), Point(xr,y)), tli, nil, zp); # top inside 2343 im.draw((Point(x2,y1), Point(x1,yb1)), tlo, nil, zp); # left outside 2344 im.draw((Point(x1,y), Point(x,yb)), tli, nil, zp); # left inside 2345 im.draw((Point(xr,y1),Point(xr1,yb)), bri, nil, zp); # right inside 2346 im.draw((Point(xr1,y),Point(xr2,yb1)), bro, nil, zp); # right outside 2347 im.draw((Point(x1,yb),Point(xr1,yb1)), bri, nil, zp); # bottom inside 2348 im.draw((Point(x,yb1),Point(xr2,yb2)), bro, nil, zp); # bottom outside 2349} 2350 2351# Fill r with color 2352drawfill(im: ref Image, r: Rect, color: int) 2353{ 2354 im.draw(r, colorimage(color), nil, zp); 2355} 2356 2357# Draw string in default font at p 2358drawstring(im: ref Image, p: Point, s: string) 2359{ 2360 im.text(p, colorimage(Black), zp, fonts[DefFnt].f, s); 2361} 2362 2363# Return (width, height) of string in default font 2364measurestring(s: string) : Point 2365{ 2366 f := fonts[DefFnt].f; 2367 return (f.width(s), f.height); 2368} 2369 2370# Mark as "changed" everything with change flags on the loc path 2371markchanges(loc: ref Loc) 2372{ 2373 lastf : ref Frame = nil; 2374 for(i := 0; i < loc.n; i++) { 2375 case loc.le[i].kind { 2376 LEframe => 2377 lastf = loc.le[i].frame; 2378 lastf.layout.flags |= Lchanged; 2379 LEline => 2380 loc.le[i].line.flags |= Lchanged; 2381 LEitem => 2382 pick it := loc.le[i].item { 2383 Itable => 2384 it.table.flags |= Lchanged; 2385 Ifloat => 2386 # whole layout will be redone if layout changes 2387 # and there are any floats 2388 ; 2389 } 2390 LEtablecell => 2391 if(lastf == nil) 2392 raise "EXInternal: markchanges no lastf"; 2393 c := loc.le[i].tcell; 2394 clay := lastf.sublays[c.layid]; 2395 if(clay != nil) 2396 clay.flags |= Lchanged; 2397 } 2398 } 2399} 2400 2401# one-item cache for colorimage 2402prevrgb := -1; 2403prevrgbimage : ref Image = nil; 2404 2405colorimage(rgb: int) : ref Image 2406{ 2407 if(rgb == prevrgb) 2408 return prevrgbimage; 2409 prevrgb = rgb; 2410 if(rgb == Black) 2411 prevrgbimage = display.black; 2412 else if(rgb == White) 2413 prevrgbimage = display.white; 2414 else { 2415 hv := rgb % NCOLHASH; 2416 if (hv < 0) 2417 hv = -hv; 2418 xhd := colorhashtab[hv]; 2419 x := xhd; 2420 while(x != nil && x.rgb != rgb) 2421 x = x.next; 2422 if(x == nil) { 2423# pix := I->closest_rgbpix((rgb>>16)&255, (rgb>>8)&255, rgb&255); 2424# im := display.color(pix); 2425 im := display.rgb((rgb>>16)&255, (rgb>>8)&255, rgb&255); 2426 if(im == nil) 2427 raise sys->sprint("exLayout: can't allocate color #%8.8ux: %r", rgb); 2428 x = ref Colornode(rgb, im, xhd); 2429 colorhashtab[hv] = x; 2430 } 2431 prevrgbimage = x.im; 2432 } 2433 return prevrgbimage; 2434} 2435 2436# Use f.background.image (if not nil) or f.background.color to fill r (in cim coord system) 2437# with background color. 2438fillbg(f: ref Frame, r: Rect) 2439{ 2440 bgi: ref Image; 2441 ii := f.doc.background.image; 2442 if (ii != nil && ii.ci != nil && ii.ci.mims != nil) 2443 bgi = ii.ci.mims[0].im; 2444 if(bgi == nil) 2445 bgi = colorimage(f.doc.background.color); 2446 f.cim.drawop(r, bgi, nil, f.viewr.min, Draw->S); 2447} 2448 2449TRIup, TRIdown, TRIleft, TRIright: con iota; 2450# Assume r is a square 2451drawtriangle(im: ref Image, r: Rect, kind, style: int) 2452{ 2453 drawfill(im, r, Grey); 2454 b := r.max.x - r.min.x; 2455 if(b < 4) 2456 return; 2457 b2 := b/2; 2458 bm2 := b-ReliefBd; 2459 p := array[3] of Point; 2460 col012, col20 : ref Image; 2461 d := colorimage(DarkGrey); 2462 l := colorimage(White); 2463 case kind { 2464 TRIup => 2465 p[0] = Point(b2, ReliefBd); 2466 p[1] = Point(bm2,bm2); 2467 p[2] = Point(ReliefBd,bm2); 2468 col012 = d; 2469 col20 = l; 2470 TRIdown => 2471 p[0] = Point(b2,bm2); 2472 p[1] = Point(ReliefBd,ReliefBd); 2473 p[2] = Point(bm2,ReliefBd); 2474 col012 = l; 2475 col20 = d; 2476 TRIleft => 2477 p[0] = Point(bm2, ReliefBd); 2478 p[1] = Point(bm2, bm2); 2479 p[2] = Point(ReliefBd,b2); 2480 col012 = d; 2481 col20 = l; 2482 TRIright => 2483 p[0] = Point(ReliefBd,bm2); 2484 p[1] = Point(ReliefBd,ReliefBd); 2485 p[2] = Point(bm2,b2); 2486 col012 = l; 2487 col20 = d; 2488 } 2489 if(style == ReliefSunk) { 2490 t := col012; 2491 col012 = col20; 2492 col20 = t; 2493 } 2494 for(i := 0; i < 3; i++) 2495 p[i] = p[i].add(r.min); 2496 im.fillpoly(p, ~0, colorimage(Grey), zp); 2497 im.line(p[0], p[1], 0, 0, ReliefBd/2, col012, zp); 2498 im.line(p[1], p[2], 0, 0, ReliefBd/2, col012, zp); 2499 im.line(p[2], p[0], 0, 0, ReliefBd/2, col20, zp); 2500} 2501 2502abs(a: int) : int 2503{ 2504 if(a < 0) 2505 return -a; 2506 return a; 2507} 2508 2509Frame.new() : ref Frame 2510{ 2511 f := ref Frame; 2512 f.parent = nil; 2513 f.cim = nil; 2514 f.r = Rect(zp, zp); 2515 f.animpid = 0; 2516 f.reset(); 2517 return f; 2518} 2519 2520Frame.newkid(parent: ref Frame, ki: ref Kidinfo, r: Rect) : ref Frame 2521{ 2522 f := ref Frame; 2523 f.parent = parent; 2524 f.cim = parent.cim; 2525 f.r = r; 2526 f.animpid = 0; 2527 f.reset(); 2528 f.src = ki.src; 2529 f.name = ki.name; 2530 f.marginw = ki.marginw; 2531 f.marginh = ki.marginh; 2532 f.framebd = ki.framebd; 2533 f.flags = ki.flags; 2534 return f; 2535} 2536 2537# Note: f.parent, f.cim and f.r should not be reset 2538# And if f.parent is true, don't reset params set in frameset. 2539Frame.reset(f: self ref Frame) 2540{ 2541 f.id = ++frameid; 2542 f.doc = nil; 2543 if(f.parent == nil) { 2544 f.src = nil; 2545 f.name = ""; 2546 f.marginw = FRMARGIN; 2547 f.marginh = FRMARGIN; 2548 f.framebd = 0; 2549 f.flags = FRvscrollauto | FRhscrollauto; 2550 } 2551 f.layout = nil; 2552 f.sublays = nil; 2553 f.sublayid = 0; 2554 f.controls = nil; 2555 f.controlid = 0; 2556 f.cr = f.r; 2557 f.isdirty = 1; 2558 f.dirtyr = f.cr; 2559 f.viewr = Rect(zp, zp); 2560 f.totalr = f.viewr; 2561 f.vscr = nil; 2562 f.hscr = nil; 2563 hadkids := (f.kids != nil); 2564 f.kids = nil; 2565 if(f.animpid != 0) 2566 CU->kill(f.animpid, 0); 2567 if(J != nil && hadkids) 2568 J->frametreechanged(f); 2569 f.animpid = 0; 2570} 2571 2572Frame.dirty(f: self ref Frame, r: Draw->Rect) 2573{ 2574 if (f.isdirty) 2575 f.dirtyr= f.dirtyr.combine(r); 2576 else { 2577 f.dirtyr = r; 2578 f.isdirty = 1; 2579 } 2580} 2581 2582Frame.addcontrol(f: self ref Frame, c: ref Control) : int 2583{ 2584 if(len f.controls <= f.controlid) { 2585 newcontrols := array[len f.controls + 30] of ref Control; 2586 newcontrols[0:] = f.controls; 2587 f.controls = newcontrols; 2588 } 2589 f.controls[f.controlid] = c; 2590 ans := f.controlid++; 2591 return ans; 2592} 2593 2594Frame.xscroll(f: self ref Frame, kind, val: int) 2595{ 2596 newx := f.viewr.min.x; 2597 case kind { 2598 CAscrollpage => 2599 newx += val*(f.cr.dx()*8/10); 2600 CAscrollline => 2601 newx += val*f.cr.dx()/10; 2602 CAscrolldelta => 2603 newx += val; 2604 CAscrollabs => 2605 newx = val; 2606 } 2607 f.scrollabs(Point(newx, f.viewr.min.y)); 2608} 2609 2610# Don't actually scroll by "page" and "line", 2611# But rather, 80% and 10%, which give more 2612# context in the first case, and more motion 2613# in the second. 2614Frame.yscroll(f: self ref Frame, kind, val: int) 2615{ 2616 newy := f.viewr.min.y; 2617 case kind { 2618 CAscrollpage => 2619 newy += val*(f.cr.dy()*8/10); 2620 CAscrollline => 2621 newy += val*f.cr.dy()/20; 2622 CAscrolldelta => 2623 newy += val; 2624 CAscrollabs => 2625 newy = val; 2626 } 2627 f.scrollabs(Point(f.viewr.min.x, newy)); 2628} 2629 2630Frame.scrollrel(f : self ref Frame, p : Point) 2631{ 2632 (x, y) := p; 2633 x += f.viewr.min.x; 2634 y += f.viewr.min.y; 2635 f.scrollabs(f.viewr.min.add(p)); 2636} 2637 2638Frame.scrollabs(f : self ref Frame, p : Point) 2639{ 2640 (x, y) := p; 2641 lay := f.layout; 2642 margin := 0; 2643 if (lay != nil) 2644 margin = lay.margin; 2645 x = max(0, min(x, f.totalr.max.x)); 2646 y = max(0, min(y, f.totalr.max.y + margin - f.cr.dy())); 2647 (oldx, oldy) := f.viewr.min; 2648 if (oldx != x || oldy != y) { 2649 f.viewr.min = (x, y); 2650 fixframegeom(f); 2651 # blit scroll 2652 dx := f.viewr.min.x - oldx; 2653 dy := f.viewr.min.y - oldy; 2654 origin := f.lptosp(zp); 2655 destr := f.viewr.addpt(origin); 2656 srcpt := destr.min.add((dx, dy)); 2657 oclipr := f.cim.clipr; 2658 f.cim.clipr = f.cr; 2659 f.cim.drawop(destr, f.cim, nil, srcpt, Draw->S); 2660 if (dx > 0) 2661 f.dirty(Rect((f.viewr.max.x - dx, f.viewr.min.y), f.viewr.max)); 2662 else if (dx < 0) 2663 f.dirty(Rect(f.viewr.min, (f.viewr.min.x - dx, f.viewr.max.y))); 2664 2665 if (dy > 0) 2666 f.dirty(Rect((f.viewr.min.x, f.viewr.max.y-dy), f.viewr.max)); 2667 else if (dy < 0) 2668 f.dirty(Rect(f.viewr.min, (f.viewr.max.x, f.viewr.min.y-dy))); 2669#f.cim.draw(destr, display.white, nil, zp); 2670 drawall(f); 2671 f.cim.clipr = oclipr; 2672 } 2673} 2674 2675# Convert layout coords (where (0,0) is top left of layout) 2676# to screen coords (i.e., coord system of mouse, f.cr, etc.) 2677Frame.sptolp(f: self ref Frame, sp: Point) : Point 2678{ 2679 return f.viewr.min.add(sp.sub(f.cr.min)); 2680} 2681 2682# Reverse translation of sptolp 2683Frame.lptosp(f: self ref Frame, lp: Point) : Point 2684{ 2685 return lp.add(f.cr.min.sub(f.viewr.min)); 2686} 2687 2688# Return Loc of Item or Scrollbar containing p (p in screen coords) 2689# or item it, if that is not nil. 2690Frame.find(f: self ref Frame, p: Point, it: ref Item) : ref Loc 2691{ 2692 return framefind(Loc.new(), f, p, it); 2693} 2694 2695# Find it (if non-nil) or place where p is (known to be inside f's layout). 2696framefind(loc: ref Loc, f: ref Frame, p: Point, it: ref Item) : ref Loc 2697{ 2698 loc.add(LEframe, f.r.min); 2699 loc.le[loc.n-1].frame = f; 2700 if(it == nil) { 2701 if(f.vscr != nil && p.in(f.vscr.r)) { 2702 loc.add(LEcontrol, f.vscr.r.min); 2703 loc.le[loc.n-1].control = f.vscr; 2704 loc.pos = p.sub(f.vscr.r.min); 2705 return loc; 2706 } 2707 if(f.hscr != nil && p.in(f.hscr.r)) { 2708 loc.add(LEcontrol, f.hscr.r.min); 2709 loc.le[loc.n-1].control = f.hscr; 2710 loc.pos = p.sub(f.hscr.r.min); 2711 return loc; 2712 } 2713 } 2714 if(it != nil || p.in(f.cr)) { 2715 lay := f.layout; 2716 if(f.kids != nil) { 2717 for(fl := f.kids; fl != nil; fl = tl fl) { 2718 kf := hd fl; 2719 try := framefind(loc, kf, p, it); 2720 if(try != nil) 2721 return try; 2722 } 2723 } 2724 else if(lay != nil) 2725 return layfind(loc, f, lay, f.lptosp(zp), p, it); 2726 } 2727 return nil; 2728} 2729 2730# Find it (if non-nil) or place where p is (known to be inside f's layout). 2731# p (in screen coords), lay offset by origin also in screen coords 2732layfind(loc: ref Loc, f: ref Frame, lay: ref Lay, origin, p: Point, it: ref Item) : ref Loc 2733{ 2734 for(flist := lay.floats; flist != nil; flist = tl flist) { 2735 fl := hd flist; 2736 fymin := fl.y+origin.y; 2737 fymax := fymin + fl.item.height; 2738 inside := 0; 2739 xx : int; 2740 if(it != nil || (fymin <= p.y && p.y < fymax)) { 2741 xx = origin.x + lay.margin; 2742 if(fl.side == Aright) { 2743 xx -= fl.x; 2744 xx += lay.targetwidth; 2745# if(lay == f.layout) 2746# xx = origin.x + (f.cr.dx() - lay.margin) - fl.x; 2747## xx += f.cr.dx() - fl.x; 2748# else 2749# xx += lay.width - fl.x; 2750 } 2751 else 2752 xx += fl.x; 2753 if(p.x >= xx && p.x < xx+fl.item.width) 2754 inside = 1; 2755 } 2756 fp := Point(xx,fymin); 2757 match := 0; 2758 if(it != nil) { 2759 pick fi := fl.item { 2760 Itable => 2761 loc.add(LEitem, fp); 2762 loc.le[loc.n-1].item = fl; 2763 loc.pos = p.sub(fp); 2764 lloc := tablefind(loc, f, fi, fp, p, it); 2765 if(lloc != nil) 2766 return lloc; 2767 Iimage => 2768 match = (it == fl || it == fl.item); 2769 } 2770 } 2771 if(match || inside) { 2772 loc.add(LEitem, fp); 2773 loc.le[loc.n-1].item = fl; 2774 loc.pos = p.sub(fp); 2775 if(it == fl.item) { 2776 loc.add(LEitem, fp); 2777 loc.le[loc.n-1].item = fl.item; 2778 } 2779 if(inside) { 2780 pick fi := fl.item { 2781 Itable => 2782 loc = tablefind(loc, f, fi, fp, p, it); 2783 } 2784 } 2785 return loc; 2786 } 2787 } 2788 for(l :=lay.start; l != nil; l = l.next) { 2789 o := origin.add(l.pos); 2790 if(it != nil || (o.y <= p.y && p.y < o.y+l.height)) { 2791 lloc := linefind(loc, f, l, o, p, it); 2792 if(lloc != nil) 2793 return lloc; 2794 if(it == nil && o.y + l.height >= p.y) 2795 break; 2796 } 2797 } 2798 return nil; 2799} 2800 2801# p (in screen coords), line at o, also in screen coords 2802linefind(loc: ref Loc, f: ref Frame, l: ref Line, o, p: Point, it: ref Item) : ref Loc 2803{ 2804 loc.add(LEline, o); 2805 loc.le[loc.n-1].line = l; 2806 x := o.x; 2807 y := o.y; 2808 inside := 0; 2809 for(i := l.items; i != nil; i = i.next) { 2810 if(it != nil || (x <= p.x && p.x < x+i.width)) { 2811 yy := y; 2812 h := 0; 2813 pick pi := i { 2814 Itext => 2815 fnt := fonts[pi.fnt].f; 2816 yy += l.ascent - fnt.ascent + (int pi.voff) - Voffbias; 2817 h = fnt.height; 2818 Irule => 2819 h = pi.size; 2820 Iimage => 2821 yy = y; 2822 if(pi.align == Abottom) 2823 yy += l.ascent - pi.imheight; 2824 else if(pi.align == Amiddle) 2825 yy += l.ascent - (pi.imheight/2); 2826 h = pi.imheight; 2827 Iformfield => 2828 h = pi.height; 2829 yy += l.ascent - pi.ascent; 2830 if(it != nil) { 2831 if(it == pi.formfield.image) { 2832 loc.add(LEitem, Point(x,yy)); 2833 loc.le[loc.n-1].item = i; 2834 loc.add(LEitem, Point(x,yy)); 2835 loc.le[loc.n-1].item = it; 2836 loc.pos = zp; # doesn't matter, its an 'it' test 2837 return loc; 2838 } 2839 } 2840 else if(yy < p.y && p.y < yy+h && pi.formfield.ctlid >= 0) { 2841 loc.add(LEcontrol, Point(x,yy)); 2842 loc.le[loc.n-1].control = f.controls[pi.formfield.ctlid]; 2843 loc.pos = p.sub(Point(x,yy)); 2844 return loc; 2845 } 2846 Itable => 2847 lloc := tablefind(loc, f, pi, Point(x,y), p, it); 2848 if(lloc != nil) 2849 return lloc; 2850 # else leave h==0 so p test will fail 2851 2852 # floats were handled separately. nulls can be picked by 'it' test 2853 # leave h==0, so p test will fail 2854 } 2855 if(it == i || (it == nil && yy <= p.y && p.y < yy+h)) { 2856 loc.add(LEitem, Point(x,yy)); 2857 loc.le[loc.n-1].item = i; 2858 loc.pos = p.sub(Point(x,yy)); 2859 return loc; 2860 } 2861 if(it == nil) 2862 return nil; 2863 } 2864 x += i.width; 2865 if(it == nil && x >= p.x) 2866 break; 2867 } 2868 loc.n--; 2869 return nil; 2870} 2871 2872tablefind(loc: ref Loc, f: ref Frame, ti: ref Item.Itable, torigin: Point, p: Point, it: ref Item) : ref Loc 2873{ 2874 loc.add(LEitem, torigin); 2875 loc.le[loc.n-1].item = ti; 2876 t := ti.table; 2877 (hsp, vsp, pad, bd, cbd, hsep, vsep) := tableparams(t); 2878 if(t.caption_lay >= 0) { 2879 caplay := f.sublays[t.caption_lay]; 2880 capy := torigin.y; 2881 if(t.caption_place == Abottom) 2882 capy += t.toth-t.caph+vsp; 2883 lloc := layfind(loc, f, caplay, Point(torigin.x,capy), p, it); 2884 if(lloc != nil) 2885 return lloc; 2886 } 2887 for(cl := t.cells; cl != nil; cl = tl cl) { 2888 c := hd cl; 2889 if(c.layid == -1 || c.layid >= len f.sublays) 2890 continue; 2891 clay := f.sublays[c.layid]; 2892 if(clay == nil) 2893 continue; 2894 cx := torigin.x + t.cols[c.col].pos.x; 2895 cy := torigin.y + t.rows[c.row].pos.y; 2896 wd := cellwidth(t, c, hsep); 2897 ht := cellheight(t, c, vsep); 2898 if(it == nil && !p.in(Rect(Point(cx,cy),Point(cx+wd,cy+ht)))) 2899 continue; 2900 if(c.align.valign != Atop && c.align.valign != Abaseline) { 2901 n := ht - clay.height; 2902 if(c.align.valign == Amiddle) 2903 cy += n/2; 2904 else if(c.align.valign == Abottom) 2905 cy += n; 2906 } 2907 loc.add(LEtablecell, Point(cx,cy)); 2908 loc.le[loc.n-1].tcell = c; 2909 lloc := layfind(loc, f, clay, Point(cx,cy), p, it); 2910 if(lloc != nil) 2911 return lloc; 2912 loc.n--; 2913 if(it == nil) 2914 return nil; 2915 } 2916 loc.n--; 2917 return nil; 2918} 2919 2920# (called from jscript) 2921# 'it' is an Iimage item in frame f whose image is to be switched 2922# to come from the src URL. 2923# 2924# For now, assume this is called only after the entire build process 2925# has finished. Also, only handle the case where the image has 2926# been preloaded and is in the cache now. This isn't right (BUG), but will 2927# cover most of the cases of extant image swapping, and besides, 2928# image swapping is mostly cosmetic anyway. 2929# 2930# For now, pay no attention to scaling issues or animation issues. 2931Frame.swapimage(f: self ref Frame, im: ref Item.Iimage, src: string) 2932{ 2933 u := U->parse(src); 2934 if(u.scheme == "") 2935 return; 2936 u = U->mkabs(u, f.doc.base); 2937 # width=height=0 finds u if in cache 2938 newci := CImage.new(u, nil, 0, 0); 2939 cachedci := (CU->imcache).look(newci); 2940 if(cachedci == nil || cachedci.mims == nil) 2941 return; 2942 im.ci = cachedci; 2943 2944 # we're assuming image will have same dimensions 2945 # as one that is replaced, so no relayout is needed; 2946 # otherwise need to call haveimage() instead of drawall() 2947 # Netscape scales replacement image to size of replaced image 2948 2949 f.dirty(f.totalr); 2950 drawall(f); 2951} 2952 2953Frame.focus(f : self ref Frame, focus, raisex : int) 2954{ 2955 di := f.doc; 2956 if (di == nil || (CU->config).doscripts == 0) 2957 return; 2958 if (di.evmask && raisex) { 2959 kind := E->SEonfocus; 2960 if (!focus) 2961 kind = E->SEonblur; 2962 if(di.evmask & kind) 2963 se := ref E->ScriptEvent(kind, f.id, -1, -1, -1, -1, -1, -1, 0, nil, nil, 0); 2964 } 2965} 2966 2967Control.newff(f: ref Frame, ff: ref B->Formfield) : ref Control 2968{ 2969 ans : ref Control = nil; 2970 case ff.ftype { 2971 Ftext or Fpassword or Ftextarea => 2972 nh := ff.size; 2973 nv := 1; 2974 linewrap := 0; 2975 if(ff.ftype == Ftextarea) { 2976 nh = ff.cols; 2977 nv = ff.rows; 2978 linewrap = 1; 2979 } 2980 ans = Control.newentry(f, nh, nv, linewrap); 2981 if(ff.ftype == Fpassword) 2982 ans.flags |= CFsecure; 2983 ans.entryset(ff.value); 2984 Fcheckbox or Fradio => 2985 ans = Control.newcheckbox(f, ff.ftype==Fradio); 2986 if((ff.flags&B->FFchecked) != byte 0) 2987 ans.flags |= CFactive; 2988 Fsubmit or Fimage or Freset or Fbutton => 2989 if(ff.image == nil) 2990 ans = Control.newbutton(f, nil, nil, ff.value, nil, 0, 1); 2991 else { 2992 pick i := ff.image { 2993 Iimage => 2994 pic, picmask : ref Image; 2995 if(i.ci.mims != nil) { 2996 pic = i.ci.mims[0].im; 2997 picmask = i.ci.mims[0].mask; 2998 } 2999 lab := ""; 3000 if((CU->config).imagelvl == CU->ImgNone) { 3001 lab = i.altrep; 3002 i = nil; 3003 } 3004 ans = Control.newbutton(f, pic, picmask, lab, i, 0, 0); 3005 } 3006 } 3007 Fselect => 3008 n := len ff.options; 3009 if(n > 0) { 3010 ao := array[n] of Option; 3011 l := ff.options; 3012 for(i := 0; i < n; i++) { 3013 o := hd l; 3014 # these are copied, so selected can be used for current state 3015 ao[i] = *o; 3016 l = tl l; 3017 } 3018 nvis := ff.size; 3019 ans = Control.newselect(f, nvis, ao); 3020 } 3021 Ffile => 3022 if(dbg) 3023 sys->print("warning: unimplemented file form field\n"); 3024 } 3025 if(ans != nil) 3026 ans.ff = ff; 3027 return ans; 3028} 3029 3030Control.newscroll(f: ref Frame, isvert, length, breadth: int) : ref Control 3031{ 3032 # need room for at least two squares and 2 borders of size 2 3033 if(length < 12) { 3034 breadth = 0; 3035 length = 0; 3036 } 3037 else if(breadth*2 + 4 > length) 3038 breadth = (length - 4) / 2; 3039 maxpt : Point; 3040 flags := CFenabled; 3041 if(isvert) { 3042 maxpt = Point(breadth, length); 3043 flags |= CFscrvert; 3044 } 3045 else 3046 maxpt = Point(length, breadth); 3047 return ref Control.Cscrollbar(f, nil, Rect(zp,maxpt), flags, nil, 0, 0, 1, 0, nil, (0, 0)); 3048} 3049 3050Control.newentry(f: ref Frame, nh, nv, linewrap: int) : ref Control 3051{ 3052 w := ctlcharspace*nh + 2*ENTHMARGIN; 3053 h := ctllinespace*nv + 2*ENTVMARGIN; 3054 scr : ref Control; 3055 if (linewrap) { 3056 scr = Control.newscroll(f, 1, h-4, SCRFBREADTH); 3057 scr.r.addpt(Point(w,0)); 3058 w += SCRFBREADTH; 3059 } 3060 ans := ref Control.Centry(f, nil, Rect(zp,Point(w,h)), CFenabled, nil, scr, "", (0, 0), 0, linewrap, 0); 3061 if (scr != nil) { 3062 pick pscr := scr { 3063 Cscrollbar => 3064 pscr.ctl = ans; 3065 } 3066 scr.scrollset(0, 1, 1, 0, 0); 3067 } 3068 return ans; 3069} 3070 3071Control.newbutton(f: ref Frame, pic, picmask: ref Image, lab: string, it: ref Item.Iimage, candisable, dorelief: int) : ref Control 3072{ 3073 dpic, dpicmask: ref Image; 3074 w := 0; 3075 h := 0; 3076 if(pic != nil) { 3077 w = pic.r.dx(); 3078 h = pic.r.dy(); 3079 } 3080 else if(it != nil) { 3081 w = it.imwidth; 3082 h = it.imheight; 3083 } 3084 else { 3085 w = fonts[CtlFnt].f.width(lab); 3086 h = ctllinespace; 3087 } 3088 if(dorelief) { 3089 # form image buttons are shown without margins in other browsers 3090 w += 2*BUTMARGIN; 3091 h += 2*BUTMARGIN; 3092 } 3093 r := Rect(zp, Point(w,h)); 3094 if(candisable && pic != nil) { 3095 # make "greyed out" image: 3096 # - convert pic to monochrome (ones where pic is non-white) 3097 # - draw pic in White, then DarkGrey shifted (-1,-1) and use 3098 # union of those two areas as mask 3099 dpicmask = display.newimage(pic.r, Draw->GREY1, 0, D->White); 3100 dpic = display.newimage(pic.r, pic.chans, 0, D->White); 3101 dpic.draw(dpic.r, colorimage(White), pic, zp); 3102 dpicmask.draw(dpicmask.r, display.black, pic, zp); 3103 dpic.draw(dpic.r.addpt(Point(-1,-1)), colorimage(DarkGrey), pic, zp); 3104 dpicmask.draw(dpicmask.r.addpt(Point(-1,-1)), display.black, pic, zp); 3105 } 3106 b := ref Control.Cbutton(f, nil, r, CFenabled, nil, pic, picmask, dpic, dpicmask, lab, dorelief); 3107 return b; 3108} 3109 3110Control.newcheckbox(f: ref Frame, isradio: int) : ref Control 3111{ 3112 return ref Control.Ccheckbox(f, nil, Rect((0,0),(CBOXWID,CBOXHT)), CFenabled, nil, isradio); 3113} 3114 3115Control.newselect(f: ref Frame, nvis: int, options: array of B->Option) : ref Control 3116{ 3117 nvis = min(5, len options); 3118 if (nvis < 1) 3119 nvis = 1; 3120 fnt := fonts[CtlFnt].f; 3121 w := 0; 3122 first := -1; 3123 for(i := 0; i < len options; i++) { 3124 if (first == -1 && options[i].selected) 3125 first = i; 3126 w = max(w, fnt.width(options[i].display)); 3127 } 3128 if (first == -1) 3129 first = 0; 3130 if (len options -nvis > 0 && len options - nvis < first) 3131 first = len options - nvis; 3132 w += 2*SELMARGIN; 3133 h := ctllinespace*nvis + 2*SELMARGIN; 3134 scr: ref Control; 3135 if (nvis > 1 && nvis < len options) { 3136 scr = Control.newscroll(f, 1, h, SCRFBREADTH); 3137 scr.r.addpt(Point(w,0)); 3138 } 3139 if (nvis < len options) 3140 w += SCRFBREADTH; 3141 ans := ref Control.Cselect(f, nil, Rect(zp, Point(w,h)), CFenabled, nil, nil, scr, nvis, first, options); 3142 if(scr != nil) { 3143 pick pscr := scr { 3144 Cscrollbar => 3145 pscr.ctl = ans; 3146 } 3147 scr.scrollset(first, first+nvis, len options, len options, 0); 3148 } 3149 return ans; 3150} 3151 3152Control.newlistbox(f: ref Frame, nrow, ncol: int, options: array of B->Option) : ref Control 3153{ 3154 fnt := fonts[CtlFnt].f; 3155 w := charspace*ncol + 2*SELMARGIN; 3156 h := fnt.height*nrow + 2*SELMARGIN; 3157 3158 vscr: ref Control = nil; 3159 #if(nrow < len options) { 3160 vscr = Control.newscroll(f, 1, (h-4)+SCRFBREADTH, SCRFBREADTH); 3161 vscr.r.addpt(Point(w-SCRFBREADTH,0)); 3162 w += SCRFBREADTH; 3163 #} 3164 3165 maxw := 0; 3166 for(i := 0; i < len options; i++) 3167 maxw = max(maxw, fnt.width(options[i].display)); 3168 3169 hscr: ref Control = nil; 3170 #if(w < maxw) { 3171 # allow for border (inset(2)) 3172 hscr = Control.newscroll(f, 0, (w-4)-SCRFBREADTH, SCRFBREADTH); 3173 hscr.r.addpt(Point(0, h-SCRBREADTH)); 3174 h += SCRFBREADTH; 3175 #} 3176 3177 ans := ref Control.Clistbox(f, nil, Rect(zp, Point(w,h)), CFenabled, nil, hscr, vscr, nrow, 0, 0, maxw/charspace, options, nil); 3178 if(vscr != nil) { 3179 pick pscr := vscr { 3180 Cscrollbar => 3181 pscr.ctl = ans; 3182 } 3183 vscr.scrollset(0, nrow, len options, len options, 0); 3184 } 3185 if(hscr != nil) { 3186 pick pscr := hscr { 3187 Cscrollbar => 3188 pscr.ctl = ans; 3189 } 3190 hscr.scrollset(0, w-SCRFBREADTH, maxw, 0, 0); 3191 } 3192 return ans; 3193} 3194 3195Control.newanimimage(f: ref Frame, cim: ref CU->CImage, bg: Background) : ref Control 3196{ 3197 return ref Control.Canimimage(f, nil, Rect((0,0),(cim.width,cim.height)), 0, nil, cim, 0, 0, big 0, bg); 3198} 3199 3200Control.newlabel(f: ref Frame, s: string) : ref Control 3201{ 3202 w := fonts[DefFnt].f.width(s); 3203 h := ctllinespace + 2*ENTVMARGIN; # give it same height as an entry box 3204 return ref Control.Clabel(f, nil, Rect(zp,Point(w,h)), 0, nil, s); 3205} 3206 3207Control.disable(c: self ref Control) 3208{ 3209 if(c.flags & CFenabled) { 3210 win := c.f.cim; 3211 c.flags &= ~CFenabled; 3212 if(c.f.cim != nil) 3213 c.draw(1); 3214 } 3215} 3216 3217Control.enable(c: self ref Control) 3218{ 3219 if(!(c.flags & CFenabled)) { 3220 c.flags |= CFenabled; 3221 if(c.f.cim != nil) 3222 c.draw(1); 3223 } 3224} 3225 3226changeevent(c: ref Control) 3227{ 3228 onchange := 0; 3229 pick pc := c { 3230 Centry => 3231 onchange = pc.onchange; 3232 pc.onchange = 0; 3233# this code reproduced Navigator 2 bug 3234# changes to Select Formfield selection only resulted in onchange event upon 3235# loss of focus. Now handled by domouse() code so event can be raised 3236# immediately 3237# Cselect => 3238# onchange = pc.onchange; 3239# pc.onchange = 0; 3240 } 3241 if(onchange && (c.ff.evmask & E->SEonchange)) { 3242 se := ref E->ScriptEvent(E->SEonchange, c.f.id, c.ff.form.formid, c.ff.fieldid, -1, -1, -1, -1, 1, nil, nil, 0); 3243 J->jevchan <-= se; 3244 } 3245} 3246 3247blurfocusevent(c: ref Control, kind, raisex: int) 3248{ 3249 if((CU->config).doscripts && c.ff != nil && c.ff.evmask) { 3250 if(kind == E->SEonblur) 3251 changeevent(c); 3252 if (!raisex || !(c.ff.evmask & kind)) 3253 return; 3254 se := ref E->ScriptEvent(kind, c.f.id, c.ff.form.formid, c.ff.fieldid, -1, -1, -1, -1, 0, nil, nil, 0); 3255 J->jevchan <-= se; 3256 } 3257} 3258 3259Control.losefocus(c: self ref Control, raisex: int) 3260{ 3261 if(c.flags & CFhasfocus) { 3262 c.flags &= ~CFhasfocus; 3263 if(c.f.cim != nil) { 3264 blurfocusevent(c, E->SEonblur, raisex); 3265 c.draw(1); 3266 } 3267 } 3268} 3269 3270Control.gainfocus(c: self ref Control, raisex: int) 3271{ 3272 if(!(c.flags & CFhasfocus)) { 3273 c.flags |= CFhasfocus; 3274 if(c.f.cim != nil) { 3275 blurfocusevent(c, E->SEonfocus, raisex); 3276 c.draw(1); 3277 } 3278 G->clientfocus(); 3279 } 3280} 3281 3282Control.scrollset(c: self ref Control, v1, v2, vmax, nsteps, draw: int) 3283{ 3284 pick sc := c { 3285 Cscrollbar => 3286 if(v1 < 0) 3287 v1 = 0; 3288 if(v2 > vmax) 3289 v2 = vmax; 3290 if(v1 > v2) 3291 v1 = v2; 3292 if(v1 == 0 && v2 == vmax) { 3293 sc.mindelta = 1; 3294 sc.deltaval = 0; 3295 sc.top = 0; 3296 sc.bot = 0; 3297 } 3298 else { 3299 length, breadth: int; 3300 if(sc.flags&CFscrvert) { 3301 length = sc.r.max.y - sc.r.min.y; 3302 breadth = sc.r.max.x - sc.r.min.x; 3303 } 3304 else { 3305 length = sc.r.max.x - sc.r.min.x; 3306 breadth = sc.r.max.y - sc.r.min.y; 3307 } 3308 l := length - (2*breadth + MINSCR); 3309 if(l <= 0) 3310 l = 1; 3311 if(l < 0) 3312 raise "EXInternal: negative scrollbar trough"; 3313 sc.top = l*v1/vmax; 3314 sc.bot = l*(vmax-v2)/vmax; 3315 if (nsteps == 0) 3316 sc.mindelta = 1; 3317 else 3318 sc.mindelta = max(1, length/nsteps); 3319 sc.deltaval = max(1, vmax/(l/sc.mindelta))*SCRDELTASF; 3320 } 3321 if(sc.f.cim != nil && draw) 3322 sc.draw(1); 3323 } 3324} 3325 3326SPECMASK : con 16rf000; 3327CTRLMASK : con 16r1f; 3328DEL : con 16r7f; 3329TAB : con '\t'; 3330CR: con '\n'; 3331 3332Control.dokey(ctl: self ref Control, keychar: int) : int 3333{ 3334 if(!(ctl.flags&CFenabled)) 3335 return CAnone; 3336 ans := CAnone; 3337 pick c := ctl { 3338 Centry => 3339 olds := c.s; 3340 slen := len c.s; 3341 (sels, sele) := normalsel(c.sel); 3342 modified := 0; 3343 (osels, osele) := (sels, sele); 3344 case keychar { 3345 ('a' & CTRLMASK) or Keyboard->Home => 3346 (sels, sele) = (0, 0); 3347 ('e' & CTRLMASK) or Keyboard->End => 3348 (sels, sele) = (slen, slen); 3349 'f' & CTRLMASK or Keyboard->Right => 3350 if(sele < slen) 3351 (sels, sele) = (sele+1, sele+1); 3352 'b' & CTRLMASK or Keyboard->Left => 3353 if(sels > 0) 3354 (sels, sele) = (sels-1, sels-1); 3355 Keyboard->Up => 3356 if (c.linewrap) 3357 sels = sele = entryupdown(c, sels, -1); 3358 Keyboard->Down => 3359 if (c.linewrap) 3360 sels = sele = entryupdown(c, sele, 1); 3361 'u' & CTRLMASK => 3362 entrydelrange(c, 0, slen); 3363 modified = 1; 3364 (sels, sele) = c.sel; 3365 'c' & CTRLMASK => 3366 entrysetsnarf(c); 3367 'v' & CTRLMASK => 3368 entryinsertsnarf(c); 3369 modified = 1; 3370 (sels, sele) = c.sel; 3371 'h' & CTRLMASK or DEL=> 3372 if (sels != sele) { 3373 entrydelrange(c, sels, sele); 3374 modified = 1; 3375 } else if(sels > 0) { 3376 entrydelrange(c, sels-1, sels); 3377 modified = 1; 3378 } 3379 (sels, sele) = c.sel; 3380 Keyboard->Del => 3381 if (sels != sele) { 3382 entrydelrange(c, sels, sele); 3383 modified = 1; 3384 } else if(sels < len c.s) { 3385 entrydelrange(c, sels, sels+1); 3386 modified = 1; 3387 } 3388 (sels, sele) = c.sel; 3389 TAB => 3390 ans = CAtabkey; 3391 * => 3392 if ((keychar & SPECMASK) == Keyboard->Spec) 3393 # ignore all other special keys 3394 break; 3395 if(keychar == CR) { 3396 if(c.linewrap) 3397 keychar = '\n'; 3398 else 3399 ans = CAreturnkey; 3400 } 3401 if(keychar > CTRLMASK || (keychar == '\n' && c.linewrap)) { 3402 if (sels != sele) { 3403 entrydelrange(c, sels, sele); 3404 (sels, sele) = c.sel; 3405 } 3406 slen = len c.s; 3407 c.s[slen] = 0; # expand string by 1 char 3408 for(k := slen; k > sels; k--) 3409 c.s[k] = c.s[k-1]; 3410 c.s[sels] = keychar; 3411 (sels, sele) = (sels+1, sels+1); 3412 modified = 1; 3413 } 3414 } 3415 c.sel = (sels, sele); 3416 if(osels != sels || osele != sele || modified) { 3417 entryscroll(c); 3418 c.draw(1); 3419 } 3420 if (c.s != olds) 3421 c.onchange = 1; 3422 } 3423 return ans; 3424} 3425 3426Control.domouse(ctl: self ref Control, p: Point, mtype: int, oldgrab : ref Control) : (int, ref Control) 3427{ 3428 up := (mtype == E->Mlbuttonup || mtype == E->Mldrop); 3429 down := (mtype == E->Mlbuttondown); 3430 drag := (mtype == E->Mldrag); 3431 hold := (mtype == E->Mhold); 3432 move := (mtype == E->Mmove); 3433 3434 # any button actions stop auto-repeat 3435 # it's up to the individual controls to re-instate it 3436 if (!move) 3437 E->autorepeat(nil, 0, 0); 3438 3439 if(!(ctl.flags&CFenabled)) 3440 return (CAnone, nil); 3441 ans := CAnone; 3442 changed := 0; 3443 newgrab : ref Control; 3444 grabbed := oldgrab != nil; 3445 pick c := ctl { 3446 Cbutton => 3447 if(down) { 3448 c.flags |= CFactive; 3449 newgrab = c; 3450 changed = 1; 3451 } 3452 else if(move && c.ff == nil) { 3453 ans = CAflyover; 3454 } 3455 else if (drag && grabbed) { 3456 newgrab = c; 3457 active := 0; 3458 if (p.in(c.r)) 3459 active = CFactive; 3460 if ((c.flags & CFactive) != active) 3461 changed = 1; 3462 c.flags = (c.flags & ~CFactive) | active; 3463 } 3464 else if(up) { 3465 if (c.flags & CFactive) 3466 ans = CAbuttonpush; 3467 c.flags &= ~CFactive; 3468 changed = 1; 3469 } 3470 Centry => 3471 if(c.scr != nil && !grabbed && p.x >= c.r.max.x-SCRFBREADTH) { 3472 pick scr := c.scr { 3473 Cscrollbar => 3474 return scr.domouse(p, mtype, oldgrab); 3475 } 3476 } 3477 (sels, sele) := c.sel; 3478 if(mtype == E->Mlbuttonup && grabbed) { 3479 if (sels != sele) 3480 ans = CAselected; 3481 } 3482 if(down || (drag && grabbed)) { 3483 newgrab = c; 3484 x := c.r.min.x+ENTHMARGIN; 3485 fnt := fonts[CtlFnt].f; 3486 s := c.s; 3487 if(c.flags&CFsecure) { 3488 for(i := 0; i < len s; i++) 3489 s[i] = '*'; 3490 } 3491 (osels, osele) := c.sel; 3492 s1 := " "; 3493 i := 0; 3494 iend := len s - 1; 3495 if(c.linewrap) { 3496 (lines, linestarts, topline, cursline) := entrywrapcalc(c); 3497 if(len lines > 1) { 3498 lineno := topline + (p.y - (c.r.min.y+ENTVMARGIN)) / ctllinespace; 3499 lineno = min(lineno, len lines -1); 3500 lineno = max(lineno, 0); 3501 3502 i = linestarts[lineno]; 3503 iend = i + len lines[lineno] -1; 3504 } 3505 } else 3506 x -= fnt.width(s[:c.left]); 3507 for(; i <= iend; i++) { 3508 s1[0] = s[i]; 3509 cx := fnt.width(s1); 3510 if(p.x < x + cx) 3511 break; 3512 x += cx; 3513 } 3514 sele = i; 3515 3516 if (down) 3517 sels = sele; 3518 c.sel = (sels, sele); 3519 3520 if (sels != osels || sele != osele) { 3521 changed = 1; 3522 entryscroll(c); 3523 if (p.x < c.r.min.x + ENTHMARGIN || p.x > c.r.max.x - ENTHMARGIN 3524 || p.y < c.r.min.y + ENTVMARGIN || p.y > c.r.max.y - ENTVMARGIN) { 3525 E->autorepeat(ref (Event.Emouse)(p, mtype), ARTICK, ARTICK); 3526 } 3527 } 3528 3529 if(!(c.flags&CFhasfocus)) 3530 ans = CAkeyfocus; 3531 } 3532 Ccheckbox=> 3533 if(up) { 3534 if(c.isradio) { 3535 if(!(c.flags&CFactive)) { 3536 c.flags |= CFactive; 3537 changed = 1; 3538 ans = CAbuttonpush; 3539 # turn off other radio button 3540 frm := c.ff.form; 3541 for(lf := frm.fields; lf != nil; lf = tl lf) { 3542 ff := hd lf; 3543 if(ff == c.ff) 3544 continue; 3545 if(ff.ftype == Fradio && ff.name==c.ff.name && ff.ctlid >= 0) { 3546 d := c.f.controls[ff.ctlid]; 3547 if(d.flags&CFactive) { 3548 d.flags &= ~CFactive; 3549 d.draw(1); 3550 break; # at most one other should be on 3551 } 3552 } 3553 } 3554 } 3555 } 3556 else { 3557 c.flags ^= CFactive; 3558 changed = 1; 3559 } 3560 } 3561 Cselect => 3562 if (c.nvis == 1 && up && c.popup == nil && c.r.contains(p)) 3563 return (CAdopopup, nil); 3564 if(c.scr != nil && (grabbed || p.x >= c.r.max.x-SCRFBREADTH)) { 3565 pick scr := c.scr { 3566 Cscrollbar => 3567 (a, grab) := scr.domouse(p, mtype, oldgrab); 3568 if (grab != nil) 3569 grab = c; 3570 return (a, grab); 3571 } 3572 return (ans, nil); 3573 } 3574 n := (p.y - (c.r.min.y+SELMARGIN))/ctllinespace + c.first; 3575 if (n >= c.first && n < c.first+c.nvis) { 3576 if ((c.ff.flags&B->FFmultiple) != byte 0) { 3577 if (down) { 3578 c.options[n].selected ^= 1; 3579 changed = 1; 3580 } 3581 } else if (up || drag) { 3582 changed = c.options[n].selected == 0; 3583 c.options[n].selected = 1; 3584 for(i := 0; i < len c.options; i++) { 3585 if(i != n) 3586 c.options[i].selected = 0; 3587 } 3588 } 3589 } 3590 if (up) { 3591 if (c.popup != nil) 3592 ans = CAdonepopup; 3593 else 3594 ans = CAchanged; 3595 } 3596 Clistbox => 3597 if(c.vscr != nil && (c.grab == c.vscr || (!grabbed && p.x >= c.r.max.x-SCRFBREADTH))) { 3598 c.grab = nil; 3599 pick vscr := c.vscr { 3600 Cscrollbar => 3601 (a, grab) := vscr.domouse(p, mtype, oldgrab); 3602 if (grab != nil) { 3603 c.grab = c.vscr; 3604 grab = c; 3605 } 3606 return (a, grab); 3607 } 3608 } 3609 else if(c.hscr != nil && (c.grab == c.hscr || (!grabbed && p.y >= c.r.max.y-SCRFBREADTH))) { 3610 c.grab = nil; 3611 pick hscr := c.hscr { 3612 Cscrollbar => 3613 (a, grab) := hscr.domouse(p, mtype, oldgrab); 3614 if (grab != nil) { 3615 c.grab = c.hscr; 3616 grab = c; 3617 } 3618 return (a, grab); 3619 } 3620 } 3621 else if(up) { 3622 fnt := fonts[CtlFnt].f; 3623 n := (p.y - (c.r.min.y+SELMARGIN))/fnt.height + c.first; 3624 if(n >= 0 && n < len c.options) { 3625 c.options[n].selected ^= 1; 3626 # turn off other selections 3627 for(i := 0; i < len c.options; i++) { 3628 if(i != n) 3629 c.options[i].selected = 0; 3630 } 3631 ans = CAchanged; 3632 changed = 1; 3633 } 3634 } 3635 Cscrollbar => 3636 val := 0; 3637 v, vmin, vmax, b: int; 3638 if(c.flags&CFscrvert) { 3639 v = p.y; 3640 vmin = c.r.min.y; 3641 vmax = c.r.max.y; 3642 b = c.r.dx(); 3643 } 3644 else { 3645 v = p.x; 3646 vmin = c.r.min.x; 3647 vmax = c.r.max.x; 3648 b = c.r.dy(); 3649 } 3650 vsltop := vmin+b+c.top; 3651 vslbot := vmax-b-c.bot; 3652 actflags := 0; 3653 oldactflags := c.flags&CFscrallact; 3654 3655 if ((down || drag) && !up && !hold) 3656 newgrab = c; 3657 3658 if (down) { 3659 newgrab = c; 3660 holdval := 0; 3661 repeat := 1; 3662 if (v >= vsltop && v < vslbot) { 3663 holdval = v - vsltop; 3664 actflags = CFactive; 3665 repeat = 0; 3666 } 3667 if(v < vmin+b) { 3668 holdval = -1; 3669 actflags = CFscracta1; 3670 } 3671 else if(v < vsltop) { 3672 holdval = -1; 3673 actflags = CFscracttr1; 3674 } 3675 else if(v >= vmax-b) { 3676 holdval = 1; 3677 actflags = CFscracta2; 3678 } 3679 else if(v >= vslbot) { 3680 holdval = 1; 3681 actflags = CFscracttr2; 3682 } 3683 c.holdstate = (actflags, holdval); 3684 if (repeat) { 3685 E->autorepeat(ref (Event.Emouse)(p, E->Mhold), ARPAUSE, ARTICK); 3686 } 3687 } 3688 if (drag) { 3689 (actflags, val) = c.holdstate; 3690 if (actflags == CFactive) { 3691 # dragging main scroll widget (relative to top of drag block) 3692 val = (v - vsltop) - val; 3693 if(abs(val) >= c.mindelta) { 3694 ans = CAscrolldelta; 3695 val = (c.deltaval * (val / c.mindelta))/SCRDELTASF; 3696 } 3697 } else { 3698 E->autorepeat(ref (Event.Emouse)(p, E->Mhold), ARTICK, ARTICK); 3699 } 3700 } 3701 if (up || hold) { 3702 # set the action according to the hold state 3703 # Note: main widget (case CFactive) handled by drag 3704 act := 0; 3705 (act, val) = c.holdstate; 3706 case act { 3707 CFscracta1 or 3708 CFscracta2 => 3709 ans = CAscrollline; 3710 CFscracttr1 or 3711 CFscracttr2 => 3712 ans = CAscrollpage; 3713 } 3714 if (up) { 3715 c.holdstate = (0, 0); 3716 } else { # hold 3717 (actflags, nil) = c.holdstate; 3718 if (ans != CAnone) { 3719 E->autorepeat(ref (Event.Emouse)(p, E->Mhold), ARTICK, ARTICK); 3720 newgrab = c; 3721 } 3722 } 3723 } 3724 c.flags = (c.flags & ~CFscrallact) | actflags; 3725 if(ans != CAnone) { 3726 ftoscroll := c.f; 3727 if(c.ctl != nil) { 3728 pick cff := c.ctl { 3729 Centry => 3730 ny := (cff.r.dy() - 2 * ENTVMARGIN) / ctllinespace; 3731 (nil, linestarts, topline, nil) := entrywrapcalc(cff); 3732 nlines := len linestarts; 3733 case ans { 3734 CAscrollpage => 3735 topline += val*ny; 3736 CAscrollline => 3737 topline += val; 3738 CAscrolldelta => 3739# # insufficient for large number of lines 3740 topline += val; 3741# if(val > 0) 3742# topline++; 3743# else 3744# topline--; 3745 } 3746 if (topline+ny >= nlines) 3747 topline = (nlines-1) - ny; 3748 if (topline < 0) 3749 topline = 0; 3750 cff.left = linestarts[topline]; 3751 c.scrollset(topline, topline+ny, nlines - 1, nlines, 1); 3752 cff.draw(1); 3753 return (ans, newgrab); 3754 Cselect => 3755 newfirst := cff.first; 3756 case ans { 3757 CAscrollpage => 3758 newfirst += val*cff.nvis; 3759 CAscrollline => 3760 newfirst += val; 3761 CAscrolldelta => 3762# # insufficient for very long select lists 3763 newfirst += val; 3764# if(val > 0) 3765# newfirst++; 3766# else 3767# newfirst--; 3768 } 3769 newfirst = max(0, min(newfirst, len cff.options - cff.nvis)); 3770 cff.first = newfirst; 3771 nopt := len cff.options; 3772 c.scrollset(newfirst, newfirst+cff.nvis, nopt, nopt, 0); 3773 cff.draw(1); 3774 return (ans, newgrab); 3775 Clistbox => 3776 if(c.flags&CFscrvert) { 3777 newfirst := cff.first; 3778 case ans { 3779 CAscrollpage => 3780 newfirst += val*cff.nvis; 3781 CAscrollline => 3782 newfirst += val; 3783 CAscrolldelta => 3784 newfirst += val; 3785# if(val > 0) 3786# newfirst++; 3787# else 3788# newfirst--; 3789 } 3790 newfirst = max(0, min(newfirst, len cff.options - cff.nvis)); 3791 cff.first = newfirst; 3792 c.scrollset(newfirst, newfirst+cff.nvis, len cff.options, 0, 1); 3793 # TODO: need redraw only vscr and content 3794 } 3795 else { 3796 hw := cff.maxcol; 3797 w := (c.r.max.x - c.r.min.x - SCRFBREADTH)/charspace; 3798 newstart := cff.start; 3799 case ans { 3800 CAscrollpage => 3801 newstart += val*hw; 3802 CAscrollline => 3803 newstart += val; 3804 CAscrolldelta => 3805 if(val > 0) 3806 newstart++; 3807 else 3808 newstart--; 3809 } 3810 if(hw < w) 3811 newstart = 0; 3812 else 3813 newstart = max(0, min(newstart, hw - w)); 3814 cff.start = newstart; 3815 c.scrollset(newstart, w+newstart, hw, 0, 1); 3816 # TODO: need redraw only hscr and content 3817 } 3818 cff.draw(1); 3819 return (ans, newgrab); 3820 } 3821 } 3822 else { 3823 if(c.flags&CFscrvert) 3824 c.f.yscroll(ans, val); 3825 else 3826 c.f.xscroll(ans, val); 3827 } 3828 changed = 1; 3829 } 3830 else if(actflags != oldactflags) { 3831 changed = 1; 3832 } 3833 } 3834 if(changed) 3835 ctl.draw(1); 3836 return (ans, newgrab); 3837} 3838 3839# returns a new popup control 3840Control.dopopup(ctl: self ref Control): ref Control 3841{ 3842 sel : ref Control.Cselect; 3843 pick c := ctl { 3844 Cselect => 3845 if (c.nvis > 1) 3846 return nil; 3847 sel = c; 3848 * => 3849 return nil; 3850 } 3851 3852 w := sel.r.dx(); 3853 nopt := len sel.options; 3854 nvis := min(nopt, POPUPLINES); 3855 first := sel.first; 3856 if (first + nvis > nopt) 3857 first = nopt - nvis; 3858 h := ctllinespace*nvis + 2*SELMARGIN; 3859 r := Rect(sel.r.min, sel.r.min.add(Point(w, h))); 3860 popup := G->getpopup(r); 3861 if (popup == nil) 3862 return nil; 3863 scr : ref Control; 3864 if (nvis < nopt) { 3865 scr = Control.newscroll(sel.f, 1, h, SCRFBREADTH); 3866 scr.r.addpt(Point(w,0)); 3867 } 3868 newsel := ref Control.Cselect(sel.f, sel.ff, r, sel.flags, popup, sel, scr, nvis, first, sel.options); 3869 if(scr != nil) { 3870 pick pscr := scr { 3871 Cscrollbar => 3872 pscr.ctl = newsel; 3873 } 3874 scr.popup = popup; 3875 scr.scrollset(first, first+nvis, nopt, nopt, 0); 3876 } 3877 newsel.draw(1); 3878 return newsel; 3879} 3880 3881# returns original control for which this was a popup 3882Control.donepopup(ctl: self ref Control): ref Control 3883{ 3884 owner: ref Control; 3885 pick c := ctl { 3886 Cselect => 3887 if (c.owner == nil) 3888 return nil; 3889 owner = c.owner; 3890 * => 3891 return nil; 3892 } 3893 G->cancelpopup(); 3894 pick c := owner { 3895 Cselect => 3896 for (first := 0; first < len c.options; first++) 3897 if (c.options[first].selected) 3898 break; 3899 if (first == len c.options) 3900 first = 0; 3901 c.first = first; 3902 } 3903 owner.draw(1); 3904 return owner; 3905} 3906 3907 3908Control.reset(ctl: self ref Control) 3909{ 3910 pick c := ctl { 3911 Cbutton => 3912 c.flags &= ~CFactive; 3913 Centry => 3914 c.s = ""; 3915 c.sel = (0, 0); 3916 c.left = 0; 3917 if(c.ff != nil && c.ff.value != "") 3918 c.s = c.ff.value; 3919 if (c.scr != nil) 3920 c.scr.scrollset(0, 1, 1, 0, 0); 3921 Ccheckbox=> 3922 c.flags &= ~CFactive; 3923 if(c.ff != nil && (c.ff.flags&B->FFchecked) != byte 0) 3924 c.flags |= CFactive; 3925 Cselect => 3926 nopt := len c.options; 3927 if(c.ff != nil) { 3928 l := c.ff.options; 3929 for(i := 0; i < nopt; i++) { 3930 o := hd l; 3931 c.options[i].selected = o.selected; 3932 l = tl l; 3933 } 3934 } 3935 c.first = 0; 3936 if(c.scr != nil) { 3937 c.scr.scrollset(0, c.nvis, nopt, nopt, 0); 3938 } 3939 Clistbox => 3940 c.first = 0; 3941 nopt := len c.options; 3942 if(c.vscr != nil) { 3943 c.vscr.scrollset(0, c.nvis, nopt, nopt, 0); 3944 } 3945 hw := 0; 3946 for(i := 0; i < len c.options; i++) 3947 hw = max(hw, fonts[DefFnt].f.width(c.options[i].display)); 3948 if(c.hscr != nil) { 3949 c.hscr.scrollset(0, c.r.max.x, hw, 0, 0); 3950 } 3951 Canimimage => 3952 c.cur = 0; 3953 } 3954 ctl.draw(0); 3955} 3956 3957Control.draw(ctl: self ref Control, flush: int) 3958{ 3959 win := ctl.f.cim; 3960 if (ctl.popup != nil) 3961 win = ctl.popup.image; 3962 if (win == nil) 3963 return; 3964 oclipr := win.clipr; 3965 clipr := oclipr; 3966 any: int; 3967 if (ctl.popup == nil) { 3968 (clipr, any) = ctl.r.clip(ctl.f.cr); 3969 if(!any && ctl != ctl.f.vscr && ctl != ctl.f.hscr) 3970 return; 3971 win.clipr = clipr; 3972 } 3973 pick c := ctl { 3974 Cbutton => 3975 if(c.ff != nil && c.ff.image != nil && c.pic == nil) { 3976 # check to see if image arrived 3977 # (dimensions will have been set by checkffsize, if needed; 3978 # this code is only for when the HTML specified the dimensions) 3979 pick imi := c.ff.image { 3980 Iimage => 3981 if(imi.ci.mims != nil) { 3982 c.pic = imi.ci.mims[0].im; 3983 c.picmask = imi.ci.mims[0].mask; 3984 } 3985 } 3986 } 3987 if(c.dorelief || c.pic == nil) 3988 win.draw(c.r, colorimage(Grey), nil, zp); 3989 if(c.pic != nil) { 3990 p, m: ref Image; 3991 if(c.flags & CFenabled) { 3992 p = c.pic; 3993 m = c.picmask; 3994 } 3995 else { 3996 p = c.dpic; 3997 m = c.dpicmask; 3998 } 3999 w := p.r.dx(); 4000 h := p.r.dy(); 4001 x := c.r.min.x + (c.r.dx() - w) / 2; 4002 y := c.r.min.y + (c.r.dy() - h) / 2; 4003 if((c.flags & CFactive) && c.dorelief) { 4004 x++; 4005 y++; 4006 } 4007 win.draw(Rect((x,y),(x+w,y+h)), p, m, zp); 4008 } 4009 else if(c.label != "") { 4010 p := c.r.min.add(Point(BUTMARGIN, BUTMARGIN)); 4011 if(c.flags & CFactive) 4012 p = p.add(Point(1,1)); 4013 win.text(p, colorimage(Black), zp, fonts[CtlFnt].f, c.label); 4014 } 4015 if(c.dorelief) { 4016 relief := ReliefRaised; 4017 if(c.flags & CFactive) 4018 relief = ReliefSunk; 4019 drawrelief(win, c.r.inset(2), relief); 4020 } 4021 Centry => 4022 win.draw(c.r, colorimage(White), nil, zp); 4023 insetr := c.r.inset(2); 4024 drawrelief(win,insetr, ReliefSunk); 4025 eclipr := c.r; 4026 eclipr.min.x += ENTHMARGIN; 4027 eclipr.max.x -= ENTHMARGIN; 4028 eclipr.min.y += ENTVMARGIN; 4029 eclipr.max.y -= ENTVMARGIN; 4030# if (c.scr != nil) 4031# eclipr.max.x -= SCRFBREADTH; 4032 (eclipr, any) = clipr.clip(eclipr); 4033 win.clipr = eclipr; 4034 p := c.r.min.add(Point(ENTHMARGIN,ENTVMARGIN)); 4035 s := c.s; 4036 fnt := fonts[CtlFnt].f; 4037 if(c.left > 0) 4038 s = s[c.left:]; 4039 if(c.flags&CFsecure) { 4040 for(i := 0; i < len s; i++) 4041 s[i] = '*'; 4042 } 4043 4044 (sels, sele) := normalsel(c.sel); 4045 (sels, sele) = (sels-c.left, sele-c.left); 4046 4047 lines : array of string; 4048 linestarts : array of int; 4049 textw := c.r.dx()-2*ENTHMARGIN; 4050 if (c.scr != nil) { 4051 textw -= SCRFBREADTH; 4052 c.scr.r = c.scr.r.subpt(c.scr.r.min); 4053 c.scr.r = c.scr.r.addpt(Point(insetr.max.x-SCRFBREADTH,insetr.min.y)); 4054 c.scr.draw(0); 4055 } 4056 if (c.linewrap) 4057 (lines, linestarts) = wrapstring(fnt, s, textw); 4058 else 4059 (lines, linestarts) = (array [] of {s}, array [] of {0}); 4060 4061 q := p; 4062 black := colorimage(Black); 4063 white := colorimage(White); 4064 navy := colorimage(Navy); 4065 nlines := len lines; 4066 for (n := 0; n < nlines; n++) { 4067 segs : list of (int, int, int); 4068 # only show cursor or selection if we have focus 4069 if (c.flags & CFhasfocus) 4070 segs = selsegs(len lines[n], sels-linestarts[n], sele-linestarts[n]); 4071 else 4072 segs = (0, len lines[n], 0) :: nil; 4073 for (; segs != nil; segs = tl segs) { 4074 (ss, se, sel) := hd segs; 4075 txt := lines[n][ss:se]; 4076 w := fnt.width(txt); 4077 txtcol : ref Image; 4078 if (!sel) 4079 txtcol = black; 4080 else { 4081 txtcol = white; 4082 bgcol := navy; 4083 if (n < nlines-1 && sele >= linestarts[n+1]) 4084 w = (p.x-q.x) + textw; 4085 selr := Rect((q.x, q.y-1), (q.x+w, q.y+ctllinespace+1)); 4086 if (selr.dx() == 0) { 4087 # empty selection - assume cursor 4088 bgcol = black; 4089 selr.max.x = selr.min.x + 2; 4090 } 4091 win.draw(selr, bgcol, nil, zp); 4092 } 4093 if (se > ss) 4094 win.text(q, txtcol, zp, fnt, txt); 4095 q.x += w; 4096 } 4097 q = (p.x, q.y + ctllinespace); 4098 } 4099 Ccheckbox=> 4100 win.draw(c.r, colorimage(White), nil, zp); 4101 if(c.isradio) { 4102 a := CBOXHT/2; 4103 a1 := a-1; 4104 cen := Point(c.r.min.x+a,c.r.min.y+a); 4105 win.ellipse(cen, a1, a1, 1, colorimage(DarkGrey), zp); 4106 win.arc(cen, a, a, 0, colorimage(Black), zp, 45, 180); 4107 win.arc(cen, a, a, 0, colorimage(Grey), zp, 225, 180); 4108 if(c.flags&CFactive) 4109 win.fillellipse(cen, 2, 2, colorimage(Black), zp); 4110 } 4111 else { 4112 ir := c.r.inset(2); 4113 ir.min.x += CBOXWID-CBOXHT; 4114 ir.max.x -= CBOXWID-CBOXHT; 4115 drawrelief(win, ir, ReliefSunk); 4116 if(c.flags&CFactive) { 4117 p1 := Point(ir.min.x, ir.min.y); 4118 p2 := Point(ir.max.x, ir.max.y); 4119 p3 := Point(ir.max.x, ir.min.y); 4120 p4 := Point(ir.min.x, ir.max.y); 4121 win.line(p1, p2, D->Endsquare, D->Endsquare, 0, colorimage(Black), zp); 4122 win.line(p3, p4, D->Endsquare, D->Endsquare, 0, colorimage(Black), zp); 4123 } 4124 } 4125 Cselect => 4126 black := colorimage(Black); 4127 white := colorimage(White); 4128 navy := colorimage(Navy); 4129 win.draw(c.r, white, nil, zp); 4130 drawrelief(win, c.r.inset(2), ReliefSunk); 4131 ir := c.r.inset(SELMARGIN); 4132 p := ir.min; 4133 fnt := fonts[CtlFnt].f; 4134 drawsel := c.nvis > 1; 4135 for(i := c.first; i < len c.options && i < c.first+c.nvis; i++) { 4136 if(drawsel && c.options[i].selected) { 4137 maxx := ir.max.x; 4138 if (c.scr != nil) 4139 maxx -= SCRFBREADTH; 4140 r := Rect((p.x-SELMARGIN,p.y),(maxx,p.y+ctllinespace)); 4141 win.draw(r, navy, nil, zp); 4142 win.text(p, white, zp, fnt, c.options[i].display); 4143 } 4144 else { 4145 win.text(p, black, zp, fnt, c.options[i].display); 4146 } 4147 p.y += ctllinespace; 4148 } 4149 if (c.nvis == 1 && len c.options > 1) { 4150 # drop down select list - draw marker (must be same width as scroll bar) 4151 r := Rect((ir.max.x - SCRFBREADTH, ir.min.y), ir.max); 4152 drawtriangle(win, r, TRIdown, ReliefRaised); 4153 } 4154 if(c.scr != nil) { 4155 c.scr.r = Rect((ir.max.x - SCRFBREADTH, ir.min.y), ir.max); 4156 c.scr.draw(0); 4157 } 4158 Clistbox => 4159 black := colorimage(Black); 4160 white := colorimage(White); 4161 navy := colorimage(Navy); 4162 win.draw(c.r, white, nil, zp); 4163 insetr := c.r.inset(2); 4164 #drawrelief(win, c.r.inset(2), ReliefSunk); 4165 ir := c.r.inset(SELMARGIN); 4166 p := ir.min; 4167 fnt := fonts[CtlFnt].f; 4168 for(i := c.first; i < len c.options && i < c.first+c.nvis; i++) { 4169 txt := ""; 4170 if (c.start < len c.options[i].display) 4171 txt = c.options[i].display[c.start:]; 4172 if(c.options[i].selected) { 4173 r := Rect((p.x-SELMARGIN,p.y),(c.r.max.x-SCRFBREADTH,p.y+fnt.height)); 4174 win.draw(r, navy, nil, zp); 4175 win.text(p, white, zp, fnt, txt); 4176 } 4177 else { 4178 win.text(p, black, zp, fnt, txt); 4179 } 4180 p.y +=fnt.height; 4181 } 4182 if(c.vscr != nil) { 4183 c.vscr.r = c.vscr.r.subpt(c.vscr.r.min); 4184 c.vscr.r = c.vscr.r.addpt(Point(insetr.max.x-SCRFBREADTH,insetr.min.y)); 4185 c.vscr.draw(0); 4186 } 4187 if(c.hscr != nil) { 4188 c.hscr.r = c.hscr.r.subpt(c.hscr.r.min); 4189 c.hscr.r = c.hscr.r.addpt(Point(insetr.min.x, insetr.max.y-SCRFBREADTH)); 4190 c.hscr.draw(0); 4191 } 4192 drawrelief(win, insetr, ReliefSunk); 4193 4194 Cscrollbar => 4195 # Scrollbar components: arrow 1 (a1), trough 1 (t1), slider (s), trough 2 (t2), arrow 2 (a2) 4196 x := c.r.min.x; 4197 y := c.r.min.y; 4198 ra1, rt1, rs, rt2, ra2: Rect; 4199 b, l, a1kind, a2kind: int; 4200 if(c.flags&CFscrvert) { 4201 l = c.r.max.y - c.r.min.y; 4202 b = c.r.max.x - c.r.min.x; 4203 xr := x+b; 4204 yt1 := y+b; 4205 ys := yt1+c.top; 4206 yb := y+l; 4207 ya2 := yb-b; 4208 yt2 := ya2-c.bot; 4209 ra1 = Rect(Point(x,y),Point(xr,yt1)); 4210 rt1 = Rect(Point(x,yt1),Point(xr,ys)); 4211 rs = Rect(Point(x,ys),Point(xr,yt2)); 4212 rt2 = Rect(Point(x,yt2),Point(xr,ya2)); 4213 ra2 = Rect(Point(x,ya2),Point(xr,yb)); 4214 a1kind = TRIup; 4215 a2kind = TRIdown; 4216 } 4217 else { 4218 l = c.r.max.x - c.r.min.x; 4219 b = c.r.max.y - c.r.min.y; 4220 yb := y+b; 4221 xt1 := x+b; 4222 xs := xt1+c.top; 4223 xr := x+l; 4224 xa2 := xr-b; 4225 xt2 := xa2-c.bot; 4226 ra1 = Rect(Point(x,y),Point(xt1,yb)); 4227 rt1 = Rect(Point(xt1,y),Point(xs,yb)); 4228 rs = Rect(Point(xs,y),Point(xt2,yb)); 4229 rt2 = Rect(Point(xt2,y),Point(xa2,yb)); 4230 ra2 = Rect(Point(xa2,y),Point(xr,yb)); 4231 a1kind = TRIleft; 4232 a2kind = TRIright; 4233 } 4234 a1relief := ReliefRaised; 4235 if(c.flags&CFscracta1) 4236 a1relief = ReliefSunk; 4237 a2relief := ReliefRaised; 4238 if(c.flags&CFscracta2) 4239 a2relief = ReliefSunk; 4240 drawtriangle(win, ra1, a1kind, a1relief); 4241 drawtriangle(win, ra2, a2kind, a2relief); 4242 drawfill(win, rt1, Grey); 4243 rs = rs.inset(2); 4244 drawfill(win, rs, Grey); 4245 rsrelief := ReliefRaised; 4246 if(c.flags&CFactive) 4247 rsrelief = ReliefSunk; 4248 drawrelief(win, rs, rsrelief); 4249 drawfill(win, rt2, Grey); 4250 Canimimage => 4251 i := c.cur; 4252 if(c.redraw) 4253 i = 0; 4254 else if(i > 0) { 4255 iprev := i-1; 4256 if(c.cim.mims[iprev].bgcolor != -1) { 4257 i = iprev; 4258 # get i back to before all "reset to previous" 4259 # images (which will be skipped in following 4260 # image drawing loop) 4261 while(i > 0 && c.cim.mims[i].bgcolor == -2) 4262 i--; 4263 } 4264 } 4265 bgi := colorimage(c.bg.color); 4266 if(c.bg.image != nil && c.bg.image.ci != nil && len c.bg.image.ci.mims > 0) 4267 bgi = c.bg.image.ci.mims[0].im; 4268 for( ; i <= c.cur; i++) { 4269 mim := c.cim.mims[i]; 4270 if(i > 0 && i < c.cur && mim.bgcolor == -2) 4271 continue; 4272 p := c.r.min.add(mim.origin); 4273 r := mim.im.r; 4274 r = Rect(p, p.add(Point(r.dx(), r.dy()))); 4275 4276 # IE takes "clear-to-background" disposal method to mean 4277 # clear to background of HTML page, ignoring any background 4278 # color specified in the GIF. 4279 # IE clears to background before frame 0 4280 if(i == 0) 4281 win.draw(c.r, bgi, nil, zp); 4282 4283 if(i != c.cur && mim.bgcolor >= 0) 4284 win.draw(r, bgi, nil, zp); 4285 else 4286 win.draw(r, mim.im, mim.mask, zp); 4287 } 4288 Clabel => 4289 p := c.r.min.add(Point(0,ENTVMARGIN)); 4290 win.text(p, colorimage(Black), zp, fonts[DefFnt].f, c.s); 4291 } 4292 if(flush) { 4293 if (ctl.popup != nil) 4294 ctl.popup.flush(ctl.r); 4295 else 4296 G->flush(ctl.r); 4297 } 4298 win.clipr = oclipr; 4299} 4300 4301# Break s up into substrings that fit in width availw 4302# when printing with font fnt. 4303# The second returned array contains the indexes into the original 4304# string where the corresponding line starts (which might not be simply 4305# the sum of the preceding lines because of cr/lf's in the original string 4306# which are omitted from the lines array. 4307# Empty lines (ending in cr) get put into the array as empty strings. 4308# The start indices array has an entry for the phantom next line, to avoid 4309# the need for special cases in the rest of the code. 4310wrapstring(fnt: ref Font, s: string, availw: int) : (array of string, array of int) 4311{ 4312 sl : list of (string, int) = nil; 4313 sw := fnt.width(s); 4314 n := 0; 4315 k := 0; # index into original s where current s starts 4316 origlen := len s; 4317 done := 0; 4318 while(!done) { 4319 kincr := 0; 4320 s1, s2: string; 4321 if(s == "") { 4322 s1 = s; 4323 done = 1; 4324 } 4325 else { 4326 # if any newlines in s1, it's a forced break 4327 # (and newlines aren't to appear in result) 4328 (s1, s2) = S->splitl(s, "\n"); 4329 if(s2 != nil && fnt.width(s1) <= availw) { 4330 s = s2[1:]; 4331 sw = fnt.width(s); 4332 kincr = (len s1) + 1; 4333 } 4334 else if(sw <= availw) { 4335 s1 = s; 4336 done = 1; 4337 } 4338 else { 4339 (s1, nil, s, sw) = breakstring(s, sw, fnt, availw, 0); 4340 kincr = len s1; 4341 if(s == "") 4342 done = 1; 4343 } 4344 } 4345 sl = (s1, k) :: sl; 4346 k += kincr; 4347 n++; 4348 } 4349 # reverse sl back to original order 4350 lines := array[n] of string; 4351 linestarts := array[n+1] of int; 4352 linestarts[n] = origlen; 4353 while(sl != nil) { 4354 (ss, nn) := hd sl; 4355 lines[--n] = ss; 4356 linestarts[n] = nn; 4357 sl = tl sl; 4358 } 4359 return (lines, linestarts); 4360} 4361 4362normalsel(sel : (int, int)) : (int, int) 4363{ 4364 (s, e) := sel; 4365 if (s > e) 4366 (e, s) = sel; 4367 return (s, e); 4368} 4369 4370selsegs(n, s, e : int) : list of (int, int, int) 4371{ 4372 if (e < 0 || s > n) 4373 # selection is not in 0..n 4374 return (0, n, 0) :: nil; 4375 4376 if (e > n) { 4377 # second half of string is selected 4378 if (s <= 0) 4379 return (0, n, 1) :: nil; 4380 return (0, s, 0) :: (s, n, 1) :: nil; 4381 } 4382 4383 if (s < 0) { 4384 # first half of string is selected 4385 if (e >= n) 4386 return (0, n, 1) :: nil; 4387 return (0, e, 1) :: (e, n, 0) :: nil; 4388 } 4389 # middle section of string is selected 4390 return (0, s, 0) :: (s, e, 1) :: (e, n, 0) :: nil; 4391} 4392 4393# Figure out in which area of scrollbar, if any, p lies. 4394# Then use p and mtype from mouse event to return desired action. 4395Control.entryset(c: self ref Control, s: string) 4396{ 4397 pick e := c { 4398 Centry => 4399 e.s = s; 4400 e.sel = (0, 0); 4401 e.left = 0; 4402 # calculate scroll bar settings 4403 if (e.linewrap && e.scr != nil) { 4404 (lines, nil, nil, nil) := entrywrapcalc(e); 4405 nlines := len lines; 4406 ny := (e.r.dy() - 2 * ENTVMARGIN)/ctllinespace; 4407 e.scr.scrollset(0, ny, (nlines - 1), nlines, 0); 4408 } 4409 } 4410} 4411 4412entryupdown(e: ref Control.Centry, cur : int, delta : int) : int 4413{ 4414 e.sel = (cur, cur); 4415 (lines, linestarts, topline, cursline) := entrywrapcalc(e); 4416 newl := cursline + delta; 4417 if (newl < 0 || newl >= len lines) 4418 return cur; 4419 4420 fnt := fonts[CtlFnt].f; 4421 x := cur - linestarts[cursline]; 4422 w := fnt.width(lines[cursline][0:x]); 4423 l := lines[newl]; 4424 if (len l == 0) 4425 return linestarts[newl]; 4426 prevw := fnt.width(l); 4427 curw := prevw; 4428 for (ix := len l - 1; ix > 0 ; ix--) { 4429 prevw = curw; 4430 curw = fnt.width(l[:ix]); 4431 if (curw < w) 4432 break; 4433 } 4434 # decide on closest (curw <= w <= prevw) 4435 if (prevw-w <= w - curw) 4436 # closer to rhs 4437 ix++; 4438 return linestarts[newl]+ix; 4439} 4440 4441# delete given range of characters, and redraw 4442entrydelrange(e: ref Control.Centry, istart, iend: int) 4443{ 4444 n := iend - istart; 4445 (sels, sele) := normalsel(e.sel); 4446 if(n > 0) { 4447 e.s = e.s[0:istart] + e.s[iend:]; 4448 4449 if(sels > istart) { 4450 if(sels < iend) 4451 sels = istart; 4452 else 4453 sels -= n; 4454 } 4455 if (sele > istart) { 4456 if (sele < iend) 4457 sele = istart; 4458 else 4459 sele -= n; 4460 } 4461 4462 if(e.left > istart) 4463 e.left = max(istart-1, 0); 4464 e.sel = (sels, sele); 4465 entryscroll(e); 4466 } 4467} 4468 4469snarf : string; 4470entrysetsnarf(e: ref Control.Centry) 4471{ 4472 if (e.s == nil) 4473 return; 4474 s := e.s; 4475 (sels, sele) := normalsel(e.sel); 4476 if (sels != sele) 4477 s = e.s[sels:sele]; 4478 4479 f := sys->open("/chan/snarf", sys->OWRITE); 4480 if (f == nil) 4481 snarf = s; 4482 else { 4483 data := array of byte s; 4484 sys->write(f, data, len data); 4485 } 4486} 4487 4488entryinsertsnarf(e: ref Control.Centry) 4489{ 4490 f := sys->open("/chan/snarf", sys->OREAD); 4491 if(f != nil) { 4492 buf := array[sys->ATOMICIO] of byte; 4493 n := sys->read(f, buf, len buf); 4494 if(n > 0) { 4495 # trim a trailing newline, as a service... 4496 if(buf[n-1] == byte '\n') 4497 n--; 4498 } 4499 snarf = ""; 4500 if (n > 0) 4501 snarf = string buf[:n]; 4502 } 4503 4504 if (snarf != nil) { 4505 (sels, sele) := normalsel(e.sel); 4506 if (sels != sele) { 4507 entrydelrange(e, sels, sele); 4508 (sels, sele) = e.sel; 4509 } 4510 lhs, rhs : string; 4511 if (sels > 0) 4512 lhs = e.s[:sels]; 4513 if (sels < len e.s) 4514 rhs = e.s[sels:]; 4515 e.entryset(lhs + snarf + rhs); 4516 e.sel = (len lhs, len lhs + len snarf); 4517 } 4518} 4519 4520# make sure can see cursor and following char or two 4521entryscroll(e: ref Control.Centry) 4522{ 4523 s := e.s; 4524 slen := len s; 4525 if(e.flags&CFsecure) { 4526 for(i := 0; i < slen; i++) 4527 s[i] = '*'; 4528 } 4529 if(e.linewrap) { 4530 # For multiple line entries, c.left is the char 4531 # at the beginning of the topmost visible line, 4532 # and we just want to scroll to make sure that 4533 # the line with the cursor is visible 4534 (lines, linestarts, topline, cursline) := entrywrapcalc(e); 4535 vislines := (e.r.dy()-2*ENTVMARGIN) / ctllinespace; 4536 nlines := len linestarts; 4537 if(cursline < topline) 4538 topline = cursline; 4539 else { 4540 if(cursline >= topline+vislines) 4541 topline = cursline-vislines+1; 4542 if (topline + vislines >= nlines) 4543 topline = max(0, (nlines-1) - vislines); 4544 } 4545 e.left = linestarts[topline]; 4546 if (e.scr != nil) 4547 e.scr.scrollset(topline, topline+vislines, nlines-1, nlines, 1); 4548 } 4549 else { 4550 (nil, sele) := e.sel; 4551 # sele is always the drag point 4552 if(sele < e.left) 4553 e.left = sele; 4554 else if(sele > e.left) { 4555 fnt := fonts[CtlFnt].f; 4556 wantw := e.r.dx() -2*ENTHMARGIN; # - 2*ctlspspace; 4557 while(e.left < sele-1) { 4558 w := fnt.width(e.s[e.left:sele]); 4559 if(w < wantw) 4560 break; 4561 e.left++; 4562 } 4563 } 4564 } 4565} 4566 4567# Given e, a Centry with line wrapping, 4568# return (wrapped lines, line start indices, line# of top displayed line, line# containing cursor). 4569entrywrapcalc(e: ref Control.Centry) : (array of string, array of int, int, int) 4570{ 4571 s := e.s; 4572 if(e.flags&CFsecure) { 4573 for(i := 0; i < len s; i++) 4574 s[i] = '*'; 4575 } 4576 (nil, sele) := e.sel; 4577 textw := e.r.dx()-2*ENTHMARGIN; 4578 if (e.scr != nil) 4579 textw -= SCRFBREADTH; 4580 (lines, linestarts) := wrapstring(fonts[CtlFnt].f, s, textw); 4581 topline := 0; 4582 cursline := 0; 4583 for(i := 0; i < len lines; i++) { 4584 s = lines[i]; 4585 i1 := linestarts[i]; 4586 i2 := linestarts[i+1]; 4587 if(e.left >= i1 && e.left < i2) 4588 topline = i; 4589 if(sele >= i1 && sele < i2) 4590 cursline = i; 4591 } 4592 if(sele == linestarts[len lines]) 4593 cursline = len lines - 1; 4594 return (lines, linestarts, topline, cursline); 4595} 4596 4597Lay.new(targwidth: int, just: byte, margin: int, bg: Background) : ref Lay 4598{ 4599 ans := ref Lay(Line.new(), Line.new(), 4600 targwidth, 0, 0, margin, nil, bg, just, byte 0); 4601 if(ans.targetwidth < 0) 4602 ans.targetwidth = 0; 4603 ans.start.pos = Point(margin, margin); 4604 ans.start.next = ans.end; 4605 ans.end.prev = ans.start; 4606 # dummy item at end so ans.end will have correct y coord 4607 it := Item.newspacer(ISPnull, 0); 4608 it.state = IFbrk|IFcleft|IFcright; 4609 ans.end.items = it; 4610 return ans; 4611} 4612 4613Line.new() : ref Line 4614{ 4615 return ref Line( 4616 nil, nil, nil, # items, next, prev 4617 zp, # pos 4618 0, 0, 0, # width, height, ascent 4619 byte 0); # flags 4620} 4621 4622Loc.new() : ref Loc 4623{ 4624 return ref Loc(array[10] of Locelem, 0, zp); # le, n, pos 4625} 4626 4627Loc.add(loc: self ref Loc, kind: int, pos: Point) 4628{ 4629 if(loc.n == len loc.le) { 4630 newa := array[len loc.le + 10] of Locelem; 4631 newa[0:] = loc.le; 4632 loc.le = newa; 4633 } 4634 loc.le[loc.n].kind = kind; 4635 loc.le[loc.n].pos = pos; 4636 loc.n++; 4637} 4638 4639# return last frame in loc's path 4640Loc.lastframe(loc: self ref Loc) : ref Frame 4641{ 4642 if (loc == nil) 4643 return nil; 4644 for(i := loc.n-1; i >=0; i--) 4645 if(loc.le[i].kind == LEframe) 4646 return loc.le[i].frame; 4647 return nil; 4648} 4649 4650Loc.print(loc: self ref Loc, msg: string) 4651{ 4652 sys->print("%s: Loc with %d components, pos=(%d,%d)\n", msg, loc.n, loc.pos.x, loc.pos.y); 4653 for(i := 0; i < loc.n; i++) { 4654 case loc.le[i].kind { 4655 LEframe => 4656 sys->print("frame %x\n", loc.le[i].frame); 4657 LEline => 4658 sys->print("line %x\n", loc.le[i].line); 4659 LEitem => 4660 sys->print("item: %x", loc.le[i].item); 4661 loc.le[i].item.print(); 4662 LEtablecell => 4663 sys->print("tablecell: %x, cellid=%d\n", loc.le[i].tcell, loc.le[i].tcell.cellid); 4664 LEcontrol => 4665 sys->print("control %x\n", loc.le[i].control); 4666 } 4667 } 4668} 4669 4670Sources.new(m : ref Source) : ref Sources 4671{ 4672 srcs := ref Sources; 4673 srcs.main = m; 4674 return srcs; 4675} 4676 4677Sources.add(srcs: self ref Sources, s: ref Source, required: int) 4678{ 4679 if (required) { 4680 CU->assert(srcs.reqd == nil); 4681 srcs.reqd = s; 4682 } else 4683 srcs.srcs = s :: srcs.srcs; 4684} 4685 4686Sources.done(srcs: self ref Sources, s: ref Source) 4687{ 4688 if (s == srcs.main) { 4689 if (srcs.reqd != nil) { 4690 sys->print("FREEING MAIN WHEN REQD != nil\n"); 4691 if (s.bs == nil) 4692 sys->print("s.bs == nil\n"); 4693 else 4694 sys->print("main.eof = %d main.lim = %d, main.edata = %d\n", s.bs.eof, s.bs.lim, s.bs.edata); 4695 } 4696 srcs.main = nil; 4697 } 4698 else if (s == srcs.reqd) 4699 srcs.reqd = nil; 4700 else { 4701 new : list of ref Source; 4702 for (old := srcs.srcs; old != nil; old = tl old) { 4703 src := hd old; 4704 if (src == s) 4705 continue; 4706 new = src :: new; 4707 } 4708 srcs.srcs = new; 4709 } 4710} 4711 4712Sources.waitsrc(srcs: self ref Sources) : ref Source 4713{ 4714 if (srcs == nil) 4715 return nil; 4716 4717 bsl : list of ref ByteSource; 4718 4719 if (srcs.reqd == nil && srcs.main != nil) { 4720 pick s := srcs.main { 4721 Shtml => 4722 if (s.itsrc.toks != nil || s.itsrc.reqddata != nil) 4723 return s; 4724 } 4725 } 4726 4727 # always check for subordinates 4728 for (sl := srcs.srcs; sl != nil; sl = tl sl) 4729 bsl = (hd sl).bs :: bsl; 4730 # reqd is taken in preference to main source as main 4731 # cannot be processed until we have the whole of reqd 4732 if (srcs.reqd != nil) 4733 bsl = srcs.reqd.bs :: bsl; 4734 else if (srcs.main != nil) 4735 bsl = srcs.main.bs :: bsl; 4736 if (bsl == nil) 4737 return nil; 4738 bs : ref ByteSource; 4739 for (;;) { 4740 bs = CU->waitreq(bsl); 4741 if (srcs.reqd == nil || srcs.reqd.bs != bs) 4742 break; 4743 # only interested in reqd if we have got it all 4744 if (bs.err != "" || bs.eof) 4745 return srcs.reqd; 4746 } 4747 if (srcs.main != nil && srcs.main.bs == bs) 4748 return srcs.main; 4749 found : ref Source; 4750 for(sl = srcs.srcs; sl != nil; sl = tl sl) { 4751 s := hd sl; 4752 if(s.bs == bs) { 4753 found = s; 4754 break; 4755 } 4756 } 4757 CU->assert(found != nil); 4758 return found; 4759} 4760 4761# spawned to animate images in frame f 4762animproc(f: ref Frame) 4763{ 4764 f.animpid = sys->pctl(0, nil); 4765 aits : list of ref Item = nil; 4766 # let del be millisecs to sleep before next frame change 4767 del := 10000000; 4768 d : int; 4769 for(il := f.doc.images; il != nil; il = tl il) { 4770 it := hd il; 4771 pick i := it { 4772 Iimage => 4773 ms := i.ci.mims; 4774 if(len ms > 1) { 4775 loc := f.find(zp, it); 4776 if(loc == nil) { 4777 # could be background, I suppose -- don't animate it 4778 if(dbg) 4779 sys->print("couldn't find item for animated image\n"); 4780 continue; 4781 } 4782 p := loc.le[loc.n-1].pos; 4783 p.x += int i.hspace + int i.border; 4784 # BUG: should get background from least enclosing layout 4785 ctl := Control.newanimimage(f, i.ci, f.layout.background); 4786 ctl.r = ctl.r.addpt(p); 4787 i.ctlid = f.addcontrol(ctl); 4788 d = ms[0].delay; 4789 if(dbg) 4790 sys->print("added anim ctl %d for image %s, initial delay %d\n", 4791 i.ctlid, i.ci.src.tostring(), d); 4792 aits = it :: aits; 4793 if(d < del) 4794 del = d; 4795 } 4796 } 4797 } 4798 if(aits == nil) 4799 return; 4800 tot := big 0; 4801 for(;;) { 4802 sys->sleep(del); 4803 tot = tot + big del; 4804 newdel := 10000000; 4805 for(al := aits; al != nil; al = tl al) { 4806 it := hd al; 4807 pick i := hd al { 4808 Iimage => 4809 ms := i.ci.mims; 4810 pick c := f.controls[i.ctlid] { 4811 Canimimage => 4812 m := ms[c.cur]; 4813 d = m.delay; 4814 if(d > 0) 4815 d -= int (tot - c.ts); 4816 if(d == 0) { 4817 # advance to next frame and show it 4818 c.cur++; 4819 if(c.cur == len ms) 4820 c.cur = 0; 4821 d = ms[c.cur].delay; 4822 c.ts = tot; 4823 c.draw(1); 4824 } 4825 if(d < newdel) 4826 newdel = d; 4827 } 4828 } 4829 } 4830 del = newdel; 4831 } 4832} 4833