Revision: 620
Author: allain.lalonde
Date: Wed Jul 29 10:49:31 2009
Log: I am not happy with the code for mapping points to link addresses, but  
it passes every test I can throw at it.

I'll keep looking for something simpler, but in the end it'll be  
functionally equivalent to what's there now.
http://code.google.com/p/piccolo2d/source/detail?r=620

Modified:
   
/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

=======================================
---  
/piccolo2d.java/branches/phtml/core/src/main/java/edu/umd/cs/piccolo/nodes/PHtml.java
    
Tue Jul 28 21:35:50 2009
+++  
/piccolo2d.java/branches/phtml/core/src/main/java/edu/umd/cs/piccolo/nodes/PHtml.java
    
Wed Jul 29 10:49:31 2009
@@ -33,8 +33,6 @@
  import java.awt.Rectangle;
  import java.awt.geom.Point2D;
  import java.awt.geom.Rectangle2D;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;

  import javax.swing.JLabel;
  import javax.swing.JTextField;
@@ -50,9 +48,6 @@
   * 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).
   *
- * FIXME: so it's not necessary well-formed? Atts like <a href=unquoted />  
are
- * ok?
- *
   * @author Chris Malley (cmal...@pixelzoom.com)
   * @author Sam Reid
   * @author Allain Lalonde
@@ -68,29 +63,24 @@
       * 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.
-     *
-     * FIXME what's that?
       */
-    public static final String PROPERTY_FONT = "font";
-    // FIXME what's that?
+    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. FIXME what's that?
+     * property change event.
       */
      public static final String PROPERTY_HTML = "html";
-    // FIXME what's that?
      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. FIXME what's that?
+     * property change event.
       */
      public static final String PROPERTY_HTML_COLOR = "html color";
-    // FIXME what's that?
      public static final int PROPERTY_CODE_HTML_COLOR = 1 << 22;

      private final JLabel htmlLabel;
@@ -118,22 +108,18 @@
      }

      /**
-     * FIXME better name innerHtml as w3c does?
-     *
       * @return HTML being rendered by this node
       */
-    public String getHTML() {
+    public String getHtml() {
          return htmlLabel.getText();
      }

      /**
       * Changes the HTML being rendered by this node
       *
-     * FIXME better name innerHtml as w3c does?
-     *
       * @param newHtml
       */
-    public void setHTML(final String newHtml) {
+    public void setHtml(final String newHtml) {
          if (isNewHtml(newHtml)) {
              final String oldHtml = htmlLabel.getText();
              htmlLabel.setText(newHtml);
@@ -143,9 +129,13 @@
      }

      private boolean isNewHtml(final String html) {
-        // FIXME NPE if both are null - can this happen?
-        return htmlLabel.getText() != null && html == null ||  
htmlLabel.getText() == null && html != null
-                || !htmlLabel.getText().equals(html);
+        if (html == null && getHtml() == null) {
+            return false;
+        } else if (html == null || getHtml() == null) {
+            return true;
+        } else {
+            return !htmlLabel.getText().equals(html);
+        }
      }

      /**
@@ -172,11 +162,9 @@
       * Gets the color used to render the HTML. If you want to get the  
paint used
       * for the node, use getPaint.
       *
-     * FIXME be more consistent - either always HTML or Html but don't mix.
-     *
       * @return the color used to render the HTML.
       */
-    public Color getHTMLColor() {
+    public Color getHtmlColor() {
          return htmlLabel.getForeground();
      }

@@ -186,7 +174,7 @@
       *
       * @param newColor
       */
-    public void setHTMLColor(final Color newColor) {
+    public void setHtmlColor(final Color newColor) {
          final Color oldColor = htmlLabel.getForeground();
          htmlLabel.setForeground(newColor);
          repaint();
@@ -233,121 +221,159 @@
              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.
-     *
-     * FIXME this method looks shaky - can you refactor to get it into a  
test
-     * harness?
+     * 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 clickedPoint) {
-        int position = pointToModelIndex(clickedPoint);
-
-        String html = htmlLabel.getText();
+    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()) {
-            if (html.charAt(currentPos) != '<') {
-                currentPos++;
-            }
-            else if (position < currentPos) {
+            currentPos = html.indexOf('<', currentPos);
+            if (currentPos == -1 || position < currentPos) {
                  break;
              }
-            else {
-                int tagStart = currentPos;
-                int tagEnd = findTagEnd(html, currentPos);
-
-                currentPos = tagEnd + 1;
-
-                String tag = html.substring(tagStart, tagEnd);
-
-                position += tag.length();
-
-                if ("</a>".equals(tag)) {
-                    address = null;
-                }
-                else if (tag.startsWith("<a ")) {
-                    address = extractHref(tag);
-                }
+
+            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 String extractHref(String tag) {
-        int currentPos = 0;
-
-        String href = null;
-        int tagLength = tag.length();
-
-        while (currentPos < tag.length() - 1) {
-            currentPos++;
-            if (tag.charAt(currentPos) != '=') {
-                currentPos++;
-            }
-            else if (currentPos > 4 && "  
href".equals(tag.substring(currentPos - 5, currentPos))) {
-                currentPos++;
-                if (tag.charAt(currentPos) == '\"') {
-                    int startHref = currentPos + 1;
-                    if (currentPos < tagLength) {
-                        do {
-                            ++currentPos;
-                        } while (currentPos < tagLength &&  
tag.charAt(currentPos) != '\"');
-                    }
-                    return tag.substring(startHref, currentPos);
-                }
-                else if (currentPos < tagLength && tag.charAt(currentPos)  
== '\'') {
-                    int startHref = currentPos + 1;
-                    if (currentPos < tagLength) {
-                        do {
-                            ++currentPos;
-                        } while (currentPos < tagLength &&  
tag.charAt(currentPos) != '\'');
-                    }
-                    return tag.substring(startHref, currentPos);
-                }
-                else {
-                    int startHref = currentPos;
-
-                    if (currentPos < tagLength) {
-                        do {
-                            currentPos++;
-                        } while (currentPos < tagLength &&  
tag.charAt(currentPos) != ' '
-                                && tag.charAt(currentPos) != '>');
-                    }
-                    return tag.substring(startHref, currentPos);
-                }
+    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;
      }

-    private int findTagEnd(String html, final int startPos) {
+    /**
+     * 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;

-        while (html.charAt(currentPos) != '>') {
-            if (html.charAt(currentPos) == '\"') {
-                while (html.charAt(++currentPos) != '\"')
-                    ;
-            }
-            else if (html.charAt(currentPos) == '\'') {
-                while (html.charAt(++currentPos) != '\'')
-                    ;
-            }
-            currentPos++;
-        }
-
-        return currentPos + 1;
+        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);
+        }
      }

-    private int pointToModelIndex(final Point2D clickedPoint) {
-        final Position.Bias[] biasReturn = new Position.Bias[1];
-        return htmlView.viewToModel((float) clickedPoint.getX(), (float)  
clickedPoint.getY(), getBounds(), biasReturn);
+    /**
+     * 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
        
Tue Jul 28 21:35:50 2009
+++  
/piccolo2d.java/branches/phtml/core/src/test/java/edu/umd/cs/piccolo/nodes/PHtmlTest.java
        
Wed Jul 29 10:49:31 2009
@@ -1,52 +1,128 @@
  package edu.umd.cs.piccolo.nodes;

-import java.awt.geom.Point2D;
+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(new  
Point2D.Double(5,5)));
+        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(new  
Point2D.Double(5,5)));
+        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(new  
Point2D.Double(5,5)));
+        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(new Point2D.Double(5,5)));
+        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(new  
Point2D.Double(5,5)));
+        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(new  
Point2D.Double(5,5)));
+        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(new  
Point2D.Double(5,5)));
+        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
-~----------~----~----~----~------~----~------~--~---

Reply via email to