/* 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."); } }