/*
GNU GENERAL LICENSE
Copyright (C) 2006 The Lobo Project. Copyright (C) 2014 - 2017 Lobo Evolution
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public
License as published by the Free Software Foundation; either
verion 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General License for more details.
You should have received a copy of the GNU General Public
along with this program. If not, see <http://www.gnu.org/licenses/>.
Contact info: lobochief@users.sourceforge.net; ivan.difrancesco@yahoo.it
*/
package org.lobobrowser.html.renderer;
import java.awt.Color;
import java.awt.Component;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.net.URLConnection;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import javax.imageio.IIOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import org.apache.batik.transcoder.TranscoderException;
import org.lobobrowser.html.dombl.ModelNode;
import org.lobobrowser.html.dombl.SVGRasterizer;
import org.lobobrowser.html.domimpl.HTMLDocumentImpl;
import org.lobobrowser.html.domimpl.HTMLElementImpl;
import org.lobobrowser.html.info.BackgroundInfo;
import org.lobobrowser.html.info.BorderInfo;
import org.lobobrowser.html.renderstate.RenderState;
import org.lobobrowser.html.style.AbstractCSS2Properties;
import org.lobobrowser.html.style.CSSValuesProperties;
import org.lobobrowser.html.style.HtmlInsets;
import org.lobobrowser.html.style.HtmlValues;
import org.lobobrowser.http.UserAgentContext;
import org.lobobrowser.util.SSLCertificate;
import org.lobobrowser.util.Strings;
import org.lobobrowser.util.gui.GUITasks;
import org.w3c.dom.css.CSS2Properties;
/**
* The Class BaseElementRenderable.
*/
public abstract class BaseElementRenderable extends BaseRCollection
implements RElement, RenderableContainer, ImageObserver,CSSValuesProperties {
/** The Constant INVALID_SIZE. */
protected static final Integer INVALID_SIZE = new Integer(Integer.MIN_VALUE);
/**
* A collection of all GUI components added by descendents.
*/
private Collection<Component> guiComponents = null;
/**
* A list of absolute positioned or float parent-child pairs.
*/
protected Collection<DelayedPair> delayedPairs = null;
/**
* Background color which may be different to that from RenderState in the
* case of a Document node.
*/
protected Color backgroundColor;
/** The background image. */
protected volatile Image backgroundImage;
/** The z index. */
protected int zIndex;
/** The border top color. */
protected Color borderTopColor;
/** The border left color. */
protected Color borderLeftColor;
/** The border bottom color. */
protected Color borderBottomColor;
/** The border right color. */
protected Color borderRightColor;
/** The border insets. */
protected Insets borderInsets;
/** The margin insets. */
protected Insets marginInsets;
/** The padding insets. */
protected Insets paddingInsets;
/** The border info. */
protected BorderInfo borderInfo;
/** The last background image uri. */
protected URL lastBackgroundImageUri;
/** The default margin insets. */
protected Insets defaultMarginInsets = null;
/** The default padding insets. */
protected Insets defaultPaddingInsets = null;
/** The overflow x. */
protected int overflowX;
/** The overflow y. */
protected int overflowY;
/** The user agent context. */
protected final UserAgentContext userAgentContext;
/**
* Instantiates a new base element renderable.
*
* @param container
* the container
* @param modelNode
* the model node
* @param ucontext
* the ucontext
*/
public BaseElementRenderable(RenderableContainer container, ModelNode modelNode, UserAgentContext ucontext) {
super(container, modelNode);
this.userAgentContext = ucontext;
}
/**
* Sets the default padding insets.
*
* @param insets
* the new default padding insets
*/
public void setDefaultPaddingInsets(Insets insets) {
this.defaultPaddingInsets = insets;
}
/**
* Sets the default margin insets.
*
* @param insets
* the new default margin insets
*/
public void setDefaultMarginInsets(Insets insets) {
this.defaultMarginInsets = insets;
}
/**
* Gets the alignment x.
*
* @return the alignment x
*/
public float getAlignmentX() {
return 0.0f;
}
/**
* Gets the alignment y.
*
* @return the alignment y
*/
public float getAlignmentY() {
return 0.0f;
}
/** The layout deep can be invalidated. */
protected boolean layoutDeepCanBeInvalidated = false;
/**
* Invalidates this Renderable and all descendents. This is only used in
* special cases, such as when a new style sheet is added.
*/
@Override
public final void invalidateLayoutDeep() {
if (this.layoutDeepCanBeInvalidated) {
this.layoutDeepCanBeInvalidated = false;
this.invalidateLayoutLocal();
Iterator<?> i = this.getRenderables();
if (i != null) {
while (i.hasNext()) {
Object r = i.next();
if (r instanceof RCollection) {
((RCollection) r).invalidateLayoutDeep();
}
}
}
}
}
/*
* (non-Javadoc)
*
* @see org.lobobrowser.html.renderer.BaseBoundableRenderable#
* invalidateLayoutLocal()
*/
@Override
protected void invalidateLayoutLocal() {
RenderState rs = this.modelNode.getRenderState();
if (rs != null) {
rs.invalidate();
}
this.overflowX = RenderState.OVERFLOW_NONE;
this.overflowY = RenderState.OVERFLOW_NONE;
this.declaredWidth = INVALID_SIZE;
this.declaredHeight = INVALID_SIZE;
this.lastAvailHeightForDeclared = -1;
this.lastAvailWidthForDeclared = -1;
}
/** The declared width. */
private Integer declaredWidth = INVALID_SIZE;
/** The declared height. */
private Integer declaredHeight = INVALID_SIZE;
/** The last avail width for declared. */
private int lastAvailWidthForDeclared = -1;
/** The last avail height for declared. */
private int lastAvailHeightForDeclared = -1;
/**
* Gets the declared width.
*
* @param renderState
* the render state
* @param actualAvailWidth
* the actual avail width
* @return the declared width
*/
protected Integer getDeclaredWidth(RenderState renderState, int actualAvailWidth) {
Integer dw = this.declaredWidth;
if ((dw == INVALID_SIZE) || (actualAvailWidth != this.lastAvailWidthForDeclared)) {
this.lastAvailWidthForDeclared = actualAvailWidth;
int dwInt = this.getDeclaredWidthImpl(renderState, actualAvailWidth);
dw = dwInt == -1 ? null : new Integer(dwInt);
this.declaredWidth = dw;
}
return dw;
}
/**
* Checks for declared width.
*
* @return true, if successful
*/
public final boolean hasDeclaredWidth() {
Integer dw = this.declaredWidth;
if (dw == INVALID_SIZE) {
Object rootNode = this.modelNode;
if (rootNode instanceof HTMLElementImpl) {
HTMLElementImpl element = (HTMLElementImpl) rootNode;
CSS2Properties props = element.getCurrentStyle();
if (props == null) {
return false;
}
return !Strings.isBlank(props.getWidth());
}
return false;
}
return dw != null;
}
/**
* Gets the declared width impl.
*
* @param renderState
* the render state
* @param availWidth
* the avail width
* @return the declared width impl
*/
private final int getDeclaredWidthImpl(RenderState renderState, int availWidth) {
Object rootNode = this.modelNode;
if (rootNode instanceof HTMLElementImpl) {
HTMLElementImpl element = (HTMLElementImpl) rootNode;
CSS2Properties props = element.getCurrentStyle();
if (props == null) {
return -1;
}
String widthText = props.getWidth();
if (INHERIT.equalsIgnoreCase(widthText)) {
widthText = element.getParentStyle().getWidth();
} else if (INITIAL.equalsIgnoreCase(widthText)) {
widthText = "100%";
}
int width = -1;
if (widthText !=null){
width = HtmlValues.getPixelSize(widthText, renderState, -1, availWidth);
}
if (props.getMaxWidth() != null) {
int maxWidth = HtmlValues.getPixelSize(props.getMaxWidth(), renderState, -1, availWidth);
if (width == -1 || width > maxWidth) {
width = maxWidth;
}
}
if (props.getMinWidth() != null) {
int minWidth = HtmlValues.getPixelSize(props.getMinWidth(), element.getRenderState(), 0, availWidth);
if (width == 0 || width < minWidth) {
width = minWidth;
}
}
return width;
} else {
return -1;
}
}
/**
* Gets the declared height.
*
* @param renderState
* the render state
* @param actualAvailHeight
* the actual avail height
* @return the declared height
*/
protected Integer getDeclaredHeight(RenderState renderState, int actualAvailHeight) {
Integer dh = this.declaredHeight;
if ((dh == INVALID_SIZE) || (actualAvailHeight != this.lastAvailHeightForDeclared)) {
this.lastAvailHeightForDeclared = actualAvailHeight;
int dhInt = this.getDeclaredHeightImpl(renderState, actualAvailHeight);
dh = dhInt == -1 ? null : new Integer(dhInt);
this.declaredHeight = dh;
}
return dh;
}
/**
* Gets the declared height impl.
*
* @param renderState
* the render state
* @param availHeight
* the avail height
* @return the declared height impl
*/
protected int getDeclaredHeightImpl(RenderState renderState, int availHeight) {
Object rootNode = this.modelNode;
if (rootNode instanceof HTMLElementImpl) {
HTMLElementImpl element = (HTMLElementImpl) rootNode;
CSS2Properties props = element.getCurrentStyle();
if (props == null) {
return -1;
}
String heightText = props.getHeight();
if (INHERIT.equalsIgnoreCase(heightText)) {
heightText = element.getParentStyle().getHeight();
} else if (INITIAL.equalsIgnoreCase(heightText)) {
heightText = "100%";
}
int height = -1;
if (heightText !=null){
height = HtmlValues.getPixelSize(heightText, element.getRenderState(), -1, availHeight);
}
if (props.getMaxHeight() != null) {
int maxHeight = HtmlValues.getPixelSize(props.getMaxHeight(), element.getRenderState(), -1, availHeight);
if (height == 0 || height > maxHeight) {
height = maxHeight;
}
}
if (props.getMinHeight() != null) {
int minHeight = HtmlValues.getPixelSize(props.getMinHeight(), element.getRenderState(), -1, availHeight);
if (height == 0 || height < minHeight) {
height = minHeight;
}
}
return height;
} else {
return -1;
}
}
/**
* All overriders should call super implementation.
*
* @param g
* the g
*/
@Override
public void paint(Graphics g) {
}
/**
* Lays out children, and deals with "valid" state. Override doLayout method
* instead of this one.
*
* @param availWidth
* the avail width
* @param availHeight
* the avail height
* @param sizeOnly
* the size only
*/
@Override
public final void layout(int availWidth, int availHeight, boolean sizeOnly) {
// Must call doLayout regardless of validity state.
try {
this.doLayout(availWidth, availHeight, sizeOnly);
} finally {
this.layoutUpTreeCanBeInvalidated = true;
this.layoutDeepCanBeInvalidated = true;
}
}
/**
* Do layout.
*
* @param availWidth
* the avail width
* @param availHeight
* the avail height
* @param sizeOnly
* the size only
*/
protected abstract void doLayout(int availWidth, int availHeight, boolean sizeOnly);
/**
* Send gui components to parent.
*/
protected final void sendGUIComponentsToParent() {
// Ensures that parent has all the components
// below this renderer node. (Parent expected to have removed them).
Collection<Component> gc = this.guiComponents;
if (gc != null) {
RenderableContainer rc = this.container;
Iterator<Component> i = gc.iterator();
while (i.hasNext()) {
rc.addComponent(i.next());
}
}
}
/**
* Clear gui components.
*/
protected final void clearGUIComponents() {
Collection<Component> gc = this.guiComponents;
if (gc != null) {
gc.clear();
}
}
/*
* (non-Javadoc)
*
* @see
* org.lobobrowser.html.render.RenderableContainer#add(java.awt.Component)
*/
@Override
public Component addComponent(Component component) {
// Expected to be called in GUI thread.
// Adds only in local collection.
// Does not remove from parent.
// Sending components to parent is done
// by sendGUIComponentsToParent().
Collection<Component> gc = this.guiComponents;
if (gc == null) {
gc = new HashSet<Component>(1);
this.guiComponents = gc;
}
gc.add(component);
return component;
}
/*
* (non-Javadoc)
*
* @see
* org.lobobrowser.html.renderer.RenderableContainer#updateAllWidgetBounds()
*/
@Override
public void updateAllWidgetBounds() {
this.container.updateAllWidgetBounds();
}
/**
* Updates widget bounds below this node only. Should not be called during
* general rendering.
*/
public void updateWidgetBounds() {
java.awt.Point guiPoint = this.getGUIPoint(0, 0);
this.updateWidgetBounds(guiPoint.x, guiPoint.y);
}
/*
* (non-Javadoc)
*
* @see org.lobobrowser.html.dombl.UINode#getBoundsRelativeToBlock()
*/
@Override
public Rectangle getBoundsRelativeToBlock() {
RCollection parent = this;
int x = 0, y = 0;
while (parent != null) {
x += parent.getX();
y += parent.getY();
parent = parent.getParent();
if (parent instanceof RElement) {
break;
}
}
return new Rectangle(x, y, this.getWidth(), this.getHeight());
}
/**
* Clear style.
*
* @param isRootBlock
* the is root block
*/
protected void clearStyle(boolean isRootBlock) {
this.borderInfo = null;
this.borderInsets = null;
this.borderTopColor = null;
this.borderLeftColor = null;
this.borderBottomColor = null;
this.borderRightColor = null;
this.zIndex = 0;
this.backgroundColor = null;
this.backgroundImage = null;
this.lastBackgroundImageUri = null;
this.overflowX = RenderState.OVERFLOW_VISIBLE;
this.overflowY = RenderState.OVERFLOW_VISIBLE;
if (isRootBlock) {
// The margin of the root block behaves like extra padding.
Insets insets1 = this.defaultMarginInsets;
Insets insets2 = this.defaultPaddingInsets;
Insets finalInsets = insets1 == null ? null
: new Insets(insets1.top, insets1.left, insets1.bottom, insets1.right);
if (insets2 != null) {
if (finalInsets == null) {
finalInsets = new Insets(insets2.top, insets2.left, insets2.bottom, insets2.right);
} else {
finalInsets.top += insets2.top;
finalInsets.bottom += insets2.bottom;
finalInsets.left += insets2.left;
finalInsets.right += insets2.right;
}
}
this.marginInsets = null;
this.paddingInsets = finalInsets;
} else {
this.marginInsets = this.defaultMarginInsets;
this.paddingInsets = this.defaultPaddingInsets;
}
}
/**
* Apply style.
*
* @param availWidth
* the avail width
* @param availHeight
* the avail height
*/
protected void applyStyle(int availWidth, int availHeight) {
// TODO: Can be optimized if there's no style?
// TODO: There's part of this that doesn't depend on availWidth
// and availHeight, so it doesn't need to be redone on
// every resize.
// Note: Overridden by tables and lists.
Object rootNode = this.modelNode;
HTMLElementImpl rootElement;
boolean isRootBlock;
if (rootNode instanceof HTMLDocumentImpl) {
isRootBlock = true;
HTMLDocumentImpl doc = (HTMLDocumentImpl) rootNode;
// Need to get BODY tag, for bgcolor, etc.
rootElement = (HTMLElementImpl) doc.getBody();
} else {
isRootBlock = false;
rootElement = (HTMLElementImpl) rootNode;
}
if (rootElement == null) {
this.clearStyle(isRootBlock);
return;
}
RenderState rs = rootElement.getRenderState();
if (rs == null) {
throw new IllegalStateException(
"Element without render state: " + rootElement + "; parent=" + rootElement.getParentNode());
}
BackgroundInfo binfo = rs.getBackgroundInfo();
this.backgroundColor = binfo == null ? null : binfo.getBackgroundColor();
URL backgroundImageUri = binfo == null ? null : binfo.getBackgroundImage();
if (backgroundImageUri == null) {
this.backgroundImage = null;
this.lastBackgroundImageUri = null;
} else if (!backgroundImageUri.equals(this.lastBackgroundImageUri)) {
this.lastBackgroundImageUri = backgroundImageUri;
this.loadBackgroundImage(backgroundImageUri);
}
AbstractCSS2Properties props = rootElement.getCurrentStyle();
if (props == null) {
this.clearStyle(isRootBlock);
} else {
BorderInfo borderInfo = rs.getBorderInfo();
this.borderInfo = borderInfo;
HtmlInsets binsets = borderInfo == null ? null : borderInfo.getInsets();
HtmlInsets minsets = rs.getMarginInsets();
HtmlInsets pinsets = rs.getPaddingInsets();
Insets defaultMarginInsets = this.defaultMarginInsets;
int dmleft = 0, dmright = 0, dmtop = 0, dmbottom = 0;
if (defaultMarginInsets != null) {
dmleft = defaultMarginInsets.left;
dmright = defaultMarginInsets.right;
dmtop = defaultMarginInsets.top;
dmbottom = defaultMarginInsets.bottom;
}
Insets defaultPaddingInsets = this.defaultPaddingInsets;
int dpleft = 0, dpright = 0, dptop = 0, dpbottom = 0;
if (defaultPaddingInsets != null) {
dpleft = defaultPaddingInsets.left;
dpright = defaultPaddingInsets.right;
dptop = defaultPaddingInsets.top;
dpbottom = defaultPaddingInsets.bottom;
}
Insets borderInsets = binsets == null ? null
: binsets.getAWTInsets(0, 0, 0, 0, availWidth, availHeight, 0, 0);
if (borderInsets == null) {
borderInsets = RBlockViewport.ZERO_INSETS;
}
Insets paddingInsets = pinsets == null ? defaultPaddingInsets
: pinsets.getAWTInsets(dptop, dpleft, dpbottom, dpright, availWidth, availHeight, 0, 0);
if (paddingInsets == null) {
paddingInsets = RBlockViewport.ZERO_INSETS;
}
Insets tentativeMarginInsets = minsets == null ? defaultMarginInsets
: minsets.getAWTInsets(dmtop, dmleft, dmbottom, dmright, availWidth, availHeight, 0, 0);
if (tentativeMarginInsets == null) {
tentativeMarginInsets = RBlockViewport.ZERO_INSETS;
}
int actualAvailWidth = availWidth - paddingInsets.left - paddingInsets.right - borderInsets.left
- borderInsets.right - tentativeMarginInsets.left - tentativeMarginInsets.right;
int actualAvailHeight = availHeight - paddingInsets.top - paddingInsets.bottom - borderInsets.top
- borderInsets.bottom - tentativeMarginInsets.top - tentativeMarginInsets.bottom;
Integer declaredWidth = this.getDeclaredWidth(rs, actualAvailWidth);
Integer declaredHeight = this.getDeclaredHeight(rs, actualAvailHeight);
int autoMarginX = 0, autoMarginY = 0;
if (declaredWidth != null) {
autoMarginX = (availWidth - declaredWidth.intValue()
- (borderInsets == null ? 0 : borderInsets.left - borderInsets.right)
- (paddingInsets == null ? 0 : paddingInsets.left - paddingInsets.right)) / 2;
}
if (declaredHeight != null) {
autoMarginY = (availHeight - declaredHeight.intValue()
- (borderInsets == null ? 0 : borderInsets.top - borderInsets.bottom)
- (paddingInsets == null ? 0 : paddingInsets.top - paddingInsets.bottom)) / 2;
}
this.borderInsets = borderInsets;
if (isRootBlock) {
// In the root block, the margin behaves like an extra padding.
Insets regularMarginInsets = ((autoMarginX == 0) && (autoMarginY == 0)) ? tentativeMarginInsets
: (minsets == null ? defaultMarginInsets
: minsets.getAWTInsets(dmtop, dmleft, dmbottom, dmright, availWidth, availHeight,
autoMarginX, autoMarginY));
if (regularMarginInsets == null) {
regularMarginInsets = RBlockViewport.ZERO_INSETS;
}
this.marginInsets = null;
this.paddingInsets = new Insets(paddingInsets.top + regularMarginInsets.top,
paddingInsets.left + regularMarginInsets.left,
paddingInsets.bottom + regularMarginInsets.bottom,
paddingInsets.right + regularMarginInsets.right);
} else {
this.paddingInsets = paddingInsets;
this.marginInsets = ((autoMarginX == 0) && (autoMarginY == 0)) ? tentativeMarginInsets
: (minsets == null ? defaultMarginInsets
: minsets.getAWTInsets(dmtop, dmleft, dmbottom, dmright, availWidth, availHeight,
autoMarginX, autoMarginY));
}
if (borderInfo != null) {
this.borderTopColor = borderInfo.getTopColor();
this.borderLeftColor = borderInfo.getLeftColor();
this.borderBottomColor = borderInfo.getBottomColor();
this.borderRightColor = borderInfo.getRightColor();
} else {
this.borderTopColor = null;
this.borderLeftColor = null;
this.borderBottomColor = null;
this.borderRightColor = null;
}
String zIndex = props.getZIndex();
if (zIndex != null) {
this.zIndex = HtmlValues.getPixelSize(zIndex, null, 0);
} else {
this.zIndex = 0;
}
this.overflowX = rs.getOverflowX();
this.overflowY = rs.getOverflowY();
}
// Check if background image needs to be loaded
}
/**
* Load background image.
*
* @param imageURL
* the image url
*/
protected void loadBackgroundImage(final URL imageURL) {
Image image = null;
String url = imageURL.toString();
try {
SSLCertificate.setCertificate();
URLConnection con = imageURL.openConnection();
con.setRequestProperty("User-Agent", UserAgentContext.DEFAULT_USER_AGENT);
if (url.endsWith(".svg")) {
SVGRasterizer r = new SVGRasterizer(imageURL);
image = r.bufferedImageToImage();
} else if (url.startsWith("https")) {
BufferedImage bi = ImageIO.read(con.getInputStream());
if(bi!= null)
image = Toolkit.getDefaultToolkit().createImage(bi.getSource());
} else if (url.endsWith(".gif")) {
try {
image = new ImageIcon(imageURL).getImage();
} catch (Exception e) {
image = ImageIO.read(con.getInputStream());
}
} else if (url.endsWith(".bmp")) {
image = ImageIO.read(con.getInputStream());
} else {
image = ImageIO.read(con.getInputStream());
}
BaseElementRenderable.this.backgroundImage = image;
int w = -1;
int h = -1;
if(image!= null){
w = image.getWidth(BaseElementRenderable.this);
h = image.getHeight(BaseElementRenderable.this);
}
if (w != -1 && h != -1) {
BaseElementRenderable.this.repaint();
}
} catch (FileNotFoundException | IIOException ex) {
logger.error("loadBackgroundImage(): Image not found "+url);
} catch (IOException | TranscoderException thrown) {
logger.error("loadBackgroundImage()", thrown);
} catch (Exception e) {
logger.error("loadBackgroundImage()", e);
}
}
/*
* (non-Javadoc)
*
* @see org.lobobrowser.html.renderer.BaseRenderable#getZIndex()
*/
@Override
public int getZIndex() {
return this.zIndex;
}
/**
* Gets the border top color.
*
* @return the border top color
*/
private Color getBorderTopColor() {
Color c = this.borderTopColor;
return c == null ? Color.black : c;
}
/**
* Gets the border left color.
*
* @return the border left color
*/
private Color getBorderLeftColor() {
Color c = this.borderLeftColor;
return c == null ? Color.black : c;
}
/**
* Gets the border bottom color.
*
* @return the border bottom color
*/
private Color getBorderBottomColor() {
Color c = this.borderBottomColor;
return c == null ? Color.black : c;
}
/**
* Gets the border right color.
*
* @return the border right color
*/
private Color getBorderRightColor() {
Color c = this.borderRightColor;
return c == null ? Color.black : c;
}
/**
* Gets the width element.
*
* @return the width element
*/
protected int getWidthElement() {
return this.width;
}
/**
* Gets the height element.
*
* @return the height element
*/
protected int getHeightElement() {
return this.height;
}
/**
* Gets the start x.
*
* @return the start x
*/
protected int getStartX() {
return 0;
}
/**
* Gets the start y.
*
* @return the start y
*/
protected int getStartY() {
return 0;
}
/**
* Pre paint.
*
* @param g
* the g
*/
protected void prePaint(java.awt.Graphics g) {
int startWidth = getWidthElement();
int startHeight = getHeightElement();
int totalWidth = startWidth;
int totalHeight = startHeight;
int startX = getStartX();
int startY = getStartY();
ModelNode node = this.modelNode;
RenderState rs = node.getRenderState();
Insets marginInsets = this.marginInsets;
if (marginInsets != null) {
totalWidth -= (marginInsets.left + marginInsets.right);
totalHeight -= (marginInsets.top + marginInsets.bottom);
startX += marginInsets.left;
startY += marginInsets.top;
}
Insets borderInsets = this.borderInsets;
if (borderInsets != null) {
int btop = borderInsets.top;
int bleft = borderInsets.left;
int bright = borderInsets.right;
int bbottom = borderInsets.bottom;
int newTotalWidth = totalWidth - (bleft + bright);
int newTotalHeight = totalHeight - (btop + bbottom);
int newStartX = startX + bleft;
int newStartY = startY + btop;
Rectangle clientRegion = new Rectangle(newStartX, newStartY, newTotalWidth, newTotalHeight);
// Paint borders if the clip bounds are not contained
// by the content area.
Rectangle clipBounds = g.getClipBounds();
if (!clientRegion.contains(clipBounds)) {
BorderInfo borderInfo = this.borderInfo;
if (btop > 0) {
g.setColor(this.getBorderTopColor());
int borderStyle = borderInfo == null ? HtmlValues.BORDER_STYLE_SOLID : borderInfo.getTopStyle();
for (int i = 0; i < btop; i++) {
int leftOffset = (i * bleft) / btop;
int rightOffset = (i * bright) / btop;
if (borderStyle == HtmlValues.BORDER_STYLE_DASHED) {
GUITasks.drawDashed(g, startX + leftOffset, startY + i,
(startX + totalWidth) - rightOffset - 1, startY + i, 10 + btop, 6);
} else {
g.drawLine(startX + leftOffset, startY + i, (startX + totalWidth) - rightOffset - 1,
startY + i);
}
}
}
if (bright > 0) {
int borderStyle = borderInfo == null ? HtmlValues.BORDER_STYLE_SOLID : borderInfo.getRightStyle();
g.setColor(this.getBorderRightColor());
int lastX = (startX + totalWidth) - 1;
for (int i = 0; i < bright; i++) {
int topOffset = (i * btop) / bright;
int bottomOffset = (i * bbottom) / bright;
if (borderStyle == HtmlValues.BORDER_STYLE_DASHED) {
GUITasks.drawDashed(g, lastX - i, startY + topOffset, lastX - i,
(startY + totalHeight) - bottomOffset - 1, 10 + bright, 6);
} else {
g.drawLine(lastX - i, startY + topOffset, lastX - i,
(startY + totalHeight) - bottomOffset - 1);
}
}
}
if (bbottom > 0) {
int borderStyle = borderInfo == null ? HtmlValues.BORDER_STYLE_SOLID : borderInfo.getBottomStyle();
g.setColor(this.getBorderBottomColor());
int lastY = (startY + totalHeight) - 1;
for (int i = 0; i < bbottom; i++) {
int leftOffset = (i * bleft) / bbottom;
int rightOffset = (i * bright) / bbottom;
if (borderStyle == HtmlValues.BORDER_STYLE_DASHED) {
GUITasks.drawDashed(g, startX + leftOffset, lastY - i,
(startX + totalWidth) - rightOffset - 1, lastY - i, 10 + bbottom, 6);
} else {
g.drawLine(startX + leftOffset, lastY - i, (startX + totalWidth) - rightOffset - 1,
lastY - i);
}
}
}
if (bleft > 0) {
int borderStyle = borderInfo == null ? HtmlValues.BORDER_STYLE_SOLID : borderInfo.getLeftStyle();
g.setColor(this.getBorderLeftColor());
for (int i = 0; i < bleft; i++) {
int topOffset = (i * btop) / bleft;
int bottomOffset = (i * bbottom) / bleft;
if (borderStyle == HtmlValues.BORDER_STYLE_DASHED) {
GUITasks.drawDashed(g, startX + i, startY + topOffset, startX + i,
(startY + totalHeight) - bottomOffset - 1, 10 + bleft, 6);
} else {
g.drawLine(startX + i, startY + topOffset, startX + i,
(startY + totalHeight) - bottomOffset - 1);
}
}
}
}
// Adjust client area border
totalWidth = newTotalWidth;
totalHeight = newTotalHeight;
startX = newStartX;
startY = newStartY;
}
// Using clientG (clipped below) beyond this point.
Graphics clientG = g.create(startX, startY, totalWidth, totalHeight);
try {
Rectangle bkgBounds = null;
if (node != null) {
Color bkg = this.backgroundColor;
if ((bkg != null) && (bkg.getAlpha() > 0)) {
clientG.setColor(bkg);
bkgBounds = clientG.getClipBounds();
clientG.fillRect(bkgBounds.x, bkgBounds.y, bkgBounds.width, bkgBounds.height);
}
BackgroundInfo binfo = rs == null ? null : rs.getBackgroundInfo();
Image image = this.backgroundImage;
if (image != null) {
if (bkgBounds == null) {
bkgBounds = clientG.getClipBounds();
}
int w = image.getWidth(this);
int h = image.getHeight(this);
if ((w != -1) && (h != -1)) {
switch (binfo == null ? BackgroundInfo.BR_REPEAT : binfo.backgroundRepeat) {
case BackgroundInfo.BR_NO_REPEAT: {
int imageX;
if (binfo.isBackgroundXPositionAbsolute()) {
imageX = binfo.getBackgroundXPosition();
} else {
imageX = (binfo.getBackgroundXPosition() * (totalWidth - w)) / 100;
}
int imageY;
if (binfo.isBackgroundYPositionAbsolute()) {
imageY = binfo.getBackgroundYPosition();
} else {
imageY = (binfo.getBackgroundYPosition() * (totalHeight - h)) / 100;
}
clientG.drawImage(image, imageX, imageY, w, h, this);
break;
}
case BackgroundInfo.BR_REPEAT_X: {
int imageY;
if (binfo.isBackgroundYPositionAbsolute()) {
imageY = binfo.getBackgroundYPosition();
} else {
imageY = (binfo.getBackgroundYPosition() * (totalHeight - h)) / 100;
}
// Modulate starting x.
int x = (bkgBounds.x / w) * w;
int topX = bkgBounds.x + bkgBounds.width;
for (; x < topX; x += w) {
clientG.drawImage(image, x, imageY, w, h, this);
}
break;
}
case BackgroundInfo.BR_REPEAT_Y: {
int imageX;
if (binfo.isBackgroundXPositionAbsolute()) {
imageX = binfo.getBackgroundXPosition();
} else {
imageX = (binfo.getBackgroundXPosition() * (totalWidth - w)) / 100;
}
// Modulate starting y.
int y = (bkgBounds.y / h) * h;
int topY = bkgBounds.y + bkgBounds.height;
for (; y < topY; y += h) {
clientG.drawImage(image, imageX, y, w, h, this);
}
break;
}
default: {
// Modulate starting x and y.
int baseX = (bkgBounds.x / w) * w;
int baseY = (bkgBounds.y / h) * h;
int topX = bkgBounds.x + bkgBounds.width;
int topY = bkgBounds.y + bkgBounds.height;
// Replacing this:
for (int x = baseX; x < topX; x += w) {
for (int y = baseY; y < topY; y += h) {
clientG.drawImage(image, x, y, w, h, this);
}
}
break;
}
}
}
}
}
} finally {
clientG.dispose();
}
}
/*
* (non-Javadoc)
*
* @see java.awt.image.ImageObserver#imageUpdate(java.awt.Image, int, int,
* int, int, int)
*/
@Override
public boolean imageUpdate(Image img, int infoflags, int x, int y, int w, int h) {
// This is so that a loading image doesn't cause
// too many repaint events.
if (((infoflags & ImageObserver.ALLBITS) != 0) || ((infoflags & ImageObserver.FRAMEBITS) != 0)) {
this.repaint();
}
return true;
}
/** The Constant SCROLL_BAR_THICKNESS. */
protected static final int SCROLL_BAR_THICKNESS = 16;
/**
* Gets insets of content area. It includes margin, borders and scrollbars,
* but not padding.
*
* @param hscroll
* the hscroll
* @param vscroll
* the vscroll
* @return the insets
*/
public Insets getInsets(boolean hscroll, boolean vscroll) {
Insets mi = this.marginInsets;
Insets bi = this.borderInsets;
Insets pi = this.paddingInsets;
int top = 0;
int bottom = 0;
int left = 0;
int right = 0;
if (mi != null) {
top += mi.top;
left += mi.left;
bottom += mi.bottom;
right += mi.right;
}
if (bi != null) {
top += bi.top;
left += bi.left;
bottom += bi.bottom;
right += bi.right;
}
if (pi != null) {
top += pi.top;
left += pi.left;
bottom += pi.bottom;
right += pi.right;
}
if (hscroll) {
bottom += SCROLL_BAR_THICKNESS;
}
if (vscroll) {
right += SCROLL_BAR_THICKNESS;
}
return new Insets(top, left, bottom, right);
}
/**
* Gets the border insets.
*
* @return the border insets
*/
public Insets getBorderInsets() {
return this.borderInsets == null ? RBlockViewport.ZERO_INSETS : this.borderInsets;
}
/**
* Send delayed pairs to parent.
*/
protected final void sendDelayedPairsToParent() {
// Ensures that parent has all the components
// below this renderer node. (Parent expected to have removed them).
Collection<DelayedPair> gc = this.delayedPairs;
if (gc != null) {
RenderableContainer rc = this.container;
Iterator<DelayedPair> i = gc.iterator();
while (i.hasNext()) {
DelayedPair pair = i.next();
if (pair.containingBlock != this) {
rc.addDelayedPair(pair);
}
}
}
}
/*
* (non-Javadoc)
*
* @see
* org.lobobrowser.html.renderer.RenderableContainer#clearDelayedPairs()
*/
@Override
public final void clearDelayedPairs() {
Collection<DelayedPair> gc = this.delayedPairs;
if (gc != null) {
gc.clear();
}
}
/*
* (non-Javadoc)
*
* @see org.lobobrowser.html.renderer.RenderableContainer#getDelayedPairs()
*/
@Override
public final Collection<DelayedPair> getDelayedPairs() {
return this.delayedPairs;
}
/*
* (non-Javadoc)
*
* @see
* org.lobobrowser.html.render.RenderableContainer#add(java.awt.Component)
*/
@Override
public void addDelayedPair(DelayedPair pair) {
// Expected to be called in GUI thread.
// Adds only in local collection.
// Does not remove from parent.
// Sending components to parent is done
// by sendDelayedPairsToParent().
Collection<DelayedPair> gc = this.delayedPairs;
if (gc == null) {
// Sequence is important.
// TODO: But possibly added multiple
// times in table layout?
gc = new LinkedList<DelayedPair>();
this.delayedPairs = gc;
}
gc.add(pair);
}
/*
* (non-Javadoc)
*
* @see
* org.lobobrowser.html.renderer.RenderableContainer#getParentContainer()
*/
@Override
public RenderableContainer getParentContainer() {
return this.container;
}
/*
* (non-Javadoc)
*
* @see
* org.lobobrowser.html.renderer.BoundableRenderable#isContainedByNode()
*/
@Override
public boolean isContainedByNode() {
return true;
}
/*
* (non-Javadoc)
*
* @see org.lobobrowser.html.renderer.RElement#getCollapsibleMarginBottom()
*/
@Override
public int getCollapsibleMarginBottom() {
int cm;
Insets paddingInsets = this.paddingInsets;
if ((paddingInsets != null) && (paddingInsets.bottom > 0)) {
cm = 0;
} else {
Insets borderInsets = this.borderInsets;
if ((borderInsets != null) && (borderInsets.bottom > 0)) {
cm = 0;
} else {
cm = this.getMarginBottom();
}
}
if (this.isMarginBoundary()) {
RenderState rs = this.modelNode.getRenderState();
if (rs != null) {
FontMetrics fm = rs.getFontMetrics();
int fontHeight = fm.getHeight();
if (fontHeight > cm) {
cm = fontHeight;
}
}
}
return cm;
}
/**
* Checks if is margin boundary.
*
* @return true, if is margin boundary
*/
protected boolean isMarginBoundary() {
return ((this.overflowY != RenderState.OVERFLOW_VISIBLE) && (this.overflowX != RenderState.OVERFLOW_NONE))
|| (this.modelNode instanceof HTMLDocumentImpl);
}
/*
* (non-Javadoc)
*
* @see org.lobobrowser.html.renderer.RElement#getCollapsibleMarginTop()
*/
@Override
public int getCollapsibleMarginTop() {
int cm;
Insets paddingInsets = this.paddingInsets;
if ((paddingInsets != null) && (paddingInsets.top > 0)) {
cm = 0;
} else {
Insets borderInsets = this.borderInsets;
if ((borderInsets != null) && (borderInsets.top > 0)) {
cm = 0;
} else {
cm = this.getMarginTop();
}
}
if (this.isMarginBoundary()) {
RenderState rs = this.modelNode.getRenderState();
if (rs != null) {
FontMetrics fm = rs.getFontMetrics();
int fontHeight = fm.getHeight();
if (fontHeight > cm) {
cm = fontHeight;
}
}
}
return cm;
}
/*
* (non-Javadoc)
*
* @see org.lobobrowser.html.renderer.RElement#getMarginBottom()
*/
@Override
public int getMarginBottom() {
Insets marginInsets = this.marginInsets;
return marginInsets == null ? 0 : marginInsets.bottom;
}
/*
* (non-Javadoc)
*
* @see org.lobobrowser.html.renderer.RElement#getMarginLeft()
*/
@Override
public int getMarginLeft() {
Insets marginInsets = this.marginInsets;
return marginInsets == null ? 0 : marginInsets.left;
}
/*
* (non-Javadoc)
*
* @see org.lobobrowser.html.renderer.RElement#getMarginRight()
*/
@Override
public int getMarginRight() {
Insets marginInsets = this.marginInsets;
return marginInsets == null ? 0 : marginInsets.right;
}
/*
* (non-Javadoc)
*
* @see org.lobobrowser.html.renderer.RElement#getMarginTop()
*/
@Override
public int getMarginTop() {
Insets marginInsets = this.marginInsets;
return marginInsets == null ? 0 : marginInsets.top;
}
}