/*
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
*/
/*
* Created on Apr 16, 2005
*/
package org.lobobrowser.html.gui;
import java.awt.Adjustable;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsEnvironment;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.ClipboardOwner;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import org.lobobrowser.html.HtmlRendererContext;
import org.lobobrowser.html.dombl.ModelNode;
import org.lobobrowser.html.dombl.UINode;
import org.lobobrowser.html.domimpl.DOMNodeImpl;
import org.lobobrowser.html.domimpl.HTMLElementImpl;
import org.lobobrowser.html.renderer.BoundableRenderable;
import org.lobobrowser.html.renderer.DelayedPair;
import org.lobobrowser.html.renderer.FrameContext;
import org.lobobrowser.html.renderer.NodeRenderer;
import org.lobobrowser.html.renderer.RBlock;
import org.lobobrowser.html.renderer.RBlockViewport;
import org.lobobrowser.html.renderer.RCollection;
import org.lobobrowser.html.renderer.RElement;
import org.lobobrowser.html.renderer.Renderable;
import org.lobobrowser.html.renderer.RenderableContainer;
import org.lobobrowser.html.renderer.RenderableSpot;
import org.lobobrowser.html.renderstate.RenderState;
import org.lobobrowser.http.UserAgentContext;
import org.lobobrowser.util.Nodes;
import org.lobobrowser.util.Objects;
import org.lobobrowser.util.gui.ColorFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
/**
* A Swing component that renders a HTML block, given by a DOM root or an
* internal element, typically a DIV. This component <i>cannot</i> render
* FRAMESETs. <code>HtmlBlockPanel</code> is used by {@link HtmlPanel} whenever
* the DOM is determined <i>not</i> to be a FRAMESET.
*
* @author J. H. S.
* @see HtmlPanel
* @see FrameSetPanel
*/
public class HtmlBlockPanel extends JComponent implements NodeRenderer, RenderableContainer, ClipboardOwner {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = 1L;
/** The Constant logger. */
private static final Logger logger = LogManager.getLogger(HtmlBlockPanel.class.getName());
/** The Constant loggableInfo. */
private static final boolean loggableInfo = logger.isEnabled(Level.INFO);
/** The frame context. */
protected final FrameContext frameContext;
/** The ucontext. */
protected final UserAgentContext ucontext;
/** The rcontext. */
protected final HtmlRendererContext rcontext;
/** The start selection. */
protected RenderableSpot startSelection;
/** The end selection. */
protected RenderableSpot endSelection;
/** The rblock. */
protected RBlock rblock;
/** The preferred width. */
protected int preferredWidth = -1;
/** The default margin insets. */
protected Insets defaultMarginInsets = null;
/** The mouse press target. */
private BoundableRenderable mousePressTarget;
/** The processing document notification. */
private boolean processingDocumentNotification = false;
/** The default overflow x. */
protected int defaultOverflowX = RenderState.OVERFLOW_AUTO;
/** The default overflow y. */
protected int defaultOverflowY = RenderState.OVERFLOW_SCROLL;
/**
* Instantiates a new html block panel.
*
* @param pcontext
* the pcontext
* @param rcontext
* the rcontext
* @param frameContext
* the frame context
*/
public HtmlBlockPanel(UserAgentContext pcontext, HtmlRendererContext rcontext, FrameContext frameContext) {
this(ColorFactory.TRANSPARENT, true, pcontext, rcontext, frameContext);
}
/**
* Instantiates a new html block panel.
*
* @param background
* the background
* @param opaque
* the opaque
* @param pcontext
* the pcontext
* @param rcontext
* the rcontext
* @param frameContext
* the frame context
*/
public HtmlBlockPanel(Color background, boolean opaque, UserAgentContext pcontext, HtmlRendererContext rcontext,
FrameContext frameContext) {
this.setLayout(null);
this.setAutoscrolls(true);
this.frameContext = frameContext;
this.ucontext = pcontext;
this.rcontext = rcontext;
this.setOpaque(opaque);
this.setBackground(background);
ActionListener actionListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String command = e.getActionCommand();
if ("copy".equals(command)) {
copy();
}
}
};
if (!GraphicsEnvironment.isHeadless()) {
this.registerKeyboardAction(actionListener, "copy", KeyStroke.getKeyStroke(KeyEvent.VK_COPY, 0),
JComponent.WHEN_FOCUSED);
this.registerKeyboardAction(actionListener, "copy",
KeyStroke.getKeyStroke(KeyEvent.VK_C, Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()),
JComponent.WHEN_FOCUSED);
}
this.addMouseListener(new MouseListener() {
@Override
public void mouseClicked(MouseEvent e) {
onMouseClick(e);
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
onMouseExited(e);
}
@Override
public void mousePressed(MouseEvent e) {
onMousePressed(e);
}
@Override
public void mouseReleased(MouseEvent e) {
onMouseReleased(e);
}
});
this.addMouseMotionListener(new MouseMotionListener() {
@Override
public void mouseDragged(MouseEvent e) {
onMouseDragged(e);
}
@Override
public void mouseMoved(MouseEvent arg0) {
onMouseMoved(arg0);
}
});
this.addMouseWheelListener(new MouseWheelListener() {
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
onMouseWheelMoved(e);
}
});
this.addKeyListener(new KeyListener() {
@Override
public void keyTyped(KeyEvent evt) {
}
@Override
public void keyReleased(KeyEvent evt) {
onKeyUp(evt);
}
@Override
public void keyPressed(KeyEvent evt) {
onKeyPressed(evt);
}
});
}
/**
* Scrolls the body area to the given location.
* <p>
* This method should be called from the GUI thread.
*
* @param bounds
* The bounds in the scrollable block area that should become
* visible.
* @param xIfNeeded
* If this parameter is true, scrolling will only occur if the
* requested bounds are not currently visible horizontally.
* @param yIfNeeded
* If this parameter is true, scrolling will only occur if the
* requested bounds are not currently visible vertically.
*/
public void scrollTo(Rectangle bounds, boolean xIfNeeded, boolean yIfNeeded) {
RBlock block = this.rblock;
if (block != null) {
block.scrollTo(bounds, xIfNeeded, yIfNeeded);
}
}
/**
* Scroll by.
*
* @param xOffset
* the x offset
* @param yOffset
* the y offset
*/
public void scrollBy(int xOffset, int yOffset) {
RBlock block = this.rblock;
if (block != null) {
if (xOffset != 0) {
block.scrollBy(Adjustable.HORIZONTAL, xOffset);
}
if (yOffset != 0) {
block.scrollBy(Adjustable.VERTICAL, yOffset);
}
}
}
/**
* Scrolls the body area to the node given, if it is part of the current
* document.
* <p>
* This method should be called from the GUI thread.
*
* @param node
* A DOM node.
*/
public void scrollTo(Node node) {
Rectangle bounds = this.getNodeBoundsNoMargins(node, true);
if (bounds == null) {
return;
}
this.scrollTo(bounds, true, false);
}
/**
* Gets the rectangular bounds of the given node.
* <p>
* This method should be called from the GUI thread.
*
* @param node
* A node in the current document.
* @param relativeToScrollable
* Whether the bounds should be relative to the scrollable body
* area. Otherwise, they are relative to the root block (which is
* the essentially the same as being relative to this
* <code>HtmlBlockPanel</code> minus Swing borders).
* @return the node bounds
*/
public Rectangle getNodeBounds(Node node, boolean relativeToScrollable) {
RBlock block = this.rblock;
if (block == null) {
return null;
}
// Find UINode first
Node currentNode = node;
UINode uiNode = null;
while (currentNode != null) {
if (currentNode instanceof HTMLElementImpl) {
HTMLElementImpl element = (HTMLElementImpl) currentNode;
uiNode = element.getUINode();
if (uiNode != null) {
break;
}
}
currentNode = currentNode.getParentNode();
}
if (uiNode == null) {
return null;
}
RCollection relativeTo = relativeToScrollable ? (RCollection) block.getRBlockViewport() : (RCollection) block;
if (node == currentNode) {
BoundableRenderable br = (BoundableRenderable) uiNode;
Point guiPoint = br.getOriginRelativeTo(relativeTo);
Dimension size = br.getSize();
return new Rectangle(guiPoint, size);
} else {
return this.scanNodeBounds((RCollection) uiNode, node, relativeTo);
}
}
/**
* Gets the rectangular bounds of the given node with margins cut off.
* <p>
* Internally calls getNodeBounds and cuts off margins.
*
* @param node
* A node in the current document.
* @param relativeToScrollable
* see getNodeBounds.
* @return the node bounds no margins
*/
public Rectangle getNodeBoundsNoMargins(Node node, boolean relativeToScrollable) {
/* Do the same as getNodeBounds first */
RBlock block = this.rblock;
if (block == null) {
return null;
}
// Find UINode first
Node currentNode = node;
UINode uiNode = null;
while (currentNode != null) {
if (currentNode instanceof HTMLElementImpl) {
HTMLElementImpl element = (HTMLElementImpl) currentNode;
uiNode = element.getUINode();
if (uiNode != null) {
break;
}
}
currentNode = currentNode.getParentNode();
}
if (uiNode == null) {
return null;
}
Rectangle bounds;
RCollection relativeTo = relativeToScrollable ? (RCollection) block.getRBlockViewport() : (RCollection) block;
if (node == currentNode) {
BoundableRenderable br = (BoundableRenderable) uiNode;
Point guiPoint = br.getOriginRelativeTo(relativeTo);
Dimension size = br.getSize();
bounds = new Rectangle(guiPoint, size);
} else {
bounds = this.scanNodeBounds((RCollection) uiNode, node, relativeTo);
}
/* cut off margins */
if (uiNode instanceof RElement) {
RElement el = (RElement) uiNode;
int top = el.getMarginTop();
int left = el.getMarginLeft();
bounds.x += left;
bounds.y += top;
bounds.width -= left + el.getMarginRight();
bounds.height -= top + el.getMarginBottom();
}
return bounds;
}
/**
* Gets an aggregate of the bounds of renderer leaf nodes.
*
* @param root
* the root
* @param node
* the node
* @param relativeTo
* the relative to
* @return the rectangle
*/
private Rectangle scanNodeBounds(RCollection root, Node node, RCollection relativeTo) {
Iterator i = root.getRenderables();
Rectangle resultBounds = null;
BoundableRenderable prevBoundable = null;
if (i != null) {
while (i.hasNext()) {
Renderable r = (Renderable) i.next();
Rectangle subBounds = null;
if (r instanceof RCollection) {
RCollection rc = (RCollection) r;
prevBoundable = rc;
subBounds = this.scanNodeBounds(rc, node, relativeTo);
} else if (r instanceof BoundableRenderable) {
BoundableRenderable br = (BoundableRenderable) r;
prevBoundable = br;
if (Nodes.isSameOrAncestorOf(node, (Node) r.getModelNode())) {
Point origin = br.getOriginRelativeTo(relativeTo);
Dimension size = br.getSize();
subBounds = new Rectangle(origin, size);
}
} else {
// This would have to be a RStyleChanger. We rely on these
// when the target node has blank content.
if (Nodes.isSameOrAncestorOf(node, (Node) r.getModelNode())) {
int xInRoot = prevBoundable == null ? 0 : prevBoundable.getX() + prevBoundable.getWidth();
Point rootOrigin = root.getOriginRelativeTo(relativeTo);
subBounds = new Rectangle(rootOrigin.x + xInRoot, rootOrigin.y, 0, root.getHeight());
}
}
if (subBounds != null) {
if (resultBounds == null) {
resultBounds = subBounds;
} else {
resultBounds = subBounds.union(resultBounds);
}
}
}
}
return resultBounds;
}
/**
* Gets the root renderable.
*
* @return the root renderable
*/
public BoundableRenderable getRootRenderable() {
return this.rblock;
}
/**
* Sets the preferred width.
*
* @param width
* the new preferred width
*/
public void setPreferredWidth(int width) {
this.preferredWidth = width;
}
/**
* If the preferred size has been set with
* {@link #setPreferredSize(Dimension)}, then that size is returned.
* Otherwise a preferred size is calculated by rendering the HTML DOM,
* provided one is available and a preferred width other than
* <code>-1</code> has been set with {@link #setPreferredWidth(int)}. An
* arbitrary preferred size is returned in other scenarios.
*
* @return the preferred size
*/
@Override
public Dimension getPreferredSize() {
// Expected to be invoked in the GUI thread.
if (this.isPreferredSizeSet()) {
return super.getPreferredSize();
}
final int pw = this.preferredWidth;
if (pw != -1) {
final RBlock block = this.rblock;
if (block != null) {
// Layout should always be done in the GUI thread.
if (SwingUtilities.isEventDispatchThread()) {
block.layout(pw, 0, false, false, RenderState.OVERFLOW_VISIBLE, RenderState.OVERFLOW_VISIBLE, true);
} else {
try {
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
block.layout(pw, 0, false, false, RenderState.OVERFLOW_VISIBLE,
RenderState.OVERFLOW_VISIBLE, true);
}
});
} catch (Exception err) {
logger.log(Level.ERROR, "Unable to do preferred size layout.", err);
}
}
// Adjust for permanent vertical scrollbar.
int newPw = Math.max(block.width + block.getVScrollBarWidth(), pw);
return new Dimension(newPw, block.height);
}
}
return new Dimension(600, 400);
}
@Override
protected void finalize() throws Throwable {
super.finalize();
}
/**
* Copy.
*
* @return true, if successful
*/
public boolean copy() {
String selection = HtmlBlockPanel.this.getSelectionText();
if (selection != null) {
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
clipboard.setContents(new StringSelection(selection), HtmlBlockPanel.this);
return true;
} else {
return false;
}
}
/**
* Gets the first line height.
*
* @return the first line height
*/
public int getFirstLineHeight() {
RBlock block = this.rblock;
return block == null ? 0 : block.getFirstLineHeight();
}
/**
* Sets the selection end.
*
* @param rpoint
* the new selection end
*/
public void setSelectionEnd(RenderableSpot rpoint) {
this.endSelection = rpoint;
}
/**
* Sets the selection start.
*
* @param rpoint
* the new selection start
*/
public void setSelectionStart(RenderableSpot rpoint) {
this.startSelection = rpoint;
}
/**
* Checks if is selection available.
*
* @return true, if is selection available
*/
public boolean isSelectionAvailable() {
RenderableSpot start = this.startSelection;
RenderableSpot end = this.endSelection;
return (start != null) && (end != null) && !start.equals(end);
}
/**
* Gets the selection node.
*
* @return the selection node
*/
public Node getSelectionNode() {
RenderableSpot start = this.startSelection;
RenderableSpot end = this.endSelection;
if ((start != null) && (end != null)) {
return Nodes.getCommonAncestor((Node) start.getRenderable().getModelNode(),
(Node) end.getRenderable().getModelNode());
} else {
return null;
}
}
/**
* Sets the root node to render. This method should be invoked in the GUI
* dispatch thread.
*
* @param node
* the new root node
*/
@Override
public void setRootNode(DOMNodeImpl node) {
if (node != null) {
RBlock block = new RBlock(node, 0, this.ucontext, this.rcontext, this.frameContext, this);
block.setDefaultMarginInsets(this.defaultMarginInsets);
// block.setDefaultPaddingInsets(this.defaultPaddingInsets);
block.setDefaultOverflowX(this.defaultOverflowX);
block.setDefaultOverflowY(this.defaultOverflowY);
node.setUINode(block);
this.rblock = block;
} else {
this.rblock = null;
}
this.invalidate();
this.validateAll();
this.repaint();
}
/**
* Validate all.
*/
protected void validateAll() {
Component toValidate = this;
for (;;) {
Container parent = toValidate.getParent();
if ((parent == null) || parent.isValid()) {
break;
}
toValidate = parent;
}
toValidate.validate();
}
/**
* Revalidate panel.
*/
protected void revalidatePanel() {
// Called in the GUI thread.
this.invalidate();
this.validate();
this.repaint();
}
/**
* Gets the root node.
*
* @return the root node
*/
public DOMNodeImpl getRootNode() {
RBlock block = this.rblock;
return block == null ? null : (DOMNodeImpl) block.getModelNode();
}
/**
* On mouse click.
*
* @param event
* the event
*/
private void onMouseClick(MouseEvent event) {
// Rely on AWT mouse-click only for double-clicks
RBlock block = this.rblock;
if (block != null) {
int button = event.getButton();
int clickCount = event.getClickCount();
if ((button == MouseEvent.BUTTON1) && (clickCount == 1)) {
Point point = event.getPoint();
block.onMouseClick(event, point.x, point.y);
}
if ((button == MouseEvent.BUTTON1) && (clickCount == 2)) {
Point point = event.getPoint();
block.onDoubleClick(event, point.x, point.y);
} else if ((button == MouseEvent.BUTTON3) && (clickCount == 1)) {
block.onRightClick(event, event.getX(), event.getY());
}
}
}
/**
* On mouse pressed.
*
* @param event
* the event
*/
private void onMousePressed(MouseEvent event) {
this.requestFocus();
RBlock block = this.rblock;
if (block != null) {
Point point = event.getPoint();
this.mousePressTarget = block;
int rx = point.x;
int ry = point.y;
block.onMousePressed(event, point.x, point.y);
RenderableSpot rp = block.getLowestRenderableSpot(rx, ry);
if (rp != null) {
this.frameContext.resetSelection(rp);
} else {
this.frameContext.resetSelection(null);
}
}
}
/**
* On mouse released.
*
* @param event
* the event
*/
private void onMouseReleased(MouseEvent event) {
RBlock block = this.rblock;
if (block != null) {
Point point = event.getPoint();
int rx = point.x;
int ry = point.y;
if (event.getButton() == MouseEvent.BUTTON1) {
// TODO: This will be raised twice on a double-click.
block.onMouseClick(event, rx, ry);
}
block.onMouseReleased(event, rx, ry);
BoundableRenderable oldTarget = this.mousePressTarget;
if (oldTarget != null) {
this.mousePressTarget = null;
if (oldTarget != block) {
oldTarget.onMouseDisarmed(event);
}
}
} else {
this.mousePressTarget = null;
}
}
/**
* On mouse exited.
*
* @param event
* the event
*/
private void onMouseExited(MouseEvent event) {
BoundableRenderable oldTarget = this.mousePressTarget;
if (oldTarget != null) {
this.mousePressTarget = null;
oldTarget.onMouseDisarmed(event);
}
}
/**
* On mouse wheel moved.
*
* @param mwe
* the mwe
*/
private void onMouseWheelMoved(MouseWheelEvent mwe) {
RBlockViewport viewport = this.rblock.getRBlockViewport();
RenderableSpot spot = viewport.getLowestRenderableSpot(mwe.getX(), mwe.getY());
RBlock block = this.rblock;
for (BoundableRenderable r = spot.getRenderable(); r != null; r = r.getParent()) {
if (r instanceof RBlock) {
block = (RBlock) r;
RBlockViewport blockViewport = block.getRBlockViewport();
if (mwe.getWheelRotation() < 0) {
if (blockViewport.getY() < 0) {
break;
}
} else {
if ((blockViewport.getY() + blockViewport.getHeight()) > block.getHeight()) {
break;
}
}
}
}
if (block != null) {
switch (mwe.getScrollType()) {
case MouseWheelEvent.WHEEL_UNIT_SCROLL:
int units = mwe.getWheelRotation() * mwe.getScrollAmount();
block.scrollByUnits(Adjustable.VERTICAL, units);
break;
}
}
}
/**
* On mouse dragged.
*
* @param event
* the event
*/
private void onMouseDragged(MouseEvent event) {
RBlock block = this.rblock;
if (block != null) {
Point point = event.getPoint();
RenderableSpot rp = block.getLowestRenderableSpot(point.x, point.y);
if (rp != null) {
this.frameContext.expandSelection(rp);
}
block.ensureVisible(point);
}
}
/**
* On mouse moved.
*
* @param event
* the event
*/
private void onMouseMoved(MouseEvent event) {
RBlock block = this.rblock;
if (block != null) {
Point point = event.getPoint();
block.onMouseMoved(event, point.x, point.y, false, null);
}
}
/**
* On key press.
*
* @param event
* the event
*/
private void onKeyPressed(KeyEvent evt) {
this.requestFocus();
RBlock block = this.rblock;
if (block != null) {
block.onKeyPressed(evt);
}
}
/**
* On key up.
*
* @param event
* the event
*/
private void onKeyUp(KeyEvent evt) {
this.requestFocus();
RBlock block = this.rblock;
if (block != null) {
block.onKeyUp(evt);
}
}
@Override
public void paintComponent(Graphics g) {
if (this.isOpaque()) {
// Background not painted by default in JComponent.
Rectangle clipBounds = g.getClipBounds();
g.setColor(getBackground());
g.fillRect(clipBounds.x, clipBounds.y, clipBounds.width, clipBounds.height);
g.setColor(getForeground());
}
if (g instanceof Graphics2D) {
Graphics2D g2 = (Graphics2D) g;
try {
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
} catch (NoSuchFieldError e) {
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
}
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
}
RBlock block = this.rblock;
if (block != null) {
boolean liflag = loggableInfo;
long time1 = liflag ? System.currentTimeMillis() : 0;
block.paint(g);
if (liflag) {
long time2 = System.currentTimeMillis();
Node rootNode = this.getRootNode();
String uri = rootNode instanceof Document ? ((Document) rootNode).getDocumentURI() : "";
logger.info("paintComponent(): URI=[" + uri + "]. Block paint elapsed: " + (time2 - time1) + " ms.");
}
// Paint FrameContext selection
RenderableSpot start = this.startSelection;
RenderableSpot end = this.endSelection;
if ((start != null) && (end != null) && !start.equals(end)) {
block.paintSelection(g, false, start, end);
}
}
}
@Override
public void doLayout() {
try {
Dimension size = this.getSize();
boolean liflag = loggableInfo;
long time1 = 0;
if (liflag) {
time1 = System.currentTimeMillis();
}
this.clearComponents();
RBlock block = this.rblock;
if (block != null) {
ModelNode rootNode = block.getModelNode();
block.layout(size.width, size.height, true, true, null, false);
// Only set origin
block.setOrigin(0, 0);
block.updateWidgetBounds(0, 0);
this.updateGUIComponents();
if (liflag) {
long time2 = System.currentTimeMillis();
String uri = rootNode instanceof Document ? ((Document) rootNode).getDocumentURI() : "";
logger.info("doLayout(): URI=[" + uri + "]. Block layout elapsed: " + (time2 - time1)
+ " ms. Component count: " + this.getComponentCount() + ".");
}
} else {
if (this.getComponentCount() > 0) {
this.removeAll();
}
}
} catch (Throwable thrown) {
logger.log(Level.ERROR, "Unexpected error in layout engine. Document is " + this.getRootNode(), thrown);
}
}
/**
* Implementation of UINode.repaint().
*
* @param modelNode
* the model node
*/
public void repaint(ModelNode modelNode) {
// this.rblock.invalidateRenderStyle();
this.repaint();
}
/**
* Gets the selection text.
*
* @return the selection text
*/
public String getSelectionText() {
RenderableSpot start = this.startSelection;
RenderableSpot end = this.endSelection;
if ((start != null) && (end != null)) {
StringBuffer buffer = new StringBuffer();
this.rblock.extractSelectionText(buffer, false, start, end);
return buffer.toString();
} else {
return null;
}
}
/**
* Checks for selection.
*
* @return true, if successful
*/
public boolean hasSelection() {
RenderableSpot start = this.startSelection;
RenderableSpot end = this.endSelection;
if ((start != null) && (end != null) && !start.equals(end)) {
return true;
} else {
return false;
}
}
@Override
protected void paintChildren(Graphics g) {
// Overridding with NOP. For various reasons,
// the regular mechanism for painting children
// needs to be handled by Cobra.
}
@Override
public Color getPaintedBackgroundColor() {
return this.isOpaque() ? this.getBackground() : null;
}
@Override
public void lostOwnership(Clipboard arg0, Transferable arg1) {
}
@Override
public void relayout() {
// Expected to be called in the GUI thread.
// Renderable branch should be invalidated at this
// point, but this GUI component not necessarily.
this.revalidatePanel();
}
@Override
public void invalidateLayoutUpTree() {
// Called when renderable branch is invalidated.
// We shouldn't do anything here. Changes in renderer
// tree do not have any bearing on validity of GUI
// component.
}
@Override
public void updateAllWidgetBounds() {
this.rblock.updateWidgetBounds(0, 0);
}
@Override
public Point getGUIPoint(int clientX, int clientY) {
// This is the GUI!
return new Point(clientX, clientY);
}
@Override
public void focus() {
this.grabFocus();
}
/**
* Process document notifications.
*
* @param notifications
* the notifications
*/
void processDocumentNotifications(DocumentNotification[] notifications) {
// Called in the GUI thread.
if (this.processingDocumentNotification) {
// This should not be possible. Even if
// Javascript modifies the DOM during
// parsing, this should be executed in
// the GUI thread, not the parser thread.
throw new IllegalStateException("Recursive");
}
this.processingDocumentNotification = true;
try {
// Note: It may be assumed that usually only generic
// notifications come in batches. Other types
// of noitifications probably come one by one.
boolean topLayout = false;
ArrayList<RElement> repainters = null;
int length = notifications.length;
for (int i = 0; i < length; i++) {
DocumentNotification dn = notifications[i];
int type = dn.type;
switch (type) {
case DocumentNotification.GENERIC:
case DocumentNotification.SIZE: {
DOMNodeImpl node = dn.node;
if (node == null) {
// This is all-invalidate (new style sheet)
if (loggableInfo) {
logger.info("processDocumentNotifications(): Calling invalidateLayoutDeep().");
}
this.rblock.invalidateLayoutDeep();
// this.rblock.invalidateRenderStyle();
} else {
UINode uiNode = node.findUINode();
if (uiNode != null) {
RElement relement = (RElement) uiNode;
relement.invalidateLayoutUpTree();
// if(type == DocumentNotification.GENERIC) {
// relement.invalidateRenderStyle();
// }
} else {
if (loggableInfo) {
logger.info("processDocumentNotifications(): Unable to find UINode for " + node);
}
}
}
topLayout = true;
break;
}
case DocumentNotification.POSITION: {
// TODO: Could be more efficient.
DOMNodeImpl node = dn.node;
DOMNodeImpl parent = (DOMNodeImpl) node.getParentNode();
if (parent != null) {
UINode uiNode = parent.findUINode();
if (uiNode != null) {
RElement relement = (RElement) uiNode;
relement.invalidateLayoutUpTree();
}
}
topLayout = true;
break;
}
case DocumentNotification.LOOK: {
DOMNodeImpl node = dn.node;
UINode uiNode = node.findUINode();
if (uiNode != null) {
if (repainters == null) {
repainters = new ArrayList<RElement>(1);
}
RElement relement = (RElement) uiNode;
// relement.invalidateRenderStyle();
repainters.add(relement);
}
break;
}
default:
break;
}
}
if (topLayout) {
this.revalidatePanel();
} else {
if (repainters != null) {
Iterator<RElement> i = repainters.iterator();
while (i.hasNext()) {
RElement element = i.next();
element.repaint();
}
}
}
} finally {
this.processingDocumentNotification = false;
}
}
@Override
public void addDelayedPair(DelayedPair pair) {
// NOP
}
@Override
public RenderableContainer getParentContainer() {
return null;
}
@Override
public Collection getDelayedPairs() {
return null;
}
@Override
public void clearDelayedPairs() {
}
/** The components. */
private Set<Component> components;
/**
* Clear components.
*/
private void clearComponents() {
Set<Component> c = this.components;
if (c != null) {
c.clear();
}
}
@Override
public Component addComponent(Component component) {
Set<Component> c = this.components;
if (c == null) {
c = new HashSet<Component>();
this.components = c;
}
if (c.add(component)) {
return component;
} else {
return null;
}
}
/**
* Update gui components.
*/
private void updateGUIComponents() {
// We use this method, instead of removing all components and
// adding them back, because removal of components can cause
// them to lose focus.
Set<Component> c = this.components;
if (c == null) {
if (this.getComponentCount() != 0) {
this.removeAll();
}
} else {
// Remove children not in the set.
Set<Component> workingSet = new HashSet<Component>();
workingSet.addAll(c);
int count = this.getComponentCount();
for (int i = 0; i < count;) {
Component component = this.getComponent(i);
if (!c.contains(component)) {
this.remove(i);
count = this.getComponentCount();
} else {
i++;
workingSet.remove(component);
}
}
// Add components in set that were not previously children.
Iterator<Component> wsi = workingSet.iterator();
while (wsi.hasNext()) {
Component component = wsi.next();
this.add(component);
}
}
}
/**
* Gets the default margin insets.
*
* @return the default margin insets
*/
public Insets getDefaultMarginInsets() {
return defaultMarginInsets;
}
/**
* Sets the default margin insets.
*
* @param defaultMarginInsets
* the new default margin insets
*/
public void setDefaultMarginInsets(Insets defaultMarginInsets) {
if (!Objects.equals(this.defaultMarginInsets, defaultMarginInsets)) {
this.defaultMarginInsets = defaultMarginInsets;
RBlock block = this.rblock;
if (block != null) {
block.setDefaultMarginInsets(defaultMarginInsets);
block.relayoutIfValid();
}
}
}
/**
* Gets the default overflow x.
*
* @return the default overflow x
*/
public int getDefaultOverflowX() {
return defaultOverflowX;
}
/**
* Sets the default overflow x.
*
* @param defaultOverflowX
* the new default overflow x
*/
public void setDefaultOverflowX(int defaultOverflowX) {
if (defaultOverflowX != this.defaultOverflowX) {
this.defaultOverflowX = defaultOverflowX;
RBlock block = this.rblock;
if (block != null) {
block.setDefaultOverflowX(defaultOverflowX);
block.relayoutIfValid();
}
}
}
/**
* Gets the default overflow y.
*
* @return the default overflow y
*/
public int getDefaultOverflowY() {
return defaultOverflowY;
}
/**
* Sets the default overflow y.
*
* @param defaultOverflowY
* the new default overflow y
*/
public void setDefaultOverflowY(int defaultOverflowY) {
if (this.defaultOverflowY != defaultOverflowY) {
this.defaultOverflowY = defaultOverflowY;
RBlock block = this.rblock;
if (block != null) {
block.setDefaultOverflowY(defaultOverflowY);
block.relayoutIfValid();
}
}
}
@Override
public Insets getInsets(final boolean hscroll, final boolean vscroll) {
throw new UnsupportedOperationException(
"Method added while implementing absolute positioned elements inside relative elements. But not implemented yet.");
}
}