1 21 22 package org.lobobrowser.html.renderer; 23 24 import java.awt.Color ; 25 import java.awt.Component ; 26 import java.awt.Graphics ; 27 import java.awt.Image ; 28 import java.awt.Insets ; 29 import java.awt.Rectangle ; 30 import java.awt.image.ImageObserver ; 31 import java.net.MalformedURLException ; 32 import java.net.URL ; 33 import java.security.AccessController ; 34 import java.security.PrivilegedAction ; 35 import java.util.Collection ; 36 import java.util.HashSet ; 37 import java.util.Iterator ; 38 import java.util.logging.Level ; 39 40 import org.lobobrowser.html.HttpRequest; 41 import org.lobobrowser.html.ReadyStateChangeListener; 42 import org.lobobrowser.html.UserAgentContext; 43 import org.lobobrowser.html.domimpl.*; 44 import org.lobobrowser.html.style.BackgroundInfo; 45 import org.lobobrowser.html.style.CSS2PropertiesImpl; 46 import org.lobobrowser.html.style.HtmlValues; 47 import org.lobobrowser.html.style.RenderState; 48 import org.lobobrowser.util.gui.ColorFactory; 49 import org.w3c.dom.css.CSS2Properties; 50 51 abstract class BaseElementRenderable extends BaseRCollection implements RElement, RenderableContainer, java.awt.image.ImageObserver { 52 public static final int OVERFLOW_NONE = 0; 53 public static final int OVERFLOW_SCROLL = 1; 54 public static final int OVERFLOW_AUTO = 2; 55 public static final int OVERFLOW_HIDDEN = 3; 56 public static final int OVERFLOW_VISIBLE = 4; 57 public static final int OVERFLOW_VERTICAL = 5; 58 59 63 private Collection guiComponents = null; 64 65 69 protected Collection delayedPairs = null; 70 71 73 77 protected Color backgroundColor; 78 protected volatile Image backgroundImage; 79 protected int zIndex; 80 protected Color borderTopColor; 81 protected Color borderLeftColor; 82 protected Color borderBottomColor; 83 protected Color borderRightColor; 84 protected Insets borderInsets; 85 protected String lastBackgroundImageUri; 86 protected Insets defaultMarginInsets; 87 private int cachedOverflow = -1; 88 89 protected final UserAgentContext userAgentContext; 90 91 public BaseElementRenderable(RenderableContainer container, ModelNode modelNode, UserAgentContext ucontext) { 92 super(container, modelNode); 93 this.userAgentContext = ucontext; 94 } 95 96 public float getAlignmentX() { 97 return 0.0f; 98 } 99 100 public float getAlignmentY() { 101 return 0.0f; 102 } 103 104 126 protected boolean layoutDeepCanBeInvalidated = false; 127 128 134 public final void invalidateLayoutDeep() { 135 if(this.layoutDeepCanBeInvalidated) { 136 this.layoutDeepCanBeInvalidated = false; 137 this.invalidateLayoutLocal(); 138 Iterator i = this.getRenderables(); 139 if(i != null) { 140 while(i.hasNext()) { 141 Object r = i.next(); 142 if(r instanceof RCollection) { 143 ((RCollection) r).invalidateLayoutDeep(); 144 } 145 } 146 } 147 } 148 } 149 150 protected void invalidateLayoutLocal() { 151 this.cachedOverflow = -1; 152 } 153 154 protected int getDeclaredWidth(RenderState renderState, int availWidth) { 155 Object rootNode = this.modelNode; 156 if(rootNode instanceof HTMLElementImpl) { 157 HTMLElementImpl element = (HTMLElementImpl) rootNode; 158 CSS2Properties props = element.getCurrentStyle(); 159 if(props == null) { 160 return -1; 161 } 162 String widthText = props.getWidth(); 163 if(widthText == null || "".equals(widthText)) { 164 return -1; 165 } 166 return HtmlValues.getPixelSize(widthText, renderState, -1, availWidth); 167 } 168 else { 169 return -1; 170 } 171 } 172 173 protected int getDeclaredHeight(RenderState renderState, int availHeight) { 174 Object rootNode = this.modelNode; 175 if(rootNode instanceof HTMLElementImpl) { 176 HTMLElementImpl element = (HTMLElementImpl) rootNode; 177 CSS2Properties props = element.getCurrentStyle(); 178 if(props == null) { 179 return -1; 180 } 181 String heightText = props.getHeight(); 182 if(heightText == null || "".equals(heightText)) { 183 return -1; 184 } 185 return HtmlValues.getPixelSize(heightText, renderState, -1, availHeight); 186 } 187 else { 188 return -1; 189 } 190 } 191 192 protected int getOverflow() { 193 int co = this.cachedOverflow; 194 if(co != -1) { 195 return co; 196 } 197 Object rootNode = this.modelNode; 198 if(rootNode instanceof HTMLElementImpl) { 199 HTMLElementImpl element = (HTMLElementImpl) rootNode; 200 CSS2Properties props = element.getCurrentStyle(); 201 if(props == null) { 202 co = OVERFLOW_NONE; 203 } 204 else { 205 String overflowText = props.getOverflow(); 206 if(overflowText == null) { 207 co = OVERFLOW_NONE; 208 } 209 else { 210 String overflowTextTL = overflowText.toLowerCase(); 211 if("scroll".equals(overflowTextTL)) { 212 co = OVERFLOW_SCROLL; 213 } 214 else if("auto".equals(overflowTextTL)) { 215 co = OVERFLOW_AUTO; 216 } 217 else if("vertical".equals(overflowTextTL)) { 218 co = OVERFLOW_VERTICAL; 219 } 220 else if("hidden".equals(overflowTextTL)) { 221 co = OVERFLOW_HIDDEN; 222 } 223 else if("visible".equals(overflowTextTL)) { 224 co = OVERFLOW_VISIBLE; 225 } 226 else { 227 co = OVERFLOW_NONE; 228 } 229 } 230 } 231 } 232 else { 233 co = OVERFLOW_NONE; 234 } 235 this.cachedOverflow = co; 236 return co; 237 } 238 239 242 public void paint(Graphics g) { 243 } 244 245 public final void layout(int availWidth, int availHeight) { 246 this.layout(availWidth, availHeight, false, false); 247 } 248 249 253 public final void layout(int availWidth, int availHeight, boolean expandWidth, boolean expandHeight) { 254 try { 256 this.doLayout(availWidth, availHeight, expandWidth, expandHeight); 257 } finally { 258 this.layoutUpTreeCanBeInvalidated = true; 259 this.layoutDeepCanBeInvalidated = true; 260 } 262 } 263 264 protected abstract void doLayout(int availWidth, int availHeight, boolean expandWidth, boolean expandHeight); 265 266 protected final void sendGUIComponentsToParent() { 267 Collection gc = this.guiComponents; 270 int count = 0; 271 if(gc != null) { 272 RenderableContainer rc = this.container; 273 Iterator i = gc.iterator(); 274 while(i.hasNext()) { 275 count++; 276 rc.add((Component ) i.next()); 277 } 278 } 279 } 280 281 protected final void clearGUIComponents() { 282 Collection gc = this.guiComponents; 283 if(gc != null) { 284 gc.clear(); 285 } 286 } 287 288 291 public Component add(Component component) { 292 Collection gc = this.guiComponents; 298 if(gc == null) { 299 gc = new HashSet (1); 300 this.guiComponents = gc; 301 } 302 gc.add(component); 303 return component; 304 } 305 306 public void updateAllWidgetBounds() { 307 this.container.updateAllWidgetBounds(); 308 } 309 310 314 public void updateWidgetBounds() { 315 java.awt.Point guiPoint = this.getGUIPoint(0, 0); 316 this.updateWidgetBounds(guiPoint.x, guiPoint.y); 317 } 318 319 protected void applyStyle() { 320 Object rootNode = this.modelNode; 323 HTMLElementImpl rootElement; 324 if(rootNode instanceof HTMLDocumentImpl) { 325 HTMLDocumentImpl doc = (HTMLDocumentImpl) rootNode; 326 rootElement = (HTMLElementImpl) doc.getBody(); 328 } 329 else { 330 rootElement = (HTMLElementImpl) rootNode; 331 } 332 if(rootElement == null) { 333 this.clearStyle(); 334 this.backgroundColor = null; 335 this.backgroundImage = null; 336 this.lastBackgroundImageUri = null; 337 return; 338 } 339 RenderState rs = rootElement.getRenderState(); 340 if(rs == null) { 341 throw new IllegalStateException ("Element without render state: " + rootElement + "; parent=" + rootElement.getParentNode()); 342 } 343 BackgroundInfo binfo = rs.getBackgroundInfo(); 344 this.backgroundColor = binfo == null ? null : binfo.backgroundColor; 345 String backgroundImageUri = binfo == null ? null 346 : binfo.backgroundImage; 347 if (backgroundImageUri == null) { 348 this.backgroundImage = null; 349 this.lastBackgroundImageUri = null; 350 } 351 else if(!backgroundImageUri.equals(this.lastBackgroundImageUri)) { 352 this.lastBackgroundImageUri = backgroundImageUri; 353 this.loadBackgroundImage(backgroundImageUri); 354 } 355 CSS2PropertiesImpl props = rootElement.getCurrentStyle(); 356 if(props == null) { 357 this.clearStyle(); 358 } 359 else { 360 this.borderInsets = null; 361 this.borderTopColor = null; 362 this.borderLeftColor = null; 363 this.borderBottomColor = null; 364 this.borderRightColor = null; 365 String border = props.getBorder(); 366 if(border != null) { 367 this.applyBorder(rs, border); 368 } 369 this.borderInsets = HtmlValues.getBorderInsets(this.borderInsets, props, rs); 370 String borderColorText = props.getBorderColor(); 371 if(borderColorText != null) { 372 Color [] colorsArray = HtmlValues.getColors(borderColorText); 373 this.borderTopColor = colorsArray[0]; 374 this.borderLeftColor = colorsArray[1]; 375 this.borderBottomColor = colorsArray[2]; 376 this.borderRightColor = colorsArray[3]; 377 } 378 String borderTopColorText = props.getBorderTopColor(); 379 if(borderTopColorText != null) { 380 this.borderTopColor = ColorFactory.getInstance().getColor(borderTopColorText); 381 } 382 String borderLeftColorText = props.getBorderLeftColor(); 383 if(borderLeftColorText != null) { 384 this.borderLeftColor = ColorFactory.getInstance().getColor(borderLeftColorText); 385 } 386 String borderBottomColorText = props.getBorderBottomColor(); 387 if(borderBottomColorText != null) { 388 this.borderBottomColor = ColorFactory.getInstance().getColor(borderBottomColorText); 389 } 390 String borderRightColorText = props.getBorderRightColor(); 391 if(borderRightColorText != null) { 392 this.borderRightColor = ColorFactory.getInstance().getColor(borderRightColorText); 393 } 394 String zIndex = props.getZIndex(); 395 if(zIndex != null) { 396 try { 397 this.zIndex = Integer.parseInt(zIndex); 398 } catch(NumberFormatException err) { 399 logger.log(Level.WARNING, "Unable to parse z-index [" + zIndex + "] in element " + this.modelNode + ".", err); 400 this.zIndex = 0; 401 } 402 } 403 else { 404 this.zIndex = 0; 405 } 406 } 407 408 } 410 411 protected void loadBackgroundImage(final String uri) { 412 ModelNode rc = this.modelNode; 413 UserAgentContext ctx = this.userAgentContext; 414 if(ctx != null) { 415 final HttpRequest request = ctx.createHttpRequest(); 416 request.addReadyStateChangeListener(new ReadyStateChangeListener() { 417 public void readyStateChanged() { 418 int readyState = request.getReadyState(); 419 if(readyState == HttpRequest.STATE_COMPLETE) { 420 int status = request.getStatus(); 421 if(status == 200 || status == 0) { 422 Image img = request.getResponseImage(); 423 BaseElementRenderable.this.backgroundImage = img; 424 int w = img.getWidth(BaseElementRenderable.this); 426 int h = img.getHeight(BaseElementRenderable.this); 427 if(w != -1 && h != -1) { 429 BaseElementRenderable.this.repaint(); 430 } 431 } 432 } 433 } 434 }); 435 try { 436 final URL fullUrl = rc.getFullURL(uri); 437 SecurityManager sm = System.getSecurityManager(); 438 if(sm == null) { 439 request.open("GET", fullUrl); 440 } 441 else { 442 AccessController.doPrivileged(new PrivilegedAction () { 443 public Object run() { 444 request.open("GET", fullUrl); 447 return null; 448 } 449 }); 450 } 451 } catch(MalformedURLException mfu) { 452 rc.warn("Bad image URI: [" + uri + "]", mfu); 453 } 454 } 455 } 456 457 public int getZIndex() { 458 return this.zIndex; 459 } 460 461 void applyBorder(RenderState renderState, String border) { 462 String [] tokens = HtmlValues.splitCssValue(border); 463 for(int i = 0; i < tokens.length; i++) { 464 String token = tokens[i]; 465 if(HtmlValues.isLength(token)) { 466 int pixelSize = HtmlValues.getPixelSize(token, renderState, 0); 467 Insets bi = new Insets (pixelSize, pixelSize, pixelSize, pixelSize); 468 this.borderInsets = bi; 469 } 470 else if(ColorFactory.getInstance().isColor(token)) { 471 Color color = ColorFactory.getInstance().getColor(token); 472 this.borderLeftColor = color; 473 this.borderRightColor = color; 474 this.borderTopColor = color; 475 this.borderBottomColor = color; 476 } 477 else if(HtmlValues.isBorderStyle(token)) { 478 if("solid".equalsIgnoreCase(token) || "dashed".equalsIgnoreCase(token)) { 480 Insets bi = this.borderInsets; 481 if(bi == null) { 482 bi = new Insets (4, 4, 4, 4); 483 this.borderInsets = bi; 484 } 485 } 486 } 487 } 488 } 489 490 private Color getBorderTopColor() { 491 Color c = this.borderTopColor; 492 return c == null ? Color.black : c; 493 } 494 495 private Color getBorderLeftColor() { 496 Color c = this.borderLeftColor; 497 return c == null ? Color.black : c; 498 } 499 500 private Color getBorderBottomColor() { 501 Color c = this.borderBottomColor; 502 return c == null ? Color.black : c; 503 } 504 505 private Color getBorderRightColor() { 506 Color c = this.borderRightColor; 507 return c == null ? Color.black : c; 508 } 509 510 protected void prePaint(java.awt.Graphics g) { 511 int startWidth = this.width; 512 int startHeight = this.height; 513 int totalWidth = startWidth; 514 int totalHeight = startHeight; 515 int startX = 0; 516 int startY = 0; 517 ModelNode node = this.modelNode; 518 RenderState rs = node.getRenderState(); 519 Insets marginInsets = this.getMarginInsets(rs); 520 if(marginInsets != null) { 521 totalWidth -= (marginInsets.left + marginInsets.right); 522 totalHeight -= (marginInsets.top + marginInsets.bottom); 523 startX += marginInsets.left; 524 startY += marginInsets.top; 525 } 526 Insets borderInsets = this.borderInsets; 527 if(borderInsets != null) { 528 int btop = borderInsets.top; 529 int bleft = borderInsets.left; 530 int bright = borderInsets.right; 531 int bbottom = borderInsets.bottom; 532 533 int newTotalWidth = totalWidth - (bleft + bright); 534 int newTotalHeight = totalHeight - (btop + bbottom); 535 int newStartX = startX + bleft; 536 int newStartY = startY + btop; 537 Rectangle clientRegion = new Rectangle (newStartX, newStartY, newTotalWidth, newTotalHeight); 538 539 Rectangle clipBounds = g.getClipBounds(); 542 if(!clientRegion.contains(clipBounds)) { 543 if(btop > 0) { 545 g.setColor(this.getBorderTopColor()); 546 for(int i = 0; i < btop; i++) { 547 int leftOffset = (i * bleft) / btop; 548 int rightOffset = (i * bright) / btop; 549 g.drawLine(startX + leftOffset, startY + i, startX + totalWidth - rightOffset - 1, startY + i); 550 } 551 } 552 if(bright > 0) { 553 g.setColor(this.getBorderRightColor()); 554 int lastX = startX + totalWidth - 1; 555 for(int i = 0; i < bright; i++) { 556 int topOffset = (i * btop) / bright; 557 int bottomOffset = (i * bbottom) / bright; 558 g.drawLine(lastX - i, startY + topOffset, lastX - i, startY + totalHeight - bottomOffset - 1); 559 } 560 } 561 if(bleft > 0) { 562 g.setColor(this.getBorderLeftColor()); 563 for(int i = 0; i < bleft; i++) { 564 int topOffset = (i * btop) / bleft; 565 int bottomOffset = (i * bbottom) / bleft; 566 g.drawLine(startX + i, startY + topOffset, startX + i, startY + totalHeight - bottomOffset - 1); 567 } 568 } 569 if(bbottom > 0) { 570 g.setColor(this.getBorderBottomColor()); 571 int lastY = startY + totalHeight - 1; 572 for(int i = 0; i < bbottom; i++) { 573 int leftOffset = (i * bleft) / bbottom; 574 int rightOffset = (i * bright) / bbottom; 575 g.drawLine(startX + leftOffset, lastY - i, startX + totalWidth - rightOffset - 1, lastY - i); 576 } 577 } 578 } 579 580 totalWidth = newTotalWidth; 582 totalHeight = newTotalHeight; 583 startX = newStartX; 584 startY = newStartY; 585 586 } 587 Graphics clientG = g.create(startX, startY, totalWidth, totalHeight); 589 try { 590 Rectangle bkgBounds = null; 591 if(node != null) { 592 Color bkg = this.backgroundColor; 593 if(bkg != null && bkg.getAlpha() > 0) { 594 clientG.setColor(bkg); 595 bkgBounds = clientG.getClipBounds(); 596 clientG.fillRect(bkgBounds.x, bkgBounds.y, bkgBounds.width, bkgBounds.height); 597 } 598 BackgroundInfo binfo = rs == null ? null : rs.getBackgroundInfo(); 599 Image image = this.backgroundImage; 600 if(image != null) { 601 if(bkgBounds == null) { 602 bkgBounds = clientG.getClipBounds(); 603 } 604 int w = image.getWidth(this); 605 int h = image.getHeight(this); 606 if(w != -1 && h != -1) { 607 switch(binfo == null ? BackgroundInfo.BR_REPEAT : binfo.backgroundRepeat) { 608 case BackgroundInfo.BR_NO_REPEAT: { 609 int imageX; 610 if(binfo.backgroundXPositionAbsolute) { 611 imageX = binfo.backgroundXPosition; 612 } 613 else { 614 imageX = (binfo.backgroundXPosition * (totalWidth - w)) / 100; 615 } 616 int imageY; 617 if(binfo.backgroundYPositionAbsolute) { 618 imageY = binfo.backgroundYPosition; 619 } 620 else { 621 imageY =(binfo.backgroundYPosition * (totalHeight - h)) / 100; 622 } 623 clientG.drawImage(image, imageX, imageY, w, h, this); 624 break; 625 } 626 case BackgroundInfo.BR_REPEAT_X: { 627 int imageY; 628 if(binfo.backgroundYPositionAbsolute) { 629 imageY = binfo.backgroundYPosition; 630 } 631 else { 632 imageY = (binfo.backgroundYPosition * (totalHeight - h)) / 100; 633 } 634 int x = (bkgBounds.x / w) * w; 636 int topX = bkgBounds.x + bkgBounds.width; 637 for(; x < topX; x += w) { 638 clientG.drawImage(image, x, imageY, w, h, this); 639 } 640 break; 641 } 642 case BackgroundInfo.BR_REPEAT_Y: { 643 int imageX; 644 if(binfo.backgroundXPositionAbsolute) { 645 imageX = binfo.backgroundXPosition; 646 } 647 else { 648 imageX = (binfo.backgroundXPosition * (totalWidth - w)) / 100; 649 } 650 int y = (bkgBounds.y / h) * h; 652 int topY = bkgBounds.y + bkgBounds.height; 653 for(; y < topY; y += h) { 654 clientG.drawImage(image, imageX, y, w, h, this); 655 } 656 break; 657 } 658 default: { 659 int baseX = (bkgBounds.x / w) * w; 661 int baseY = (bkgBounds.y / h) * h; 662 int topX = bkgBounds.x + bkgBounds.width; 663 int topY = bkgBounds.y + bkgBounds.height; 664 for(int x = baseX; x < topX; x += w) { 666 for(int y = baseY; y < topY; y += h) { 667 clientG.drawImage(image, x, y, w, h, this); 668 } 669 } 670 break; 671 } 672 } 673 } 674 } 675 } 676 } finally { 677 clientG.dispose(); 678 } 679 } 680 681 void clearStyle() { 682 this.borderInsets = null; 683 this.borderTopColor = null; 684 this.borderLeftColor = null; 685 this.borderBottomColor = null; 686 this.borderRightColor = null; 687 this.zIndex = 0; 688 } 689 690 protected final Insets getMarginInsets(RenderState rs) { 691 Insets mi = rs.getMarginInsets(); 692 if(mi == null) { 693 return this.defaultMarginInsets; 694 } 695 return mi; 696 } 697 698 public boolean imageUpdate(Image img, int infoflags, int x, int y, int w, int h) { 699 if((infoflags & ImageObserver.ALLBITS) != 0 || (infoflags & ImageObserver.FRAMEBITS) != 0) { 702 this.repaint(); 703 } 704 return true; 705 } 706 707 protected static final int SCROLL_BAR_THICKNESS = 16; 708 709 713 public Insets getInsets(boolean hscroll, boolean vscroll) { 714 RenderState rs = this.modelNode.getRenderState(); 715 Insets mi = this.getMarginInsets(rs); 716 Insets bi = this.borderInsets; 717 int top = 0; 718 int bottom = 0; 719 int left = 0; 720 int right = 0; 721 if(mi != null) { 722 top += mi.top; 723 left += mi.left; 724 bottom += mi.bottom; 725 right += mi.right; 726 } 727 if(bi != null) { 734 top += bi.top; 735 left += bi.left; 736 bottom += bi.bottom; 737 right += bi.right; 738 } 739 if(hscroll) { 740 bottom += SCROLL_BAR_THICKNESS; 741 } 742 if(vscroll) { 743 right += SCROLL_BAR_THICKNESS; 744 } 745 return new Insets (top, left, bottom, right); 746 } 747 748 protected final void sendDelayedPairsToParent() { 749 Collection gc = this.delayedPairs; 752 if(gc != null) { 753 RenderableContainer rc = this.container; 754 Iterator i = gc.iterator(); 755 while(i.hasNext()) { 756 DelayedPair pair = (DelayedPair) i.next(); 757 if(pair.targetParent != this) { 758 rc.addDelayedPair(pair); 759 } 760 } 761 } 762 } 763 764 public final void clearDelayedPairs() { 765 Collection gc = this.delayedPairs; 766 if(gc != null) { 767 gc.clear(); 768 } 769 } 770 771 public final Collection getDelayedPairs() { 772 return this.delayedPairs; 773 } 774 775 778 public void addDelayedPair(DelayedPair pair) { 779 Collection gc = this.delayedPairs; 785 if(gc == null) { 786 gc = new java.util.LinkedList (); 790 this.delayedPairs = gc; 791 } 792 gc.add(pair); 793 } 794 795 public RenderableContainer getParentContainer() { 796 return this.container; 797 } 798 } 799 | Popular Tags |