org.cobraparser.html.gui.HtmlBlockPanel Maven / Gradle / Ivy
Show all versions of Cobra Show documentation
/*
GNU LESSER GENERAL PUBLIC LICENSE
Copyright (C) 2006 The Lobo Project
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Contact info: [email protected]
*/
/*
* Created on Apr 16, 2005
*/
package org.cobraparser.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.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.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JComponent;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import org.eclipse.jdt.annotation.Nullable;
import org.cobraparser.html.HtmlRendererContext;
import org.cobraparser.html.domimpl.HTMLDocumentImpl;
import org.cobraparser.html.domimpl.HTMLElementImpl;
import org.cobraparser.html.domimpl.ModelNode;
import org.cobraparser.html.domimpl.NodeImpl;
import org.cobraparser.html.domimpl.UINode;
import org.cobraparser.html.renderer.BoundableRenderable;
import org.cobraparser.html.renderer.DelayedPair;
import org.cobraparser.html.renderer.FrameContext;
import org.cobraparser.html.renderer.NodeRenderer;
import org.cobraparser.html.renderer.PositionedRenderable;
import org.cobraparser.html.renderer.RBlock;
import org.cobraparser.html.renderer.RCollection;
import org.cobraparser.html.renderer.RElement;
import org.cobraparser.html.renderer.Renderable;
import org.cobraparser.html.renderer.RenderableContainer;
import org.cobraparser.html.renderer.RenderableSpot;
import org.cobraparser.html.renderer.TranslatedRenderable;
import org.cobraparser.html.style.RenderState;
import org.cobraparser.ua.UserAgentContext;
import org.cobraparser.util.Nodes;
import org.cobraparser.util.gui.ColorFactory;
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 cannot render
* FRAMESETs. HtmlBlockPanel
is used by {@link HtmlPanel} whenever
* the DOM is determined not to be a FRAMESET.
*
* @see HtmlPanel
* @see FrameSetPanel
* @author J. H. S.
*/
public class HtmlBlockPanel extends JComponent implements NodeRenderer, RenderableContainer, ClipboardOwner {
private static final long serialVersionUID = 7851587340938903001L;
private static final Logger logger = Logger.getLogger(HtmlBlockPanel.class.getName());
private static final boolean loggableInfo = logger.isLoggable(Level.INFO);
protected final FrameContext frameContext;
protected final UserAgentContext ucontext;
protected final HtmlRendererContext rcontext;
protected RenderableSpot startSelection;
protected RenderableSpot endSelection;
protected RBlock rblock;
protected int preferredWidth = -1;
protected int defaultOverflowX = RenderState.OVERFLOW_AUTO;
protected int defaultOverflowY = RenderState.OVERFLOW_SCROLL;
private volatile boolean scrollCompleted = false;
public HtmlBlockPanel(final UserAgentContext pcontext, final HtmlRendererContext rcontext, final FrameContext frameContext) {
this(ColorFactory.TRANSPARENT, false, pcontext, rcontext, frameContext);
}
public HtmlBlockPanel(final Color background, final boolean opaque, final UserAgentContext pcontext, final HtmlRendererContext rcontext,
final FrameContext frameContext) {
this.setLayout(null);
this.setAutoscrolls(true);
this.frameContext = frameContext;
this.ucontext = pcontext;
this.rcontext = rcontext;
this.setOpaque(opaque);
this.setBackground(background);
final ActionListener actionListener = new ActionListener() {
public void actionPerformed(final ActionEvent e) {
final 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() {
public void mouseClicked(final MouseEvent e) {
onMouseClick(e);
}
public void mouseEntered(final MouseEvent e) {
}
public void mouseExited(final MouseEvent e) {
onMouseExited(e);
}
public void mousePressed(final MouseEvent e) {
onMousePressed(e);
}
public void mouseReleased(final MouseEvent e) {
onMouseReleased(e);
}
});
this.addMouseMotionListener(new MouseMotionListener() {
/*
* (non-Javadoc)
*
* @see
* java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent
* )
*/
public void mouseDragged(final MouseEvent e) {
onMouseDragged(e);
}
/*
* (non-Javadoc)
*
* @see
* java.awt.event.MouseMotionListener#mouseMoved(java.awt.event.MouseEvent
* )
*/
public void mouseMoved(final MouseEvent arg0) {
onMouseMoved(arg0);
}
});
this.addMouseWheelListener(new MouseWheelListener() {
public void mouseWheelMoved(final MouseWheelEvent e) {
onMouseWheelMoved(e);
}
});
}
/**
* Scrolls the body area to the given location.
*
* 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(final Rectangle bounds, final boolean xIfNeeded, final boolean yIfNeeded) {
final @Nullable HTMLDocumentImpl doc = (HTMLDocumentImpl) getRootNode();
if (doc != null) {
RBlock bodyBlock = (RBlock)((HTMLElementImpl) doc.getBody()).getUINode();
bodyBlock.scrollTo(bounds, xIfNeeded, yIfNeeded);
}
}
public void scrollBy(final int xOffset, final int yOffset) {
final 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.
*
* This method should be called from the GUI thread.
*
* @param node
* A DOM node.
*/
public void scrollTo(final Node node) {
final Rectangle bounds = this.getNodeBounds(node, true);
if (bounds == null) {
return;
}
this.scrollTo(bounds, true, false);
}
/**
* Gets the rectangular bounds of the given node.
*
* 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
* HtmlBlockPanel
minus Swing borders).
*/
public Rectangle getNodeBounds(final Node node, final boolean relativeToScrollable) {
final 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) {
final HTMLElementImpl element = (HTMLElementImpl) currentNode;
uiNode = element.getUINode();
if (uiNode != null) {
break;
}
}
currentNode = currentNode.getParentNode();
}
if (uiNode == null) {
return null;
}
final RCollection relativeTo = relativeToScrollable ? (RCollection) block.getRBlockViewport() : (RCollection) block;
if (node == currentNode) {
final BoundableRenderable br = (BoundableRenderable) uiNode;
final Point guiPoint = br.getOriginRelativeTo(relativeTo);
final Dimension size = br.getSize();
return new Rectangle(guiPoint, size);
} else {
return this.scanNodeBounds((RCollection) uiNode, node, relativeTo);
}
}
/**
* Gets an aggregate of the bounds of renderer leaf nodes.
*/
private Rectangle scanNodeBounds(final RCollection root, final Node node, final RCollection relativeTo) {
final Iterator i = root.getRenderables(false);
Rectangle resultBounds = null;
BoundableRenderable prevBoundable = null;
if (i != null) {
while (i.hasNext()) {
final Renderable rn = i.next();
final Renderable r = rn instanceof PositionedRenderable ? (((PositionedRenderable)rn).renderable) : rn;
Rectangle subBounds = null;
if (r instanceof RCollection) {
final RCollection rc = (RCollection) r;
prevBoundable = rc;
subBounds = this.scanNodeBounds(rc, node, relativeTo);
} else if (r instanceof BoundableRenderable) {
final BoundableRenderable br = (BoundableRenderable) r;
prevBoundable = br;
if (Nodes.isSameOrAncestorOf(node, (Node) r.getModelNode())) {
final Point origin = br.getOriginRelativeTo(relativeTo);
final 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())) {
final int xInRoot = prevBoundable == null ? 0 : prevBoundable.getVisualX() + prevBoundable.getVisualWidth();
final Point rootOrigin = root.getOriginRelativeTo(relativeTo);
subBounds = new Rectangle(rootOrigin.x + xInRoot, rootOrigin.y, 0, root.getVisualHeight());
}
}
if (subBounds != null) {
if (resultBounds == null) {
resultBounds = subBounds;
} else {
resultBounds = subBounds.union(resultBounds);
}
}
}
}
return resultBounds;
}
public BoundableRenderable getRootRenderable() {
return this.rblock;
}
/**
* Allows {@link #getPreferredSize()} to render the HTML block in order to
* determine the preferred size of this component. Note that
* getPreferredSize() is a potentially time-consuming
* operation if the preferred width is set.
*
* @param width
* The preferred blocked width. Use -1
to unset.
*/
public void setPreferredWidth(final 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 -1
has been set
* with {@link #setPreferredWidth(int)}. An arbitrary preferred size is
* returned in other scenarios.
*/
@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() {
public void run() {
block.layout(pw, 0, false, false, RenderState.OVERFLOW_VISIBLE, RenderState.OVERFLOW_VISIBLE, true);
}
});
} catch (final Exception err) {
logger.log(Level.SEVERE, "Unable to do preferred size layout.", err);
}
}
// Adjust for permanent vertical scrollbar.
final int newPw = Math.max(block.width + block.getVScrollBarWidth(), pw);
return new Dimension(newPw, block.height);
}
}
return new Dimension(600, 400);
}
@Override
public void finalize() throws Throwable {
super.finalize();
}
public boolean copy() {
final String selection = HtmlBlockPanel.this.getSelectionText();
if (selection != null) {
final Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
clipboard.setContents(new StringSelection(selection), HtmlBlockPanel.this);
return true;
} else {
return false;
}
}
public int getFirstLineHeight() {
final RBlock block = this.rblock;
return block == null ? 0 : block.getFirstLineHeight();
}
public void setSelectionEnd(final RenderableSpot rpoint) {
this.endSelection = rpoint;
}
public void setSelectionStart(final RenderableSpot rpoint) {
this.startSelection = rpoint;
}
public boolean isSelectionAvailable() {
final RenderableSpot start = this.startSelection;
final RenderableSpot end = this.endSelection;
return (start != null) && (end != null) && !start.equals(end);
}
public Node getSelectionNode() {
final RenderableSpot start = this.startSelection;
final RenderableSpot end = this.endSelection;
if ((start != null) && (end != null)) {
return Nodes.getCommonAncestor((Node) start.renderable.getModelNode(), (Node) end.renderable.getModelNode());
} else {
return null;
}
}
/**
* Sets the root node to render. This method should be invoked in the GUI
* dispatch thread.
*/
public void setRootNode(final NodeImpl node) {
scrollCompleted = false;
layoutCompleted = new CompletableFuture<>();
if (node != null) {
final RBlock block = new RBlock(node, 0, this.ucontext, this.rcontext, this.frameContext, this);
block.setDefaultOverflowX(this.defaultOverflowX);
block.setDefaultOverflowY(this.defaultOverflowY);
node.setUINode(block);
this.rblock = block;
} else {
this.rblock = null;
}
this.invalidate();
this.validateAll();
this.repaint();
}
protected void validateAll() {
Component toValidate = this;
for (;;) {
final Container parent = toValidate.getParent();
if ((parent == null) || parent.isValid()) {
break;
}
toValidate = parent;
}
toValidate.validate();
}
protected void revalidatePanel() {
// Called in the GUI thread.
this.invalidate();
this.validate();
// TODO: Could be paintImmediately.
this.repaint();
}
private @Nullable NodeImpl getRootNode() {
final RBlock block = this.rblock;
return block == null ? null : (NodeImpl) block.getModelNode();
}
private void onMouseClick(final MouseEvent event) {
// Rely on AWT mouse-click only for double-clicks
final RBlock block = this.rblock;
if (block != null) {
final int clickCount = event.getClickCount();
if (SwingUtilities.isLeftMouseButton(event) && (clickCount > 1)) {
// TODO: Double-click must be revised. It generates
// a single click via mouse release.
final Point point = event.getPoint();
block.onDoubleClick(event, point.x, point.y);
} else if (SwingUtilities.isMiddleMouseButton(event) && (clickCount == 1)) {
block.onMiddleClick(event, event.getX(), event.getY());
} else if (SwingUtilities.isRightMouseButton(event) && (clickCount == 1)) {
block.onRightClick(event, event.getX(), event.getY());
}
}
}
private BoundableRenderable mousePressTarget;
private Map desktopHints = null;
private void onMousePressed(final MouseEvent event) {
this.requestFocus();
final RBlock block = this.rblock;
if (block != null) {
final Point point = event.getPoint();
this.mousePressTarget = block;
final int rx = point.x;
final int ry = point.y;
block.onMousePressed(event, rx, ry);
final RenderableSpot rp = block.getLowestRenderableSpot(rx, ry);
if (rp != null) {
this.frameContext.resetSelection(rp);
} else {
this.frameContext.resetSelection(null);
}
}
}
private void onMouseReleased(final MouseEvent event) {
final RBlock block = this.rblock;
if (block != null) {
final Point point = event.getPoint();
final int rx = point.x;
final int ry = point.y;
if (SwingUtilities.isLeftMouseButton(event)) {
// TODO: This will be raised twice on a double-click.
if (event.isControlDown()) {
block.onMiddleClick(event, rx, ry);
} else {
block.onMouseClick(event, rx, ry);
}
} else if (SwingUtilities.isRightMouseButton(event)) {
block.onRightClick(event, rx, ry);
}
block.onMouseReleased(event, rx, ry);
final BoundableRenderable oldTarget = this.mousePressTarget;
if (oldTarget != null) {
this.mousePressTarget = null;
if (oldTarget != block) {
oldTarget.onMouseDisarmed(event);
}
}
} else {
this.mousePressTarget = null;
}
}
private void onMouseExited(final MouseEvent event) {
final BoundableRenderable oldTarget = this.mousePressTarget;
if (oldTarget != null) {
this.mousePressTarget = null;
oldTarget.onMouseDisarmed(event);
}
}
private Renderable getInnerMostRenderable(final int x, final int y) {
final RBlock block = this.rblock;
BoundableRenderable r = block.getRenderable(x - block.getVisualX(), y - block.getVisualY());
int xi = x, yi = y;
BoundableRenderable inner = null;
BoundableRenderable prevR = null;
do {
if (r instanceof RCollection) {
RCollection rc = (RCollection) r;
if (prevR != null) {
final Point oi = prevR.getOriginRelativeTo(rc);
xi -= oi.x;
yi -= oi.y;
}
// xi -= rc.getVisualX();
// yi -= rc.getVisualY();
inner = rc.getRenderable(xi, yi);
if (inner != null) {
prevR = r;
r = inner;
}
} else {
inner = null;
}
} while (inner != null);
return r;
}
private RBlock getContainingBlock(final Renderable r) {
if (r instanceof RBlock) {
return (RBlock) r;
} else if (r instanceof TranslatedRenderable) {
return getContainingBlock(((TranslatedRenderable) r).getChild());
} else if (r == null) {
return null;
} else if (r instanceof BoundableRenderable) {
return getContainingBlock(((BoundableRenderable)r).getParent());
} else {
return null;
}
}
private void onMouseWheelMoved(final MouseWheelEvent mwe) {
final RBlock block = this.rblock;
if (block != null) {
switch (mwe.getScrollType()) {
case MouseWheelEvent.WHEEL_UNIT_SCROLL:
final int factor = mwe.isShiftDown() ? 2 : 1;
final int units = mwe.getWheelRotation() * mwe.getScrollAmount() * factor;
final Renderable innerMostRenderable = getInnerMostRenderable(mwe.getX(), mwe.getY());
boolean consumed = false;
RBlock innerBlock = getContainingBlock(innerMostRenderable);
do {
if (innerBlock != null) {
consumed = innerBlock.scrollByUnits(Adjustable.VERTICAL, units);
innerBlock = getContainingBlock(innerBlock.getParent());
}
} while ((!consumed) && (innerBlock != null));
break;
}
}
}
private void onMouseDragged(final MouseEvent event) {
final RBlock block = this.rblock;
if (block != null) {
final Point point = event.getPoint();
final RenderableSpot rp = block.getLowestRenderableSpot(point.x, point.y);
if (rp != null) {
this.frameContext.expandSelection(rp);
}
block.ensureVisible(point);
}
}
private void onMouseMoved(final MouseEvent event) {
final RBlock block = this.rblock;
if (block != null) {
final Point point = event.getPoint();
block.onMouseMoved(event, point.x, point.y, false, null);
}
}
private boolean applyRenderHints = true;
void disableRenderHints() {
this.applyRenderHints = false;
}
/*
* (non-Javadoc)
*
* @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
*/
// protected void paintComponent(Graphics g) {
@Override
public void paint(final Graphics g) {
// We go against Sun's advice and override
// paint() instead of paintComponent(). Scrollbars
// do not repaint correctly if we use
// paintComponent.
if (this.isOpaque()) {
// Background not painted by default in JComponent.
final Rectangle clipBounds = g.getClipBounds();
g.setColor(this.getBackground());
g.fillRect(clipBounds.x, clipBounds.y, clipBounds.width, clipBounds.height);
}
if (applyRenderHints && g instanceof Graphics2D) {
final Graphics2D g2 = (Graphics2D) g;
if (desktopHints == null) {
desktopHints = (Map) (Toolkit.getDefaultToolkit().getDesktopProperty("awt.font.desktophints"));
}
if (desktopHints != null) {
g2.addRenderingHints(desktopHints);
} else {
try {
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
} catch (final NoSuchFieldError e) {
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
}
}
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
}
final RBlock block = this.rblock;
if (block != null) {
block.paint(g);
// Paint FrameContext selection
final RenderableSpot start = this.startSelection;
final RenderableSpot end = this.endSelection;
if ((start != null) && (end != null) && !start.equals(end)) {
block.paintSelection(g, false, start, end);
}
}
// For debugging
// drawGrid(g);
}
private void drawGrid(final Graphics g) {
final int GRID_SIZE = 50;
final int OFFSET_X = 0;
final int OFFSET_Y = 0;
// Draw grid for debug
final Rectangle clipBounds = g.getClipBounds();
g.setColor(new Color(0, 0, 0, 30));
for (int i = 0; i < clipBounds.width; i+= GRID_SIZE) {
g.drawLine(i + OFFSET_X, 0, i + OFFSET_X, clipBounds.height);
}
for (int j = 0; j < clipBounds.height; j+= GRID_SIZE) {
g.drawLine(0, j + OFFSET_Y, clipBounds.width, j + OFFSET_Y);
}
}
@Override
public void doLayout() {
final NodeImpl rootNode = getRootNode();
if (rootNode instanceof HTMLDocumentImpl) {
final HTMLDocumentImpl doc = (HTMLDocumentImpl) rootNode;
final boolean layoutBlocked = doc.layoutBlocked.get();
if (layoutBlocked) {
return;
}
// Note: There were issues with this previously. See GH #147
doc.primeNodeData();
}
try {
final Dimension size = this.getSize();
this.clearComponents();
final RBlock block = this.rblock;
if (block != null) {
block.layout(size.width, size.height, true, true, null, false);
// Only set origin
block.setOrigin(0, 0);
block.updateWidgetBounds(0, 0);
this.updateGUIComponents();
// dumpRndTree(block);
if (!scrollCompleted) {
scrollCompleted = true;
if (rootNode instanceof HTMLDocumentImpl) {
final HTMLDocumentImpl doc = (HTMLDocumentImpl) rootNode;
final String ref = doc.getDocumentURL().getRef();
if (ref != null && ref.length() > 0) {
scrollTo(doc.getElementById(ref));
}
}
}
layoutCompleted.complete(true);
} else {
if (this.getComponentCount() > 0) {
this.removeAll();
}
}
} catch (final Exception thrown) {
logger.log(Level.SEVERE, "Unexpected error in layout engine. Document is " + this.getRootNode(), thrown);
}
}
/**
* Implementation of UINode.repaint().
*/
public void repaint(final ModelNode modelNode) {
// this.rblock.invalidateRenderStyle();
this.repaint();
}
public String getSelectionText() {
final RenderableSpot start = this.startSelection;
final RenderableSpot end = this.endSelection;
if ((start != null) && (end != null)) {
final StringBuffer buffer = new StringBuffer();
this.rblock.extractSelectionText(buffer, false, start, end);
return buffer.toString();
} else {
return null;
}
}
public boolean hasSelection() {
final RenderableSpot start = this.startSelection;
final RenderableSpot end = this.endSelection;
if ((start != null) && (end != null) && !start.equals(end)) {
return true;
} else {
return false;
}
}
@Override
protected void paintChildren(final Graphics g) {
// Overridding with NOP. For various reasons,
// the regular mechanism for painting children
// needs to be handled by Cobra.
}
public Color getPaintedBackgroundColor() {
return this.isOpaque() ? this.getBackground() : null;
}
/*
* (non-Javadoc)
*
* @see
* java.awt.datatransfer.ClipboardOwner#lostOwnership(java.awt.datatransfer
* .Clipboard, java.awt.datatransfer.Transferable)
*/
public void lostOwnership(final Clipboard arg0, final Transferable arg1) {
}
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();
}
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.
}
public void updateAllWidgetBounds() {
this.rblock.updateWidgetBounds(0, 0);
}
public Point getGUIPoint(final int clientX, final int clientY) {
// This is the GUI!
return new Point(clientX, clientY);
}
public void focus() {
this.grabFocus();
}
private boolean processingDocumentNotification = false;
void processDocumentNotifications(final 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 repainters = null;
final int length = notifications.length;
for (int i = 0; i < length; i++) {
final DocumentNotification dn = notifications[i];
final int type = dn.type;
switch (type) {
case DocumentNotification.GENERIC:
case DocumentNotification.SIZE: {
final NodeImpl 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 {
final UINode uiNode = node.findUINode();
if (uiNode != null) {
final RElement relement = (RElement) uiNode;
relement.invalidateLayoutUpTree();
relement.invalidateLayoutDeep();
// 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.
final NodeImpl node = dn.node;
final NodeImpl parent = (NodeImpl) node.getParentNode();
if (parent != null) {
final UINode uiNode = parent.findUINode();
if (uiNode != null) {
final RElement relement = (RElement) uiNode;
relement.invalidateLayoutUpTree();
}
}
topLayout = true;
break;
}
case DocumentNotification.LOOK: {
final NodeImpl node = dn.node;
final UINode uiNode = node.findUINode();
if (uiNode != null) {
if (repainters == null) {
repainters = new ArrayList<>(1);
}
final RElement relement = (RElement) uiNode;
relement.invalidateRenderStyle();
repainters.add(relement);
}
break;
}
default:
break;
}
}
if (topLayout) {
this.revalidatePanel();
} else {
if (repainters != null) {
final Iterator i = repainters.iterator();
while (i.hasNext()) {
final RElement element = i.next();
element.repaint();
}
}
}
} finally {
this.processingDocumentNotification = false;
}
}
public RenderableContainer getParentContainer() {
return null;
}
public void addDelayedPair(final DelayedPair pair) {
throw new UnsupportedOperationException("Delayed pairs are not being handled at this level.");
}
public Collection getDelayedPairs() {
throw new UnsupportedOperationException("Delayed pairs are not being handled at this level.");
}
public void clearDelayedPairs() {
throw new UnsupportedOperationException("Delayed pairs are not being handled at this level.");
}
private Set components;
private void clearComponents() {
final Set c = this.components;
if (c != null) {
c.clear();
}
}
public Component addComponent(final Component component) {
Set c = this.components;
if (c == null) {
c = new HashSet<>();
this.components = c;
}
if (c.add(component)) {
return component;
} else {
return null;
}
}
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.
final Set c = this.components;
if (c == null) {
if (this.getComponentCount() != 0) {
this.removeAll();
}
} else {
// Remove children not in the set.
final Set workingSet = new HashSet<>();
workingSet.addAll(c);
int count = this.getComponentCount();
for (int i = 0; i < count;) {
final 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.
final Iterator wsi = workingSet.iterator();
while (wsi.hasNext()) {
final Component component = wsi.next();
this.add(component);
}
}
}
public int getDefaultOverflowX() {
return defaultOverflowX;
}
public void setDefaultOverflowX(final int defaultOverflowX) {
if (defaultOverflowX != this.defaultOverflowX) {
this.defaultOverflowX = defaultOverflowX;
final RBlock block = this.rblock;
if (block != null) {
block.setDefaultOverflowX(defaultOverflowX);
block.relayoutIfValid();
}
}
}
public int getDefaultOverflowY() {
return defaultOverflowY;
}
public void setDefaultOverflowY(final int defaultOverflowY) {
if (this.defaultOverflowY != defaultOverflowY) {
this.defaultOverflowY = defaultOverflowY;
final 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.");
}
@Override
public Insets getInsetsMarginBorder(final boolean hscroll, final boolean vscroll) {
throw new UnsupportedOperationException("Method added while fixing #32. Not implemented yet.");
}
@Override
public int getVisualHeight() {
return rblock.getVisualHeight();
}
@Override
public int getVisualWidth() {
return rblock.getVisualWidth();
}
@Override
public Rectangle getVisualBounds() {
return new Rectangle(getX(), getY(), getVisualWidth(), getVisualHeight());
}
public Point translateDescendentPoint(BoundableRenderable descendent, int x, int y) {
return rblock.translateDescendentPoint(descendent, x, y);
}
@Override
public Point getOriginRelativeTo(RCollection bodyLayout) {
// TODO Auto-generated method stub
return null;
}
@Override
public Point getOriginRelativeToAbs(RCollection bodyLayout) {
// TODO Auto-generated method stub
return null;
}
@SuppressWarnings("unused")
private static void dumpRndTree(final Renderable root) {
System.out.println("------------------------------");
RBlock.dumpRndTree("", true, root, true);
System.out.println("------------------------------");
}
private CompletableFuture layoutCompleted = new CompletableFuture<>();
public Future layoutCompletion() {
return layoutCompleted;
}
public boolean isReadyToPaint() {
final RBlock block = this.rblock;
if (block != null) {
final HTMLDocumentImpl doc = (HTMLDocumentImpl) block.getModelNode();
return (!doc.getWindow().hasPendingTasks()) && block.isReadyToPaint();
}
return false;
}
}