Revision: 640
Author: allain.lalonde
Date: Tue Aug 4 06:56:20 2009
Log: Added javadocs to PHtml and renamed it to PHtmlView
http://code.google.com/p/piccolo2d/source/detail?r=640
Added:
/piccolo2d.java/branches/phtml/core/src/main/java/edu/umd/cs/piccolo/nodes/PHtmlView.java
/piccolo2d.java/branches/phtml/core/src/test/java/edu/umd/cs/piccolo/nodes/PHtmlViewTest.java
Deleted:
/piccolo2d.java/branches/phtml/core/src/main/java/edu/umd/cs/piccolo/nodes/PHtml.java
/piccolo2d.java/branches/phtml/core/src/test/java/edu/umd/cs/piccolo/nodes/PHtmlTest.java
=======================================
--- /dev/null
+++
/piccolo2d.java/branches/phtml/core/src/main/java/edu/umd/cs/piccolo/nodes/PHtmlView.java
Tue Aug 4 06:56:20 2009
@@ -0,0 +1,442 @@
+/*
+ * Copyright (c) 1998-2008, University of Maryland
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
modification, are permitted provided
+ * that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice,
this list of conditions
+ * and the following disclaimer.
+ *
+ * Redistributions in binary form must reproduce the above copyright
notice, this list of conditions
+ * and the following disclaimer in the documentation and/or other
materials provided with the
+ * distribution.
+ *
+ * None of the name of the University of Maryland, the name of the
Piccolo2D project, or the names of its
+ * contributors may be used to endorse or promote products derived from
this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package edu.umd.cs.piccolo.nodes;
+
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+
+import javax.swing.JLabel;
+import javax.swing.JTextField;
+import javax.swing.plaf.basic.BasicHTML;
+import javax.swing.text.Position;
+import javax.swing.text.View;
+
+import edu.umd.cs.piccolo.PNode;
+import edu.umd.cs.piccolo.util.PPaintContext;
+
+/**
+ * PHtml is a Piccolo node for rendering HTML text. It uses a JLabel under
the
+ * hood so you have the same restrictions regarding html as you have when
using
+ * standard Swing components (HTML 3.2 + subset of CSS 1.0).
+ *
+ * @author Chris Malley ([email protected])
+ * @author Sam Reid
+ * @author Allain Lalonde
+ */
+public class PHtmlView extends PNode {
+
+ private static final long serialVersionUID = 1L;
+
+ /** Default font to use if not overridden in the HTML markup. */
+ private static final Font DEFAULT_FONT = new JTextField().getFont();
+
+ /** Default font color to use if not overridden in the HTML markup */
+ private static final Color DEFAULT_HTML_COLOR = Color.BLACK;
+
+ /**
+ * The property name that identifies a change of this node's font (see
+ * {...@link #getFont getFont}). Both old and new value will be set in any
+ * property change event.
+ */
+ public static final String PROPERTY_FONT = "font";
+
+ /**
+ * The property code that identifies a change of this node's font (see
+ * {...@link #getFont getFont}). Both old and new value will be set in any
+ * property change event.
+ */
+ public static final int PROPERTY_CODE_FONT = 1 << 20;
+
+ /**
+ * The property name that identifies a change of this node's HTML (see
+ * {...@link #getHTML getHTML}). Both old and new value will be set in any
+ * property change event.
+ */
+ public static final String PROPERTY_HTML = "html";
+
+ /**
+ * The property code that identifies a change of this node's HTML (see
+ * {...@link #getHTML getHTML}). Both old and new value will be set in any
+ * property change event.
+ */
+ public static final int PROPERTY_CODE_HTML = 1 << 21;
+
+ /**
+ * The property name that identifies a change of this node's HTML
color (see
+ * {...@link #getHtml getHTMLColor}). Both old and new value will be set
in any
+ * property change event.
+ */
+ public static final String PROPERTY_HTML_COLOR = "html color";
+
+ /**
+ * The property code that identifies a change of this node's HTML
color (see
+ * {...@link #getHtml getHTMLColor}). Both old and new value will be set
in any
+ * property change event.
+ */
+ public static final int PROPERTY_CODE_HTML_COLOR = 1 << 22;
+
+ /** Underlying JLabel used to handle the rendering logic. */
+ private final JLabel htmlLabel;
+
+ /** Object that encapsulates the HTML rendering logic. */
+ private View htmlView;
+
+ /** Used to enforce bounds and wrapping on the HTML. */
+ private final Rectangle htmlBounds;
+
+ /**
+ * Creates an empty PHtml node with default font and color.
+ */
+ public PHtmlView() {
+ this(null, DEFAULT_FONT, DEFAULT_HTML_COLOR);
+ }
+
+ /**
+ * Creates a PHtml node that contains the provided HTML. It will have
+ * default font and color.
+ *
+ * @param html markup label should contain
+ */
+ public PHtmlView(final String html) {
+ this(html, DEFAULT_FONT, DEFAULT_HTML_COLOR);
+ }
+
+ /**
+ * Creates a PHtml node with the given markup and default html color.
+ *
+ * @param html markup label should contain
+ * @param htmlColor color that will be used unless overridden by the
markup
+ */
+ public PHtmlView(final String html, final Color htmlColor) {
+ this(html, DEFAULT_FONT, htmlColor);
+ }
+
+ /**
+ * Creates a PHtml node with the given markup and default HTML color.
+ *
+ * @param html markup label should contain
+ * @param font font that will be used unless overriden by the markup
+ * @param htmlColor color that will be used unless overridden by the
markup
+ */
+ public PHtmlView(final String html, final Font font, final Color
htmlColor) {
+ htmlLabel = new JLabel(html);
+ htmlLabel.setFont(font);
+ htmlLabel.setForeground(htmlColor);
+ htmlBounds = new Rectangle();
+ update();
+ }
+
+ /**
+ * @return HTML being rendered by this node
+ */
+ public String getHtml() {
+ return htmlLabel.getText();
+ }
+
+ /**
+ * Changes the HTML being rendered by this node.
+ *
+ * @param newHtml markup to swap with existing HTML
+ */
+ public void setHtml(final String newHtml) {
+ if (isNewHtml(newHtml)) {
+ final String oldHtml = htmlLabel.getText();
+ htmlLabel.setText(newHtml);
+ update();
+ firePropertyChange(PROPERTY_CODE_HTML, PROPERTY_HTML, oldHtml,
newHtml);
+ }
+ }
+
+ private boolean isNewHtml(final String html) {
+ if (html == null && getHtml() == null) {
+ return false;
+ }
+ else if (html == null || getHtml() == null) {
+ return true;
+ }
+ else {
+ return !htmlLabel.getText().equals(html);
+ }
+ }
+
+ /**
+ * Returns the default font being used when not overridden in the
markup.
+ *
+ * @return font being used when not overridden by the markup
+ */
+ public Font getFont() {
+ return htmlLabel.getFont();
+ }
+
+ /**
+ * Set the font of this PHtml. This may be overridden by the markup
using
+ * either styles or the font tag.
+ *
+ * @param newFont font to set as the default
+ */
+ public void setFont(final Font newFont) {
+ final Font oldFont = htmlLabel.getFont();
+ htmlLabel.setFont(newFont);
+ update();
+
+ firePropertyChange(PROPERTY_CODE_FONT, PROPERTY_FONT, oldFont,
newFont);
+ }
+
+ /**
+ * Gets the color used to render the HTML. If you want to get the
paint used
+ * for the node, use getPaint.
+ *
+ * @return the color used to render the HTML.
+ */
+ public Color getHtmlColor() {
+ return htmlLabel.getForeground();
+ }
+
+ /**
+ * Sets the color used to render the HTML. If you want to set the
paint used
+ * for the node, use setPaint.
+ *
+ * @param newColor new color to use when rendering HTML
+ */
+ public void setHtmlColor(final Color newColor) {
+ final Color oldColor = htmlLabel.getForeground();
+ htmlLabel.setForeground(newColor);
+ repaint();
+ firePropertyChange(PROPERTY_CODE_HTML_COLOR, PROPERTY_HTML_COLOR,
oldColor, newColor);
+ }
+
+ /**
+ * Applies all properties to the underlying JLabel, creates an
htmlView and
+ * updates bounds
+ */
+ private void update() {
+ htmlLabel.setSize(htmlLabel.getPreferredSize());
+ htmlView = BasicHTML.createHTMLView(htmlLabel, htmlLabel.getText()
== null ? "" : htmlLabel.getText());
+
+ final Rectangle2D bounds = getBounds();
+ htmlBounds.setRect(0, 0, bounds.getWidth(), bounds.getHeight());
+ repaint();
+ }
+
+ /** {...@inheritdoc} */
+ public boolean setBounds(final double x, final double y, final double
width, final double height) {
+ final boolean boundsChanged = super.setBounds(x, y, width, height);
+ update();
+ return boundsChanged;
+ }
+
+ /** {...@inheritdoc} */
+ public boolean setBounds(final Rectangle2D newBounds) {
+ final boolean boundsChanged = super.setBounds(newBounds);
+ update();
+ return boundsChanged;
+ }
+
+ /**
+ * Paints the node. The HTML string is painted last, so it appears on
top of
+ * any child nodes.
+ *
+ * @param paintContext the context in which painting is occurring
+ */
+ protected void paint(final PPaintContext paintContext) {
+ super.paint(paintContext);
+
+ if (htmlLabel.getWidth() != 0 && htmlLabel.getHeight() != 0) {
+ final Graphics2D g2 = paintContext.getGraphics();
+
+ htmlView.paint(g2, htmlBounds);
+ }
+ }
+
+ /**
+ * Returns the address specified in the link under the given point.
+ *
+ * @param clickedPoint
+ * @return String containing value of href for clicked link, or null
if no
+ * link clicked
+ */
+ public String getClickedAddress(final Point2D.Double clickedPoint) {
+ return getClickedAddress(clickedPoint.getX(), clickedPoint.getY());
+ }
+
+ /**
+ * Returns the address specified in the link under the given point.
+ *
+ * @param clickedPoint
+ * @return String containing value of href for clicked link, or null
if no
+ * link clicked
+ */
+ public String getClickedAddress(final double x, final double y) {
+ int position = pointToModelIndex(x, y);
+
+ final String html = htmlLabel.getText();
+
+ String address = null;
+
+ int currentPos = 0;
+ while (currentPos < html.length()) {
+ currentPos = html.indexOf('<', currentPos);
+ if (currentPos == -1 || position < currentPos) {
+ break;
+ }
+
+ final int tagStart = currentPos;
+ final int tagEnd = findTagEnd(html, currentPos);
+
+ if (tagEnd == -1) {
+ return null;
+ }
+
+ currentPos = tagEnd + 1;
+
+ final String tag = html.substring(tagStart, currentPos);
+
+ position += tag.length();
+
+ if ("</a>".equals(tag)) {
+ address = null;
+ }
+ else if (tag.startsWith("<a ")) {
+ address = extractHref(tag);
+ }
+ }
+
+ return address;
+ }
+
+ /**
+ * Returns the index into the raw text (without HTML) that the click
+ * occurred.
+ *
+ * @param x x component of the point clicked
+ * @param y y component of the point clicked
+ * @return index into the raw text (without HTML) that the click
occurred
+ */
+ private int pointToModelIndex(final double x, final double y) {
+ final Position.Bias[] biasReturn = new Position.Bias[1];
+ return htmlView.viewToModel((float) x, (float) y, getBounds(),
biasReturn);
+ }
+
+ /**
+ * Starting from the startPos, it finds the position at which the
given tag
+ * ends. Returns -1 if the end of the string was encountered before
the end
+ * of the tag was encountered.
+ *
+ * @param html raw HTML string being searched
+ * @param startPos where in the string to start searching for ">"
+ * @return index after the ">" character
+ */
+ private int findTagEnd(final String html, final int startPos) {
+ int currentPos = startPos;
+
+ currentPos++;
+
+ while (currentPos > 0 && currentPos < html.length() &&
html.charAt(currentPos) != '>') {
+ if (html.charAt(currentPos) == '\"') {
+ currentPos = html.indexOf('\"', currentPos + 1);
+ }
+ else if (html.charAt(currentPos) == '\'') {
+ currentPos = html.indexOf('\'', currentPos + 1);
+ }
+ currentPos++;
+ }
+
+ return currentPos == 0 || currentPos >= html.length() ? -1 :
currentPos + 1;
+ }
+
+ /**
+ * Given a tag, extracts the value of the href attribute, returns null
if
+ * none was found.
+ *
+ * @param tag from which to extract the href value
+ * @return href value without quotes or null if not found
+ */
+ private String extractHref(final String tag) {
+ int currentPos = 0;
+
+ final String href = null;
+
+ while (currentPos >= 0 && currentPos < tag.length() - 1) {
+ currentPos = tag.indexOf('=', currentPos + 1);
+ if (currentPos != -1 && isHrefAttributeAssignment(tag,
currentPos)) {
+ return extractHrefValue(tag, currentPos + 1);
+ }
+ }
+ return href;
+ }
+
+ /**
+ * Starting at the character after the equal sign of an href=..., it
extract
+ * the value. Handles single, double, and no quotes.
+ *
+ * @param tag
+ * @param startPos
+ * @return value of href or null if not found.
+ */
+ private String extractHrefValue(final String tag, final int startPos) {
+ int currentPos = startPos;
+
+ if (tag.charAt(currentPos) == '\"') {
+ final int startHref = currentPos + 1;
+ currentPos = tag.indexOf('\"', startHref);
+ return currentPos == -1 ? null : tag.substring(startHref,
currentPos);
+ }
+ else if (currentPos < tag.length() && tag.charAt(currentPos)
== '\'') {
+ final int startHref = currentPos + 1;
+ currentPos = tag.indexOf('\'', startHref);
+ return currentPos == -1 ? null : tag.substring(startHref,
currentPos);
+ }
+ else {
+ final int startHref = currentPos;
+
+ if (currentPos < tag.length()) {
+ do {
+ currentPos++;
+ } while (currentPos < tag.length() &&
tag.charAt(currentPos) != ' ' && tag.charAt(currentPos) != '>');
+ }
+ return tag.substring(startHref, currentPos);
+ }
+ }
+
+ /**
+ * Given the position in a string returns whether it points to the
equal
+ * sign of an href attribute.
+ *
+ * @param tag html code of the tag
+ * @param equalPos the index of the assignment
+ *
+ * @return true if to left of assignment is href
+ */
+ private boolean isHrefAttributeAssignment(final String tag, final int
equalPos) {
+ return tag.charAt(equalPos) == '=' && equalPos > 4 && "
href".equals(tag.substring(equalPos - 5, equalPos));
+ }
+}
=======================================
--- /dev/null
+++
/piccolo2d.java/branches/phtml/core/src/test/java/edu/umd/cs/piccolo/nodes/PHtmlViewTest.java
Tue Aug 4 06:56:20 2009
@@ -0,0 +1,128 @@
+package edu.umd.cs.piccolo.nodes;
+
+import java.awt.Color;
+import java.awt.Font;
+
+import junit.framework.TestCase;
+import edu.umd.cs.piccolo.MockPropertyChangeListener;
+import edu.umd.cs.piccolo.util.PBounds;
+
+public class PHtmlViewTest extends TestCase {
+
+ private MockPropertyChangeListener mockListener;
+
+ public void setUp() {
+ mockListener = new MockPropertyChangeListener();
+ }
+
+ public void testGetClickedAddressReturnsSingleQuotedAddress() {
+ PHtmlView html = new PHtmlView("<a
href='http://www.testing.com'>testing</a>");
+ html.setBounds(new PBounds(0, 0, 100, 100));
+ assertEquals("http://www.testing.com",
html.getClickedAddress(5,5));
+ }
+
+ public void testGetClickedAddressReturnsDoubleQuotedAddress() {
+ PHtmlView html = new PHtmlView("<a
href=\"http://www.testing.com\">testing</a>");
+ html.setBounds(new PBounds(0, 0, 100, 100));
+ assertEquals("http://www.testing.com",
html.getClickedAddress(5,5));
+ }
+
+ public void testBracketsAreValidInHrefs() {
+ PHtmlView html = new PHtmlView("<a href='a>b'>testing</a>");
+ html.setBounds(new PBounds(0, 0, 100, 100));
+ assertEquals("a>b", html.getClickedAddress(5,5));
+ }
+
+ public void testGetClickedAddressReturnsNullWhenInvalid() {
+ PHtmlView html = new PHtmlView("<a ='a>b'>testing</a>");
+ html.setBounds(new PBounds(0, 0, 100, 100));
+ assertNull(html.getClickedAddress(5,5));
+ }
+
+ public void testGetClickedAddressReturnsHrefWhenMissingEndAnchorTag() {
+ PHtmlView html = new PHtmlView("<a href='testing.com'>testing");
+ html.setBounds(new PBounds(0, 0, 100, 100));
+ assertEquals("testing.com", html.getClickedAddress(5,5));
+ }
+
+ public void testHandlesTricksyTitles() {
+ PHtmlView html = new PHtmlView("<a href=\"where to go\"
title=\"this is not the href='gotcha!' \">testing</a>");
+ html.setBounds(new PBounds(0, 0, 100, 100));
+ assertEquals("where to go", html.getClickedAddress(5,5));
+ }
+
+ public void testHandlesHrefWithoutQuotes() {
+ PHtmlView html = new PHtmlView("<a href=testing.com>testing</a>");
+ html.setBounds(new PBounds(0, 0, 100, 100));
+ assertEquals("testing.com", html.getClickedAddress(5,5));
+ }
+
+ public void testUnclosedTagsCauseIgnoreOfTag() {
+ PHtmlView html = new PHtmlView("<a href='testing.com' ");
+ html.setBounds(new PBounds(0, 0, 100, 100));
+ assertNull(html.getClickedAddress(5,5));
+ }
+
+ public void testMissingEndTagCausesRemainderOfHtmlToBeLinkTarget() {
+ PHtmlView html = new PHtmlView("<a href='testing.com'>Missing End
TAg ");
+ html.setBounds(new PBounds(0, 0, 100, 100));
+ assertEquals("testing.com", html.getClickedAddress(5,5));
+ }
+
+ public void testUnclosedQuotesCauseIgnoreOfLink() {
+ PHtmlView html = new PHtmlView("<a href='testing.com>testing");
+ html.setBounds(new PBounds(0, 0, 100, 100));
+ assertNull(html.getClickedAddress(5,5));
+ }
+
+ public void testEmptyAddressReturnsEmptyString() {
+ PHtmlView html = new PHtmlView("<a href=''>testing");
+ html.setBounds(new PBounds(0, 0, 100, 100));
+ assertEquals("", html.getClickedAddress(5,5));
+ }
+
+ public void testReturnsNullWhenClickOutsideLink() {
+ PHtmlView html = new PHtmlView("0123456789 <a href=#>testing</a>");
+ html.setBounds(new PBounds(0, 0, 100, 100));
+ assertNull(html.getClickedAddress(5,5));
+ }
+
+ public void testSetHtmlColorPersists() {
+ PHtmlView html = new PHtmlView();
+ html.setHtmlColor(Color.RED);
+ assertEquals(Color.RED, html.getHtmlColor());
+ }
+
+ public void testFontIsNotNullByDefault() {
+ PHtmlView html = new PHtmlView();
+ assertNotNull(html.getFont());
+ }
+
+ public void testHtmlColorIsNotNullByDefault() {
+ PHtmlView html = new PHtmlView();
+ assertNotNull(html.getHtmlColor());
+ }
+
+ public void testSetHtmlFiresEventOnChangeOnly() {
+ PHtmlView html = new PHtmlView();
+ html.addPropertyChangeListener(mockListener);
+ html.setHtml("testing");
+ assertEquals(1, mockListener.getPropertyChangeCount());
+ assertEquals(PHtmlView.PROPERTY_HTML,
mockListener.getPropertyChange(0).getPropertyName());
+ html.setHtml("testing");
+ assertEquals(1, mockListener.getPropertyChangeCount());
+ }
+
+ public void testSetHtmlToNullIsAllowed() {
+ PHtmlView html = new PHtmlView();
+ html.setHtml(null);
+ assertNull(html.getHtml());
+ }
+
+ public void testSetFontPerists() {
+ PHtmlView html = new PHtmlView();
+ Font font = Font.getFont("arial");
+ html.setFont(font);
+ assertSame(font, html.getFont());
+ }
+}
=======================================
---
/piccolo2d.java/branches/phtml/core/src/main/java/edu/umd/cs/piccolo/nodes/PHtml.java
Wed Jul 29 10:49:31 2009
+++ /dev/null
@@ -1,379 +0,0 @@
-/*
- * Copyright (c) 1998-2008, University of Maryland
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
modification, are permitted provided
- * that the following conditions are met:
- *
- * Redistributions of source code must retain the above copyright notice,
this list of conditions
- * and the following disclaimer.
- *
- * Redistributions in binary form must reproduce the above copyright
notice, this list of conditions
- * and the following disclaimer in the documentation and/or other
materials provided with the
- * distribution.
- *
- * None of the name of the University of Maryland, the name of the
Piccolo2D project, or the names of its
- * contributors may be used to endorse or promote products derived from
this software without specific
- * prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A
- * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR
- * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT
- * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR
- * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
USE OF THIS SOFTWARE, EVEN IF
- * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package edu.umd.cs.piccolo.nodes;
-
-import java.awt.Color;
-import java.awt.Font;
-import java.awt.Graphics2D;
-import java.awt.Rectangle;
-import java.awt.geom.Point2D;
-import java.awt.geom.Rectangle2D;
-
-import javax.swing.JLabel;
-import javax.swing.JTextField;
-import javax.swing.plaf.basic.BasicHTML;
-import javax.swing.text.Position;
-import javax.swing.text.View;
-
-import edu.umd.cs.piccolo.PNode;
-import edu.umd.cs.piccolo.util.PPaintContext;
-
-/**
- * PHtml is a Piccolo node for rendering HTML text. It uses a JLabel under
the
- * hood so you have the same restrictions regarding html as you have when
using
- * standard Swing components (HTML 3.2 + subset of CSS 1.0).
- *
- * @author Chris Malley ([email protected])
- * @author Sam Reid
- * @author Allain Lalonde
- */
-public class PHtml extends PNode {
-
- private static final long serialVersionUID = 1L;
-
- private static final Font DEFAULT_FONT = new JTextField().getFont();
- private static final Color DEFAULT_HTML_COLOR = Color.BLACK;
-
- /**
- * The property name that identifies a change of this node's font (see
- * {...@link #getFont getFont}). Both old and new value will be set in any
- * property change event.
- */
- public static final String PROPERTY_FONT = "font"; //
- public static final int PROPERTY_CODE_FONT = 1 << 20;
-
- /**
- * The property name that identifies a change of this node's html (see
- * {...@link #getHTML getHTML}). Both old and new value will be set in any
- * property change event.
- */
- public static final String PROPERTY_HTML = "html";
- public static final int PROPERTY_CODE_HTML = 1 << 21;
-
- /**
- * The property name that identifies a change of this node's html
color (see
- * {...@link #getHtml getHTMLColor}). Both old and new value will be set
in any
- * property change event.
- */
- public static final String PROPERTY_HTML_COLOR = "html color";
- public static final int PROPERTY_CODE_HTML_COLOR = 1 << 22;
-
- private final JLabel htmlLabel;
- private View htmlView;
- private final Rectangle htmlBounds;
-
- public PHtml() {
- this(null, DEFAULT_FONT, DEFAULT_HTML_COLOR);
- }
-
- public PHtml(final String html) {
- this(html, DEFAULT_FONT, DEFAULT_HTML_COLOR);
- }
-
- public PHtml(final String html, final Color htmlColor) {
- this(html, DEFAULT_FONT, htmlColor);
- }
-
- public PHtml(final String html, final Font font, final Color
htmlColor) {
- htmlLabel = new JLabel(html);
- htmlLabel.setFont(font);
- htmlLabel.setForeground(htmlColor);
- htmlBounds = new Rectangle();
- update();
- }
-
- /**
- * @return HTML being rendered by this node
- */
- public String getHtml() {
- return htmlLabel.getText();
- }
-
- /**
- * Changes the HTML being rendered by this node
- *
- * @param newHtml
- */
- public void setHtml(final String newHtml) {
- if (isNewHtml(newHtml)) {
- final String oldHtml = htmlLabel.getText();
- htmlLabel.setText(newHtml);
- update();
- firePropertyChange(PROPERTY_CODE_HTML, PROPERTY_HTML, oldHtml,
newHtml);
- }
- }
-
- private boolean isNewHtml(final String html) {
- if (html == null && getHtml() == null) {
- return false;
- } else if (html == null || getHtml() == null) {
- return true;
- } else {
- return !htmlLabel.getText().equals(html);
- }
- }
-
- /**
- * Gets the font.
- *
- * @return the font
- */
- public Font getFont() {
- return htmlLabel.getFont();
- }
-
- /**
- * Set the font of this PHtml.
- */
- public void setFont(final Font newFont) {
- final Font oldFont = htmlLabel.getFont();
- htmlLabel.setFont(newFont);
- update();
-
- firePropertyChange(PROPERTY_CODE_FONT, PROPERTY_FONT, oldFont,
newFont);
- }
-
- /**
- * Gets the color used to render the HTML. If you want to get the
paint used
- * for the node, use getPaint.
- *
- * @return the color used to render the HTML.
- */
- public Color getHtmlColor() {
- return htmlLabel.getForeground();
- }
-
- /**
- * Sets the color used to render the HTML. If you want to set the
paint used
- * for the node, use setPaint.
- *
- * @param newColor
- */
- public void setHtmlColor(final Color newColor) {
- final Color oldColor = htmlLabel.getForeground();
- htmlLabel.setForeground(newColor);
- repaint();
- firePropertyChange(PROPERTY_CODE_HTML_COLOR, PROPERTY_HTML_COLOR,
oldColor, newColor);
- }
-
- /**
- * Applies all properties to the underlying JLabel, creates an
htmlView and
- * updates bounds
- */
- private void update() {
- htmlLabel.setSize(htmlLabel.getPreferredSize());
- htmlView = BasicHTML.createHTMLView(htmlLabel, htmlLabel.getText()
== null ? "" : htmlLabel.getText());
-
- final Rectangle2D bounds = getBounds();
- htmlBounds.setRect(0, 0, bounds.getWidth(), bounds.getHeight());
- repaint();
- }
-
- public boolean setBounds(final double x, final double y, final double
width, final double height) {
- final boolean boundsChanged = super.setBounds(x, y, width, height);
- update();
- return boundsChanged;
- }
-
- public boolean setBounds(final Rectangle2D newBounds) {
- final boolean boundsChanged = super.setBounds(newBounds);
- update();
- return boundsChanged;
- }
-
- /**
- * Paints the node. The HTML string is painted last, so it appears on
top of
- * any child nodes.
- *
- * @param paintContext
- */
- protected void paint(final PPaintContext paintContext) {
- super.paint(paintContext);
-
- if (htmlLabel.getWidth() != 0 && htmlLabel.getHeight() != 0) {
- final Graphics2D g2 = paintContext.getGraphics();
-
- htmlView.paint(g2, htmlBounds);
- }
- }
-
- /**
- * Returns the address specified in the link under the given point.
- *
- * @param clickedPoint
- * @return String containing value of href for clicked link, or null
if no
- * link clicked
- */
- public String getClickedAddress(Point2D.Double clickedPoint) {
- return getClickedAddress(clickedPoint.getX(), clickedPoint.getY());
- }
-
- /**
- * Returns the address specified in the link under the given point.
- *
- * @param clickedPoint
- * @return String containing value of href for clicked link, or null
if no
- * link clicked
- */
- public String getClickedAddress(final double x, final double y) {
- int position = pointToModelIndex(x, y);
-
- final String html = htmlLabel.getText();
-
- String address = null;
-
- int currentPos = 0;
- while (currentPos < html.length()) {
- currentPos = html.indexOf('<', currentPos);
- if (currentPos == -1 || position < currentPos) {
- break;
- }
-
- final int tagStart = currentPos;
- final int tagEnd = findTagEnd(html, currentPos);
-
- if (tagEnd == -1) {
- return null;
- }
-
- currentPos = tagEnd + 1;
-
- final String tag = html.substring(tagStart, currentPos);
-
- position += tag.length();
-
- if ("</a>".equals(tag)) {
- address = null;
- }
- else if (tag.startsWith("<a ")) {
- address = extractHref(tag);
- }
- }
-
- return address;
- }
-
- private int pointToModelIndex(final double x, final double y) {
- final Position.Bias[] biasReturn = new Position.Bias[1];
- return htmlView.viewToModel((float) x, (float) y, getBounds(),
biasReturn);
- }
-
- /**
- * Starting from the startPos, it finds the position at which the
given tag
- * ends.
- *
- * Returns -1 if the end of the string was encountered before the end
of the
- * tag was encountered.
- *
- * @param html
- * @param startPos
- * @return
- */
- private int findTagEnd(final String html, final int startPos) {
- int currentPos = startPos;
-
- currentPos++;
-
- while (currentPos > 0 && currentPos < html.length() &&
html.charAt(currentPos) != '>') {
- if (html.charAt(currentPos) == '\"') {
- currentPos = html.indexOf('\"', currentPos + 1);
- }
- else if (html.charAt(currentPos) == '\'') {
- currentPos = html.indexOf('\'', currentPos + 1);
- }
- currentPos++;
- }
-
- return currentPos == 0 || currentPos >= html.length() ? -1 :
currentPos + 1;
- }
-
- /**
- * Given a tag, extracts the value of the href attribute, returns null
if
- * none was found
- *
- * @param tag
- * @return
- */
- private String extractHref(final String tag) {
- int currentPos = 0;
-
- final String href = null;
-
- while (currentPos >= 0 && currentPos < tag.length() - 1) {
- currentPos = tag.indexOf('=', currentPos + 1);
- if (currentPos != -1 && isHrefAttributeAssignment(tag,
currentPos)) {
- return extractHrefValue(tag, currentPos + 1);
- }
- }
- return href;
- }
-
- /**
- * Starting at the character after the equal sign of an href=...,
- * it extract the value. Handles single, double, and no quotes.
- *
- * @param tag
- * @param startPos
- * @return value of href or null if not found.
- */
- private String extractHrefValue(final String tag, final int startPos) {
- int currentPos = startPos;
-
- if (tag.charAt(currentPos) == '\"') {
- final int startHref = currentPos + 1;
- currentPos = tag.indexOf('\"', startHref);
- return currentPos == -1 ? null : tag.substring(startHref,
currentPos);
- }
- else if (currentPos < tag.length() && tag.charAt(currentPos)
== '\'') {
- final int startHref = currentPos + 1;
- currentPos = tag.indexOf('\'', startHref);
- return currentPos == -1 ? null : tag.substring(startHref,
currentPos);
- }
- else {
- final int startHref = currentPos;
-
- if (currentPos < tag.length()) {
- do {
- currentPos++;
- } while (currentPos < tag.length() &&
tag.charAt(currentPos) != ' ' && tag.charAt(currentPos) != '>');
- }
- return tag.substring(startHref, currentPos);
- }
- }
-
- /**
- * Given the position in a string returns whether it points to the
equal sign of an href attribute
- *
- * @param tag
- * @param equalPos
- * @return
- */
- private boolean isHrefAttributeAssignment(final String tag, final int
equalPos) {
- return tag.charAt(equalPos) == '=' && equalPos > 4 && "
href".equals(tag.substring(equalPos - 5, equalPos));
- }
-}
=======================================
---
/piccolo2d.java/branches/phtml/core/src/test/java/edu/umd/cs/piccolo/nodes/PHtmlTest.java
Wed Jul 29 10:49:31 2009
+++ /dev/null
@@ -1,128 +0,0 @@
-package edu.umd.cs.piccolo.nodes;
-
-import java.awt.Color;
-import java.awt.Font;
-
-import junit.framework.TestCase;
-import edu.umd.cs.piccolo.MockPropertyChangeListener;
-import edu.umd.cs.piccolo.util.PBounds;
-
-public class PHtmlTest extends TestCase {
-
- private MockPropertyChangeListener mockListener;
-
- public void setUp() {
- mockListener = new MockPropertyChangeListener();
- }
-
- public void testGetClickedAddressReturnsSingleQuotedAddress() {
- PHtml html = new PHtml("<a
href='http://www.testing.com'>testing</a>");
- html.setBounds(new PBounds(0, 0, 100, 100));
- assertEquals("http://www.testing.com",
html.getClickedAddress(5,5));
- }
-
- public void testGetClickedAddressReturnsDoubleQuotedAddress() {
- PHtml html = new PHtml("<a
href=\"http://www.testing.com\">testing</a>");
- html.setBounds(new PBounds(0, 0, 100, 100));
- assertEquals("http://www.testing.com",
html.getClickedAddress(5,5));
- }
-
- public void testBracketsAreValidInHrefs() {
- PHtml html = new PHtml("<a href='a>b'>testing</a>");
- html.setBounds(new PBounds(0, 0, 100, 100));
- assertEquals("a>b", html.getClickedAddress(5,5));
- }
-
- public void testGetClickedAddressReturnsNullWhenInvalid() {
- PHtml html = new PHtml("<a ='a>b'>testing</a>");
- html.setBounds(new PBounds(0, 0, 100, 100));
- assertNull(html.getClickedAddress(5,5));
- }
-
- public void testGetClickedAddressReturnsHrefWhenMissingEndAnchorTag() {
- PHtml html = new PHtml("<a href='testing.com'>testing");
- html.setBounds(new PBounds(0, 0, 100, 100));
- assertEquals("testing.com", html.getClickedAddress(5,5));
- }
-
- public void testHandlesTricksyTitles() {
- PHtml html = new PHtml("<a href=\"where to go\" title=\"this is
not the href='gotcha!' \">testing</a>");
- html.setBounds(new PBounds(0, 0, 100, 100));
- assertEquals("where to go", html.getClickedAddress(5,5));
- }
-
- public void testHandlesHrefWithoutQuotes() {
- PHtml html = new PHtml("<a href=testing.com>testing</a>");
- html.setBounds(new PBounds(0, 0, 100, 100));
- assertEquals("testing.com", html.getClickedAddress(5,5));
- }
-
- public void testUnclosedTagsCauseIgnoreOfTag() {
- PHtml html = new PHtml("<a href='testing.com' ");
- html.setBounds(new PBounds(0, 0, 100, 100));
- assertNull(html.getClickedAddress(5,5));
- }
-
- public void testMissingEndTagCausesRemainderOfHtmlToBeLinkTarget() {
- PHtml html = new PHtml("<a href='testing.com'>Missing End TAg ");
- html.setBounds(new PBounds(0, 0, 100, 100));
- assertEquals("testing.com", html.getClickedAddress(5,5));
- }
-
- public void testUnclosedQuotesCauseIgnoreOfLink() {
- PHtml html = new PHtml("<a href='testing.com>testing");
- html.setBounds(new PBounds(0, 0, 100, 100));
- assertNull(html.getClickedAddress(5,5));
- }
-
- public void testEmptyAddressReturnsEmptyString() {
- PHtml html = new PHtml("<a href=''>testing");
- html.setBounds(new PBounds(0, 0, 100, 100));
- assertEquals("", html.getClickedAddress(5,5));
- }
-
- public void testReturnsNullWhenClickOutsideLink() {
- PHtml html = new PHtml("0123456789 <a href=#>testing</a>");
- html.setBounds(new PBounds(0, 0, 100, 100));
- assertNull(html.getClickedAddress(5,5));
- }
-
- public void testSetHtmlColorPersists() {
- PHtml html = new PHtml();
- html.setHtmlColor(Color.RED);
- assertEquals(Color.RED, html.getHtmlColor());
- }
-
- public void testFontIsNotNullByDefault() {
- PHtml html = new PHtml();
- assertNotNull(html.getFont());
- }
-
- public void testHtmlColorIsNotNullByDefault() {
- PHtml html = new PHtml();
- assertNotNull(html.getHtmlColor());
- }
-
- public void testSetHtmlFiresEventOnChangeOnly() {
- PHtml html = new PHtml();
- html.addPropertyChangeListener(mockListener);
- html.setHtml("testing");
- assertEquals(1, mockListener.getPropertyChangeCount());
- assertEquals(PHtml.PROPERTY_HTML,
mockListener.getPropertyChange(0).getPropertyName());
- html.setHtml("testing");
- assertEquals(1, mockListener.getPropertyChangeCount());
- }
-
- public void testSetHtmlToNullIsAllowed() {
- PHtml html = new PHtml();
- html.setHtml(null);
- assertNull(html.getHtml());
- }
-
- public void testSetFontPerists() {
- PHtml html = new PHtml();
- Font font = Font.getFont("arial");
- html.setFont(font);
- assertSame(font, html.getFont());
- }
-}
--~--~---------~--~----~------------~-------~--~----~
Piccolo2D Developers Group: http://groups.google.com/group/piccolo2d-dev?hl=en
-~----------~----~----~----~------~----~------~--~---