org.cobraparser.html.gui.HtmlPanel 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 Nov 19, 2005
*/
package org.cobraparser.html.gui;
import java.awt.Cursor;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.EventListener;
import java.util.EventObject;
import java.util.concurrent.Future;
import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import org.cobraparser.html.HtmlRendererContext;
import org.cobraparser.html.domimpl.DocumentNotificationListener;
import org.cobraparser.html.domimpl.ElementImpl;
import org.cobraparser.html.domimpl.HTMLDocumentImpl;
import org.cobraparser.html.domimpl.NodeImpl;
import org.cobraparser.html.parser.DocumentBuilderImpl;
import org.cobraparser.html.parser.InputSourceImpl;
import org.cobraparser.html.renderer.BoundableRenderable;
import org.cobraparser.html.renderer.FrameContext;
import org.cobraparser.html.renderer.NodeRenderer;
import org.cobraparser.html.renderer.RenderableSpot;
import org.cobraparser.html.style.RenderState;
import org.cobraparser.util.gui.DefferedLayoutSupport;
import org.cobraparser.ua.UserAgentContext;
import org.cobraparser.util.EventDispatch2;
import org.cobraparser.util.gui.WrapperLayout;
import org.w3c.dom.Document;
import org.w3c.dom.Text;
import org.w3c.dom.html.HTMLFrameSetElement;
/**
* The HtmlPanel
class is a Swing component that can render a HTML
* DOM. It uses either {@link HtmlBlockPanel} or {@link FrameSetPanel}
* internally, depending on whether the document is determined to be a FRAMESET
* or not.
*
* Invoke method {@link #setDocument(Document, HtmlRendererContext)} in order to
* schedule a document for rendering.
*/
public class HtmlPanel extends JComponent implements FrameContext, DefferedLayoutSupport {
private static final long serialVersionUID = -8342517547909796721L;
private final EventDispatch2 selectionDispatch = new SelectionDispatch();
private final javax.swing.Timer notificationTimer;
private final DocumentNotificationListener notificationListener;
private final Runnable notificationImmediateAction;
private static final int NOTIF_TIMER_DELAY = 150;
private volatile boolean isFrameSet = false;
private volatile NodeRenderer nodeRenderer = null;
private volatile NodeImpl rootNode;
private volatile int preferredWidth = -1;
private volatile int defaultOverflowX = RenderState.OVERFLOW_AUTO;
private volatile int defaultOverflowY = RenderState.OVERFLOW_AUTO;
protected volatile HtmlBlockPanel htmlBlockPanel;
protected volatile FrameSetPanel frameSetPanel;
/**
* Constructs an HtmlPanel
.
*/
public HtmlPanel() {
super();
this.setLayout(WrapperLayout.getInstance());
this.setOpaque(false);
this.notificationTimer = new javax.swing.Timer(NOTIF_TIMER_DELAY, new NotificationTimerAction());
this.notificationTimer.setRepeats(false);
this.notificationListener = new LocalDocumentNotificationListener();
this.notificationImmediateAction = new Runnable() {
public void run() {
processNotifications();
}
};
}
/**
* Sets a preferred width that serves as a hint in calculating the preferred
* size of the HtmlPanel
. Note that the preferred size can only
* be calculated when a document is available, and it will vary during
* incremental rendering.
*
* This method currently does not have any effect when the document is a
* FRAMESET.
*
* Note also that setting the preferred width (to a value other than
* -1
) will negatively impact performance.
*
* @param width
* The preferred width, or -1
to unset.
*/
public void setPreferredWidth(final int width) {
this.preferredWidth = width;
final HtmlBlockPanel htmlBlock = this.htmlBlockPanel;
if (htmlBlock != null) {
htmlBlock.setPreferredWidth(width);
}
}
/**
* If the current document is not a FRAMESET, this method 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 HtmlBlockPanel htmlBlock = this.htmlBlockPanel;
if (htmlBlock != null) {
htmlBlock.scrollTo(bounds, xIfNeeded, yIfNeeded);
}
}
/**
* 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 org.w3c.dom.Node node) {
final HtmlBlockPanel htmlBlock = this.htmlBlockPanel;
if (htmlBlock != null) {
htmlBlock.scrollTo(node);
}
}
/**
* Gets the root Renderable
of the HTML block. It returns
* null
for FRAMESETs.
*/
public BoundableRenderable getBlockRenderable() {
final HtmlBlockPanel htmlBlock = this.htmlBlockPanel;
return htmlBlock == null ? null : htmlBlock.getRootRenderable();
}
/**
* Gets an instance of {@link FrameSetPanel} in case the currently rendered
* page is a FRAMESET.
*
* Note: This method should be invoked in the GUI thread.
*
* @return A FrameSetPanel
instance or null
if the
* document currently rendered is not a FRAMESET.
*/
public FrameSetPanel getFrameSetPanel() {
final int componentCount = this.getComponentCount();
if (componentCount == 0) {
return null;
}
final Object c = this.getComponent(0);
if (c instanceof FrameSetPanel) {
return (FrameSetPanel) c;
}
return null;
}
private void setUpAsBlock(final UserAgentContext ucontext, final HtmlRendererContext rcontext) {
final HtmlBlockPanel shp = this.createHtmlBlockPanel(ucontext, rcontext);
shp.setPreferredWidth(this.preferredWidth);
shp.setDefaultOverflowX(this.defaultOverflowX);
shp.setDefaultOverflowY(this.defaultOverflowY);
this.htmlBlockPanel = shp;
this.frameSetPanel = null;
this.removeAll();
this.add(shp);
this.nodeRenderer = shp;
}
private void setUpFrameSet(final NodeImpl fsrn) {
this.isFrameSet = true;
this.htmlBlockPanel = null;
final FrameSetPanel fsp = this.createFrameSetPanel();
this.frameSetPanel = fsp;
this.nodeRenderer = fsp;
this.removeAll();
this.add(fsp);
fsp.setRootNode(fsrn);
}
/**
* Method invoked internally to create a {@link HtmlBlockPanel}. It is made
* available so it can be overridden.
*/
protected HtmlBlockPanel createHtmlBlockPanel(final UserAgentContext ucontext, final HtmlRendererContext rcontext) {
return new HtmlBlockPanel(java.awt.Color.WHITE, true, ucontext, rcontext, this);
}
/**
* Method invoked internally to create a {@link FrameSetPanel}. It is made
* available so it can be overridden.
*/
protected FrameSetPanel createFrameSetPanel() {
return new FrameSetPanel();
}
/**
* Scrolls the document such that x and y coordinates are placed in the
* upper-left corner of the panel.
*
* This method may be called outside of the GUI Thread.
*
* @param x
* The x coordinate.
* @param y
* The y coordinate.
*/
public void scroll(final int x, final int y) {
if (SwingUtilities.isEventDispatchThread()) {
this.scrollImpl(x, y);
} else {
SwingUtilities.invokeLater(() -> scrollImpl(x, y));
}
}
public void scrollBy(final int x, final int y) {
if (SwingUtilities.isEventDispatchThread()) {
this.scrollByImpl(x, y);
} else {
SwingUtilities.invokeLater(() -> scrollByImpl(x, y));
}
}
private void scrollImpl(final int x, final int y) {
this.scrollTo(new Rectangle(x, y, 16, 16), false, false);
}
private void scrollByImpl(final int xOffset, final int yOffset) {
final HtmlBlockPanel bp = this.htmlBlockPanel;
if (bp != null) {
bp.scrollBy(xOffset, yOffset);
}
}
/**
* Clears the current document if any. If called outside the GUI thread, the
* operation will be scheduled to be performed in the GUI thread.
*/
public void clearDocument() {
if (SwingUtilities.isEventDispatchThread()) {
this.clearDocumentImpl();
} else {
SwingUtilities.invokeLater(() -> HtmlPanel.this.clearDocumentImpl());
}
}
private void clearDocumentImpl() {
final HTMLDocumentImpl prevDocument = (HTMLDocumentImpl) this.rootNode;
if (prevDocument != null) {
prevDocument.removeDocumentNotificationListener(this.notificationListener);
}
final NodeRenderer nr = this.nodeRenderer;
if (nr != null) {
nr.setRootNode(null);
}
this.rootNode = null;
this.htmlBlockPanel = null;
this.nodeRenderer = null;
this.isFrameSet = false;
this.removeAll();
this.revalidate();
this.repaint();
}
/**
* Sets an HTML DOM node and invalidates the component so it is rendered as
* soon as possible in the GUI thread.
*
* If this method is called from a thread that is not the GUI dispatch thread,
* the document is scheduled to be set later. Note that
* {@link #setPreferredWidth(int) preferred size} calculations should be done
* in the GUI dispatch thread for this reason.
*
* @param node
* This should normally be a Document instance obtained with
* {@link org.cobraparser.html.parser.DocumentBuilderImpl}.
*
* @param rcontext
* A renderer context.
* @see org.cobraparser.html.parser.DocumentBuilderImpl#parse(org.xml.sax.InputSource)
* @see org.cobraparser.html.test.SimpleHtmlRendererContext
*/
public void setDocument(final Document node, final HtmlRendererContext rcontext) {
setCursor(Cursor.getDefaultCursor());
if (SwingUtilities.isEventDispatchThread()) {
this.setDocumentImpl(node, rcontext);
} else {
SwingUtilities.invokeLater(() -> HtmlPanel.this.setDocumentImpl(node, rcontext));
}
}
@Override
public void setCursor(final Cursor cursor) {
if (cursor != getCursor()) {
super.setCursor(cursor);
}
}
/**
* Scrolls to the element identified by the given ID in the current document.
*
* If this method is invoked outside the GUI thread, the operation is
* scheduled to be performed as soon as possible in the GUI thread.
*
* @param nameOrId
* The name or ID of the element in the document.
*/
public void scrollToElement(final String nameOrId) {
if (SwingUtilities.isEventDispatchThread()) {
this.scrollToElementImpl(nameOrId);
} else {
SwingUtilities.invokeLater(() -> scrollToElementImpl(nameOrId));
}
}
private void scrollToElementImpl(final String nameOrId) {
final NodeImpl node = this.rootNode;
if (node instanceof HTMLDocumentImpl) {
final HTMLDocumentImpl doc = (HTMLDocumentImpl) node;
final org.w3c.dom.Element element = doc.getElementById(nameOrId);
if (element != null) {
this.scrollTo(element);
}
}
}
private void setDocumentImpl(final Document node, final HtmlRendererContext rcontext) {
// Expected to be called in the GUI thread.
/*
if (!(node instanceof HTMLDocumentImpl)) {
throw new IllegalArgumentException("Only nodes of type HTMLDocumentImpl are currently supported. Use DocumentBuilderImpl.");
}
*/
if (this.rootNode instanceof HTMLDocumentImpl) {
final HTMLDocumentImpl prevDocument = (HTMLDocumentImpl) this.rootNode;
prevDocument.removeDocumentNotificationListener(this.notificationListener);
}
if (node instanceof HTMLDocumentImpl) {
final HTMLDocumentImpl nodeImpl = (HTMLDocumentImpl) node;
nodeImpl.addDocumentNotificationListener(this.notificationListener);
}
if (node instanceof NodeImpl) {
final NodeImpl nodeImpl = (NodeImpl) node;
this.rootNode = nodeImpl;
final NodeImpl fsrn = this.getFrameSetRootNode(nodeImpl);
final boolean newIfs = fsrn != null;
if ((newIfs != this.isFrameSet) || (this.getComponentCount() == 0)) {
this.isFrameSet = newIfs;
if (newIfs) {
this.setUpFrameSet(fsrn);
} else {
this.setUpAsBlock(rcontext.getUserAgentContext(), rcontext);
}
}
final NodeRenderer nr = this.nodeRenderer;
if (nr != null) {
// These subcomponents should take care
// of revalidation.
if (newIfs) {
nr.setRootNode(fsrn);
} else {
nr.setRootNode(nodeImpl);
}
} else {
this.invalidate();
this.validate();
this.repaint();
}
}
}
/**
* Renders HTML given as a string.
*
* @param htmlSource
* The HTML source code.
* @param uri
* A base URI used to resolve item URIs.
* @param rcontext
* The {@link HtmlRendererContext} instance.
* @see org.cobraparser.html.test.SimpleHtmlRendererContext
* @see #setDocument(Document, HtmlRendererContext)
*/
public void setHtml(final String htmlSource, final String uri, final HtmlRendererContext rcontext) {
try {
final DocumentBuilderImpl builder = new DocumentBuilderImpl(rcontext.getUserAgentContext(), rcontext);
try (
final Reader reader = new StringReader(htmlSource)) {
final InputSourceImpl is = new InputSourceImpl(reader, uri);
final Document document = builder.parse(is);
this.setDocument(document, rcontext);
}
} catch (final java.io.IOException ioe) {
throw new IllegalStateException("Unexpected condition.", ioe);
} catch (final org.xml.sax.SAXException se) {
throw new IllegalStateException("Unexpected condition.", se);
}
}
/**
* Gets the HTML DOM node currently rendered if any.
*/
public NodeImpl getRootNode() {
return this.rootNode;
}
private boolean resetIfFrameSet() {
final NodeImpl nodeImpl = this.rootNode;
final NodeImpl fsrn = this.getFrameSetRootNode(nodeImpl);
final boolean newIfs = fsrn != null;
if ((newIfs != this.isFrameSet) || (this.getComponentCount() == 0)) {
this.isFrameSet = newIfs;
if (newIfs) {
this.setUpFrameSet(fsrn);
final NodeRenderer nr = this.nodeRenderer;
nr.setRootNode(fsrn);
// Set proper bounds and repaint.
this.validate();
this.repaint();
return true;
}
}
return false;
}
private NodeImpl getFrameSetRootNode(final NodeImpl node) {
if (node instanceof Document) {
final ElementImpl element = (ElementImpl) ((Document) node).getDocumentElement();
if ((element != null) && "HTML".equalsIgnoreCase(element.getTagName())) {
return this.getFrameSet(element);
} else {
return this.getFrameSet(node);
}
} else {
return null;
}
}
private NodeImpl getFrameSet(final NodeImpl node) {
final NodeImpl[] children = node.getChildrenArray();
if (children == null) {
return null;
}
final int length = children.length;
NodeImpl frameSet = null;
for (int i = 0; i < length; i++) {
final NodeImpl child = children[i];
if (child instanceof Text) {
// Ignore
} else if (child instanceof ElementImpl) {
final String tagName = child.getNodeName();
if ("HEAD".equalsIgnoreCase(tagName) || "NOFRAMES".equalsIgnoreCase(tagName) || "TITLE".equalsIgnoreCase(tagName)
|| "META".equalsIgnoreCase(tagName) || "SCRIPT".equalsIgnoreCase(tagName) || "NOSCRIPT".equalsIgnoreCase(tagName)) {
// ignore it
} else if ("FRAMESET".equalsIgnoreCase(tagName)) {
frameSet = child;
break;
} else {
if (this.hasSomeHtml((ElementImpl) child)) {
return null;
}
}
}
}
return frameSet;
}
private boolean hasSomeHtml(final ElementImpl element) {
final String tagName = element.getTagName();
if ("HEAD".equalsIgnoreCase(tagName) || "TITLE".equalsIgnoreCase(tagName) || "META".equalsIgnoreCase(tagName)) {
return false;
}
final NodeImpl[] children = element.getChildrenArray();
if (children != null) {
final int length = children.length;
for (int i = 0; i < length; i++) {
final NodeImpl child = children[i];
if (child instanceof Text) {
final String textContent = ((Text) child).getTextContent();
if ((textContent != null) && !"".equals(textContent.trim())) {
return false;
}
} else if (child instanceof ElementImpl) {
if (this.hasSomeHtml((ElementImpl) child)) {
return false;
}
}
}
}
return true;
}
/**
* Internal method used to expand the selection to the given point.
*
* Note: This method should be invoked in the GUI thread.
*/
public void expandSelection(final RenderableSpot rpoint) {
final HtmlBlockPanel block = this.htmlBlockPanel;
if (block != null) {
block.setSelectionEnd(rpoint);
block.repaint();
this.selectionDispatch.fireEvent(new SelectionChangeEvent(this, block.isSelectionAvailable()));
}
}
/**
* Internal method used to reset the selection so that it is empty at the
* given point. This is what is called when the user clicks on a point in the
* document.
*
* Note: This method should be invoked in the GUI thread.
*/
public void resetSelection(final RenderableSpot rpoint) {
final HtmlBlockPanel block = this.htmlBlockPanel;
if (block != null) {
block.setSelectionStart(rpoint);
block.setSelectionEnd(rpoint);
block.repaint();
}
this.selectionDispatch.fireEvent(new SelectionChangeEvent(this, false));
}
/**
* Gets the selection text.
*
* Note: This method should be invoked in the GUI thread.
*/
public String getSelectionText() {
final HtmlBlockPanel block = this.htmlBlockPanel;
if (block == null) {
return null;
} else {
return block.getSelectionText();
}
}
/**
* Gets a DOM node enclosing the selection. The node returned should be the
* inner-most node that encloses both selection start and end points. Note
* that the selection end point may be just outside of the selection.
*
* Note: This method should be invoked in the GUI thread.
*
* @return A node enclosing the current selection, or null
if
* there is no such node. It also returns null
for
* FRAMESETs.
*/
public org.w3c.dom.Node getSelectionNode() {
final HtmlBlockPanel block = this.htmlBlockPanel;
if (block == null) {
return null;
} else {
return block.getSelectionNode();
}
}
/**
* Returns true only if the current block has a selection. This method has no
* effect in FRAMESETs at the moment.
*/
public boolean hasSelection() {
final HtmlBlockPanel block = this.htmlBlockPanel;
if (block == null) {
return false;
} else {
return block.hasSelection();
}
}
/**
* Copies the current selection, if any, into the clipboard. This method has
* no effect in FRAMESETs at the moment.
*/
public boolean copy() {
final HtmlBlockPanel block = this.htmlBlockPanel;
if (block != null) {
return block.copy();
} else {
return false;
}
}
/**
* Adds listener of selection changes. Note that it does not have any effect
* on FRAMESETs.
*
* @param listener
* An instance of {@link SelectionChangeListener}.
*/
public void addSelectionChangeListener(final SelectionChangeListener listener) {
this.selectionDispatch.addListener(listener);
}
/**
* Removes a listener of selection changes that was previously added.
*/
public void removeSelectionChangeListener(final SelectionChangeListener listener) {
this.selectionDispatch.removeListener(listener);
}
/**
* Sets the default horizontal overflow.
*
* This method has no effect on FRAMESETs.
*
* @param overflow
* See {@link org.cobraparser.html.style.RenderState}.
*/
public void setDefaultOverflowX(final int overflow) {
this.defaultOverflowX = overflow;
final HtmlBlockPanel block = this.htmlBlockPanel;
if (block != null) {
block.setDefaultOverflowX(overflow);
}
}
/**
* Sets the default vertical overflow.
*
* This method has no effect on FRAMESETs.
*
* @param overflow
* See {@link org.cobraparser.html.style.RenderState}.
*/
public void setDefaultOverflowY(final int overflow) {
this.defaultOverflowY = overflow;
final HtmlBlockPanel block = this.htmlBlockPanel;
if (block != null) {
block.setDefaultOverflowY(overflow);
}
}
private final ArrayList notifications = new ArrayList<>(1);
private void addNotification(final DocumentNotification notification) {
// This can be called in a random thread.
final ArrayList notifs = this.notifications;
synchronized (notifs) {
notifs.add(notification);
}
if (SwingUtilities.isEventDispatchThread()) {
// In this case we want the notification to be processed
// immediately. However, we don't want potential recursions
// to occur when a Javascript property is set in the GUI thread.
// Additionally, many property values may be set in one
// event block.
SwingUtilities.invokeLater(this.notificationImmediateAction);
} else {
this.notificationTimer.restart();
}
}
/**
* Invalidates the layout of the given node and schedules it to be layed out
* later. Multiple invalidations may be processed in a single document layout.
*/
public void delayedRelayout(final NodeImpl node) {
final ArrayList notifs = this.notifications;
synchronized (notifs) {
notifs.add(new DocumentNotification(DocumentNotification.SIZE, node));
}
this.notificationTimer.restart();
}
private void processNotifications() {
// This is called in the GUI thread.
final ArrayList notifs = this.notifications;
DocumentNotification[] notifsArray;
synchronized (notifs) {
final int size = notifs.size();
if (size == 0) {
return;
}
notifsArray = new DocumentNotification[size];
notifsArray = notifs.toArray(notifsArray);
notifs.clear();
}
final int length = notifsArray.length;
for (int i = 0; i < length; i++) {
final DocumentNotification dn = notifsArray[i];
if ((dn.node instanceof HTMLFrameSetElement) && (this.htmlBlockPanel != null)) {
if (this.resetIfFrameSet()) {
// Revalidation already taken care of.
return;
}
}
}
final HtmlBlockPanel blockPanel = this.htmlBlockPanel;
if (blockPanel != null) {
blockPanel.processDocumentNotifications(notifsArray);
}
final FrameSetPanel frameSetPanel = this.frameSetPanel;
if (frameSetPanel != null) {
frameSetPanel.processDocumentNotifications(notifsArray);
}
}
private class SelectionDispatch extends EventDispatch2 {
/*
* (non-Javadoc)
*
* @see
* org.xamjwg.util.EventDispatch2#dispatchEvent(java.util.EventListener,
* java.util.EventObject)
*/
@Override
protected void dispatchEvent(final EventListener listener, final EventObject event) {
((SelectionChangeListener) listener).selectionChanged((SelectionChangeEvent) event);
}
}
private class LocalDocumentNotificationListener implements DocumentNotificationListener {
public void allInvalidated() {
HtmlPanel.this.addNotification(new DocumentNotification(DocumentNotification.GENERIC, null));
}
public void invalidated(final NodeImpl node) {
HtmlPanel.this.addNotification(new DocumentNotification(DocumentNotification.GENERIC, node));
}
public void lookInvalidated(final NodeImpl node) {
HtmlPanel.this.addNotification(new DocumentNotification(DocumentNotification.LOOK, node));
}
public void positionInvalidated(final NodeImpl node) {
HtmlPanel.this.addNotification(new DocumentNotification(DocumentNotification.POSITION, node));
}
public void sizeInvalidated(final NodeImpl node) {
HtmlPanel.this.addNotification(new DocumentNotification(DocumentNotification.SIZE, node));
}
public void externalScriptLoading(final NodeImpl node) {
// Ignorable here.
}
public void nodeLoaded(final NodeImpl node) {
HtmlPanel.this.addNotification(new DocumentNotification(DocumentNotification.GENERIC, node));
}
public void structureInvalidated(final NodeImpl node) {
HtmlPanel.this.addNotification(new DocumentNotification(DocumentNotification.GENERIC, node));
}
}
private class NotificationTimerAction implements java.awt.event.ActionListener {
public void actionPerformed(final ActionEvent e) {
HtmlPanel.this.processNotifications();
}
}
@Override
public Future layoutCompletion() {
return htmlBlockPanel.layoutCompletion();
}
public boolean isReadyToPaint() {
final HtmlBlockPanel htmlBlock = this.htmlBlockPanel;
if (htmlBlock != null) {
return (notifications.size() == 0) && htmlBlock.isReadyToPaint();
}
return false;
}
public void disableRenderHints() {
this.htmlBlockPanel.disableRenderHints();
}
}