All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.cobraparser.html.gui.HtmlBlockPanel Maven / Gradle / Ivy

There is a newer version: 1.0.2
Show newest version
/*
    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; } }