diff --git a/core/src/main/java/edu/umd/cs/piccolo/nodes/PText.java b/core/src/main/java/edu/umd/cs/piccolo/nodes/PText.java index a0671d6..37070c7 100644 --- a/core/src/main/java/edu/umd/cs/piccolo/nodes/PText.java +++ b/core/src/main/java/edu/umd/cs/piccolo/nodes/PText.java @@ -52,303 +52,313 @@ */ public class PText extends PNode { - /** - * The property name that identifies a change of this node's text (see - * {@link #getText getText}). Both old and new value will be set in any - * property change event. - */ - public static final String PROPERTY_TEXT = "text"; - public static final int PROPERTY_CODE_TEXT = 1 << 19; + /** + * The property name that identifies a change of this node's text (see + * {@link #getText getText}). Both old and new value will be set in any + * property change event. + */ + public static final String PROPERTY_TEXT = "text"; + public static final int PROPERTY_CODE_TEXT = 1 << 19; - /** - * 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 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; - public static Font DEFAULT_FONT = new Font("Helvetica", Font.PLAIN, 12); - public static double DEFAULT_GREEK_THRESHOLD = 5.5; + public static Font DEFAULT_FONT = new Font("Helvetica", Font.PLAIN, 12); + public static double DEFAULT_GREEK_THRESHOLD = 5.5; - private String text; - private Paint textPaint; - private Font font; - protected double greekThreshold = DEFAULT_GREEK_THRESHOLD; - private float justification = javax.swing.JLabel.LEFT_ALIGNMENT; - private boolean constrainHeightToTextHeight = true; - private boolean constrainWidthToTextWidth = true; - private transient TextLayout[] lines; + private String text; + private Paint textPaint; + private Font font; + protected double greekThreshold = DEFAULT_GREEK_THRESHOLD; + private float justification = javax.swing.JLabel.LEFT_ALIGNMENT; + private boolean constrainHeightToTextHeight = true; + private boolean constrainWidthToTextWidth = true; + private transient TextLayout[] lines; - public PText() { - super(); - setTextPaint(Color.BLACK); - text = ""; - } + public PText() { + super(); + setTextPaint(Color.BLACK); + text = ""; + } - public PText(String aText) { - this(); - setText(aText); - } + public PText(String aText) { + this(); + setText(aText); + } - /** - * Return the justificaiton of the text in the bounds. - * - * @return float - */ - public float getJustification() { - return justification; - } + /** + * Return the justificaiton of the text in the bounds. + * + * @return float + */ + public float getJustification() { + return justification; + } - /** - * Sets the justificaiton of the text in the bounds. - * - * @param just - */ - public void setJustification(float just) { - justification = just; - recomputeLayout(); - } + /** + * Sets the justificaiton of the text in the bounds. + * + * @param just + */ + public void setJustification(float just) { + justification = just; + recomputeLayout(); + } - /** - * Get the paint used to paint this nodes text. - * - * @return Paint - */ - public Paint getTextPaint() { - return textPaint; - } + /** + * Get the paint used to paint this nodes text. + * + * @return Paint + */ + public Paint getTextPaint() { + return textPaint; + } - /** - * Set the paint used to paint this node's text background. - * - * @param textPaint - */ - public void setTextPaint(Paint textPaint) { - this.textPaint = textPaint; - invalidatePaint(); - } + /** + * Set the paint used to paint this node's text background. + * + * @param textPaint + */ + public void setTextPaint(Paint textPaint) { + this.textPaint = textPaint; + invalidatePaint(); + } - public boolean isConstrainWidthToTextWidth() { - return constrainWidthToTextWidth; - } + public boolean isConstrainWidthToTextWidth() { + return constrainWidthToTextWidth; + } - /** - * Controls whether this node changes its width to fit the width of its - * text. If flag is true it does; if flag is false it doesn't - */ - public void setConstrainWidthToTextWidth(boolean constrainWidthToTextWidth) { - this.constrainWidthToTextWidth = constrainWidthToTextWidth; - recomputeLayout(); - } + /** + * Controls whether this node changes its width to fit the width of its + * text. If flag is true it does; if flag is false it doesn't + */ + public void setConstrainWidthToTextWidth(boolean constrainWidthToTextWidth) { + this.constrainWidthToTextWidth = constrainWidthToTextWidth; + recomputeLayout(); + } - public boolean isConstrainHeightToTextHeight() { - return constrainHeightToTextHeight; - } + public boolean isConstrainHeightToTextHeight() { + return constrainHeightToTextHeight; + } - /** - * Controls whether this node changes its height to fit the height of its - * text. If flag is true it does; if flag is false it doesn't - */ - public void setConstrainHeightToTextHeight(boolean constrainHeightToTextHeight) { - this.constrainHeightToTextHeight = constrainHeightToTextHeight; - recomputeLayout(); - } + /** + * Controls whether this node changes its height to fit the height of its + * text. If flag is true it does; if flag is false it doesn't + */ + public void setConstrainHeightToTextHeight( + boolean constrainHeightToTextHeight) { + this.constrainHeightToTextHeight = constrainHeightToTextHeight; + recomputeLayout(); + } - /** - * Returns the current greek threshold. When the screen font size will be - * below this threshold the text is rendered as 'greek' instead of drawing - * the text glyphs. - */ - public double getGreekThreshold() { - return greekThreshold; - } + /** + * Returns the current greek threshold. When the screen font size will be + * below this threshold the text is rendered as 'greek' instead of drawing + * the text glyphs. + */ + public double getGreekThreshold() { + return greekThreshold; + } - /** - * Sets the current greek threshold. When the screen font size will be below - * this threshold the text is rendered as 'greek' instead of drawing the - * text glyphs. - * - * @param threshold minimum screen font size. - */ - public void setGreekThreshold(double threshold) { - greekThreshold = threshold; - invalidatePaint(); - } + /** + * Sets the current greek threshold. When the screen font size will be below + * this threshold the text is rendered as 'greek' instead of drawing the + * text glyphs. + * + * @param threshold + * minimum screen font size. + */ + public void setGreekThreshold(double threshold) { + greekThreshold = threshold; + invalidatePaint(); + } - public String getText() { - return text; - } + public String getText() { + return text; + } - /** - * Set the text for this node. The text will be broken up into multiple - * lines based on the size of the text and the bounds width of this node. - */ - public void setText(String aText) { - String old = text; - text = (aText == null) ? "" : aText; - lines = null; - recomputeLayout(); - invalidatePaint(); - firePropertyChange(PROPERTY_CODE_TEXT, PROPERTY_TEXT, old, text); - } + /** + * Set the text for this node. The text will be broken up into multiple + * lines based on the size of the text and the bounds width of this node. + */ + public void setText(String aText) { + String old = text; + text = (aText == null) ? "" : aText; + lines = null; + recomputeLayout(); + invalidatePaint(); + firePropertyChange(PROPERTY_CODE_TEXT, PROPERTY_TEXT, old, text); + } - /** - * Returns the font of this PText. - * - * @return the font of this PText. - */ - public Font getFont() { - if (font == null) { - font = DEFAULT_FONT; - } - return font; - } + /** + * Returns the font of this PText. + * + * @return the font of this PText. + */ + public Font getFont() { + if (font == null) { + font = DEFAULT_FONT; + } + return font; + } - /** - * Set the font of this PText. Note that in Piccolo if you want to change - * the size of a text object it's often a better idea to scale the PText - * node instead of changing the font size to get that same effect. Using - * very large font sizes can slow performance. - */ - public void setFont(Font aFont) { - Font old = font; - font = aFont; - lines = null; - recomputeLayout(); - invalidatePaint(); - firePropertyChange(PROPERTY_CODE_FONT, PROPERTY_FONT, old, font); - } + /** + * Set the font of this PText. Note that in Piccolo if you want to change + * the size of a text object it's often a better idea to scale the PText + * node instead of changing the font size to get that same effect. Using + * very large font sizes can slow performance. + */ + public void setFont(Font aFont) { + Font old = font; + font = aFont; + lines = null; + recomputeLayout(); + invalidatePaint(); + firePropertyChange(PROPERTY_CODE_FONT, PROPERTY_FONT, old, font); + } - private static final TextLayout[] EMPTY_TEXT_LAYOUT_ARRAY = new TextLayout[0]; + private static final TextLayout[] EMPTY_TEXT_LAYOUT_ARRAY = new TextLayout[0]; - /** - * Compute the bounds of the text wrapped by this node. The text layout is - * wrapped based on the bounds of this node. - */ - public void recomputeLayout() { - ArrayList linesList = new ArrayList(); - double textWidth = 0; - double textHeight = 0; + /** + * Compute the bounds of the text wrapped by this node. The text layout is + * wrapped based on the bounds of this node. + */ + public void recomputeLayout() { + ArrayList linesList = new ArrayList(); + double textWidth = 0; + double textHeight = 0; - if (text != null && text.length() > 0) { - AttributedString atString = new AttributedString(text); - atString.addAttribute(TextAttribute.FONT, getFont()); - AttributedCharacterIterator itr = atString.getIterator(); - LineBreakMeasurer measurer = new LineBreakMeasurer(itr, PPaintContext.RENDER_QUALITY_HIGH_FRC); - float availableWidth = constrainWidthToTextWidth ? Float.MAX_VALUE : (float) getWidth(); + if (text != null && text.length() > 0) { + AttributedString atString = new AttributedString(text); + atString.addAttribute(TextAttribute.FONT, getFont()); + AttributedCharacterIterator itr = atString.getIterator(); + LineBreakMeasurer measurer = new LineBreakMeasurer(itr, + PPaintContext.RENDER_QUALITY_HIGH_FRC); + float availableWidth = constrainWidthToTextWidth ? Float.MAX_VALUE + : (float) getWidth(); - int nextLineBreakOffset = text.indexOf('\n'); - if (nextLineBreakOffset == -1) { - nextLineBreakOffset = Integer.MAX_VALUE; - } - else { - nextLineBreakOffset++; - } + int nextLineBreakOffset = text.indexOf('\n'); + if (nextLineBreakOffset == -1) { + nextLineBreakOffset = Integer.MAX_VALUE; + } else { + nextLineBreakOffset++; + } - while (measurer.getPosition() < itr.getEndIndex()) { - TextLayout aTextLayout = computeNextLayout(measurer, availableWidth, nextLineBreakOffset); + while (measurer.getPosition() < itr.getEndIndex()) { + TextLayout aTextLayout = computeNextLayout(measurer, + availableWidth, nextLineBreakOffset); - if (nextLineBreakOffset == measurer.getPosition()) { - nextLineBreakOffset = text.indexOf('\n', measurer.getPosition()); - if (nextLineBreakOffset == -1) { - nextLineBreakOffset = Integer.MAX_VALUE; - } - else { - nextLineBreakOffset++; - } - } + if (nextLineBreakOffset == measurer.getPosition()) { + nextLineBreakOffset = text.indexOf('\n', measurer + .getPosition()); + if (nextLineBreakOffset == -1) { + nextLineBreakOffset = Integer.MAX_VALUE; + } else { + nextLineBreakOffset++; + } + } - linesList.add(aTextLayout); - textHeight += aTextLayout.getAscent(); - textHeight += aTextLayout.getDescent() + aTextLayout.getLeading(); - textWidth = Math.max(textWidth, aTextLayout.getAdvance()); - } - } + linesList.add(aTextLayout); + textHeight += aTextLayout.getAscent(); + textHeight += aTextLayout.getDescent() + + aTextLayout.getLeading(); + textWidth = Math.max(textWidth, aTextLayout.getAdvance()); + } + } - lines = (TextLayout[]) linesList.toArray(EMPTY_TEXT_LAYOUT_ARRAY); + lines = (TextLayout[]) linesList.toArray(EMPTY_TEXT_LAYOUT_ARRAY); - if (constrainWidthToTextWidth || constrainHeightToTextHeight) { - double newWidth = getWidth(); - double newHeight = getHeight(); + if (constrainWidthToTextWidth || constrainHeightToTextHeight) { + double newWidth = getWidth(); + double newHeight = getHeight(); - if (constrainWidthToTextWidth) { - newWidth = textWidth; - } + if (constrainWidthToTextWidth) { + newWidth = textWidth; + } - if (constrainHeightToTextHeight) { - newHeight = textHeight; - } + if (constrainHeightToTextHeight) { + newHeight = textHeight; + } - super.setBounds(getX(), getY(), newWidth, newHeight); - } - } + super.setBounds(getX(), getY(), newWidth, newHeight); + } + } - // provided in case someone needs to override the way that lines are - // wrapped. - protected TextLayout computeNextLayout(LineBreakMeasurer measurer, float availibleWidth, int nextLineBreakOffset) { - return measurer.nextLayout(availibleWidth, nextLineBreakOffset, false); - } + // provided in case someone needs to override the way that lines are + // wrapped. + protected TextLayout computeNextLayout(LineBreakMeasurer measurer, + float availibleWidth, int nextLineBreakOffset) { + return measurer.nextLayout(availibleWidth, nextLineBreakOffset, false); + } - protected void paint(PPaintContext paintContext) { - super.paint(paintContext); + protected void paint(PPaintContext paintContext) { + super.paint(paintContext); - float screenFontSize = getFont().getSize() * (float) paintContext.getScale(); - if (textPaint != null && screenFontSize > greekThreshold) { - float x = (float) getX(); - float y = (float) getY(); - float bottomY = (float) getHeight() + y; + float screenFontSize = getFont().getSize() + * (float) paintContext.getScale(); + if (textPaint == null || screenFontSize <= greekThreshold) + return; - Graphics2D g2 = paintContext.getGraphics(); + float x = (float) getX(); + float y = (float) getY(); + float bottomY = (float) getHeight() + y; - if (lines == null) { - recomputeLayout(); - repaint(); - return; - } + Graphics2D g2 = paintContext.getGraphics(); - g2.setPaint(textPaint); + if (lines == null) { + recomputeLayout(); + repaint(); + return; + } - for (int i = 0; i < lines.length; i++) { - TextLayout tl = lines[i]; - y += tl.getAscent(); + g2.setPaint(textPaint); - if (bottomY < y) { - return; - } + for (int i = 0; i < lines.length; i++) { + TextLayout tl = lines[i]; + y += tl.getAscent(); - float offset = (float) (getWidth() - tl.getAdvance()) * justification; - tl.draw(g2, x + offset, y); + if (bottomY < y) { + return; + } - y += tl.getDescent() + tl.getLeading(); - } - } - } + float offset = (float) (getWidth() - tl.getAdvance()) + * justification; + tl.draw(g2, x + offset, y); - protected void internalUpdateBounds(double x, double y, double width, double height) { - recomputeLayout(); - } + y += tl.getDescent() + tl.getLeading(); + } + } - // **************************************************************** - // Debugging - methods for debugging - // **************************************************************** + protected void internalUpdateBounds(double x, double y, double width, + double height) { + recomputeLayout(); + } - /** - * Returns a string representing the state of this node. This method is - * intended to be used only for debugging purposes, and the content and - * format of the returned string may vary between implementations. The - * returned string may be empty but may not be null. - * - * @return a string representation of this node's state - */ - protected String paramString() { - StringBuffer result = new StringBuffer(); + // **************************************************************** + // Debugging - methods for debugging + // **************************************************************** - result.append("text=" + (text == null ? "null" : text)); - result.append(",font=" + (font == null ? "null" : font.toString())); - result.append(','); - result.append(super.paramString()); + /** + * Returns a string representing the state of this node. This method is + * intended to be used only for debugging purposes, and the content and + * format of the returned string may vary between implementations. The + * returned string may be empty but may not be null. + * + * @return a string representation of this node's state + */ + protected String paramString() { + StringBuffer result = new StringBuffer(); - return result.toString(); - } + result.append("text=" + (text == null ? "null" : text)); + result.append(",font=" + (font == null ? "null" : font.toString())); + result.append(','); + result.append(super.paramString()); + + return result.toString(); + } } diff --git a/core/src/test/java/edu/umd/cs/piccolo/nodes/PTextTest.java b/core/src/test/java/edu/umd/cs/piccolo/nodes/PTextTest.java index c29f2e4..81fc8a4 100644 --- a/core/src/test/java/edu/umd/cs/piccolo/nodes/PTextTest.java +++ b/core/src/test/java/edu/umd/cs/piccolo/nodes/PTextTest.java @@ -28,18 +28,32 @@ */ package edu.umd.cs.piccolo.nodes; -import junit.framework.TestCase; +import java.awt.Color; +import java.awt.Font; +import java.beans.PropertyChangeListener; -import edu.umd.cs.piccolo.nodes.PText; +import javax.swing.JLabel; + +import edu.umd.cs.piccolo.MockPropertyChangeListener; + +import junit.framework.TestCase; public class PTextTest extends TestCase { - public PTextTest(String name) { + private PText textNode; + private MockPropertyChangeListener mockListener; + + public PTextTest(String name) { super(name); } + public void setUp() { + textNode = new PText(); + mockListener = new MockPropertyChangeListener(); + } + public void testClone() { - PText textNode = new PText("Boo"); + textNode.setText("Boo"); PText clonedNode = (PText) textNode.clone(); assertEquals("Boo", clonedNode.getText()); assertEquals(textNode.getFont(), clonedNode.getFont()); @@ -50,16 +64,14 @@ assertEquals("", textNode.getText()); } - public void testTextMayBeAssignedEmptyString() { - PText textNode = new PText("Before"); + public void testTextMayBeAssignedEmptyString() { textNode.setText(""); assertEquals("", textNode.getText()); } - public void testTextNullGetsInterpretedAsEmptyString() { - PText text = new PText("Before"); - text.setText(null); - assertEquals("", text.getText()); + public void testTextNullGetsInterpretedAsEmptyString() { + textNode.setText(null); + assertEquals("", textNode.getText()); } @@ -75,16 +87,78 @@ } public void testBoundsOfEmptyString() { - PText t = new PText(); - t.setText(""); - assertEquals(0, t.getBoundsReference().getWidth(), 0.000001); - t.setText(null); - assertEquals(0, t.getBoundsReference().getWidth(), 0.000001); + textNode.setText(""); + assertEquals(0, textNode.getBoundsReference().getWidth(), 0.000001); + textNode.setText(null); + assertEquals(0, textNode.getBoundsReference().getWidth(), 0.000001); } public void testToString() { - PText t = new PText(); - t.setText("hello world"); - assertNotNull(t.toString()); + textNode.setText("hello world"); + assertNotNull(textNode.toString()); + } + + public void testJustificationIsLeftByDefault() { + assertEquals(JLabel.LEFT_ALIGNMENT, textNode.getJustification(), 0.000001); + } + + public void testSetJustificationPersists() { + textNode.setJustification(JLabel.RIGHT_ALIGNMENT); + assertEquals(JLabel.RIGHT_ALIGNMENT, textNode.getJustification(), 0.000001); + } + + public void testTextPaintIsBlackByDefault() { + assertEquals(Color.BLACK, textNode.getTextPaint()); + } + + public void testSetTextPaintPersists() { + textNode.setTextPaint(Color.RED); + assertEquals(Color.RED, textNode.getTextPaint()); + } + + public void testConstrainWidthToTextTrueByDefault() { + assertTrue(textNode.isConstrainWidthToTextWidth()); + } + + public void testConstrainHeightToTextTrueByDefault() { + assertTrue(textNode.isConstrainHeightToTextHeight()); + } + + public void testConstrainWidthPersists() { + textNode.setConstrainWidthToTextWidth(true); + assertTrue(textNode.isConstrainWidthToTextWidth()); + } + + public void testConstrainHeightPersists() { + textNode.setConstrainHeightToTextHeight(true); + assertTrue(textNode.isConstrainHeightToTextHeight()); + } + + public void testDefaultGreekThreshold() { + assertEquals(PText.DEFAULT_GREEK_THRESHOLD, textNode.getGreekThreshold(), 0.000001); + } + + public void testSetGreekThreshold() { + textNode.setGreekThreshold(2); + assertEquals(2, textNode.getGreekThreshold(), 0.000001); + } + + public void testDefaultFont() { + assertEquals(PText.DEFAULT_FONT, textNode.getFont()); + } + + public void testSetFontPersists() { + Font newFont = new Font("Arial", Font.BOLD, 10); + textNode.setFont(newFont); + assertEquals(newFont, textNode.getFont()); + } + + public void testSetFontFiresPropertyChangedEvent() { + textNode.addPropertyChangeListener(PText.PROPERTY_FONT, mockListener); + Font newFont = new Font("Arial", Font.BOLD, 10); + textNode.setFont(newFont); + + assertEquals(1, mockListener.getPropertyChangeCount()); + assertEquals(PText.PROPERTY_FONT, mockListener.getPropertyChange(0).getPropertyName()); } }