org.piccolo2d.extras.swt.PSWTText Maven / Gradle / Ivy
/**
* Copyright (C) 1998-1999 by University of Maryland, College Park, MD 20742, USA
* All rights reserved.
*/
package org.piccolo2d.extras.swt;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Iterator;
import org.eclipse.swt.graphics.FontMetrics;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Display;
import org.piccolo2d.PNode;
import org.piccolo2d.util.PPaintContext;
/**
* PSWTText creates a visual component to support text. Multiple lines
* can be entered, and basic editing is supported. A caret is drawn, and can be
* repositioned with mouse clicks. The text object is positioned so that its
* upper-left corner is at the origin, though this can be changed with the
* translate methods.
*/
public class PSWTText extends PNode {
private static final long serialVersionUID = 1L;
/** Below this magnification render text as 'greek'. */
protected static final double DEFAULT_GREEK_THRESHOLD = 5.5;
/** Default color of text rendered as 'greek'. */
protected static final Color DEFAULT_GREEK_COLOR = Color.gray;
/** Default font name of text. */
protected static final String DEFAULT_FONT_NAME = "Helvetica";
/** Default font style for text. */
protected static final int DEFAULT_FONT_STYLE = Font.PLAIN;
/** Default font size for text. */
protected static final int DEFAULT_FONT_SIZE = 12;
/** Default font for text. */
protected static final Font DEFAULT_FONT = new Font(DEFAULT_FONT_NAME, DEFAULT_FONT_STYLE, DEFAULT_FONT_SIZE);
/** Default color for text. */
protected static final Color DEFAULT_PEN_COLOR = Color.black;
/** Default text when new text area is created. */
protected static final String DEFAULT_TEXT = "";
/** Default background transparency state. */
protected static final boolean DEFAULT_IS_TRANSPARENT = false;
/** Default padding. */
protected static final int DEFAULT_PADDING = 2;
/** Whether the text be drawn with a transparent background. */
private boolean transparent = DEFAULT_IS_TRANSPARENT;
/** Below this magnification text is rendered as greek. */
protected double greekThreshold = DEFAULT_GREEK_THRESHOLD;
/** Color for greek text. */
protected Color greekColor = DEFAULT_GREEK_COLOR;
/** Current pen color. */
protected Color penColor = DEFAULT_PEN_COLOR;
/** Current text font. */
protected Font font = DEFAULT_FONT;
/** The amount of padding on each side of the text. */
protected int padding = DEFAULT_PADDING;
/** Each element is one line of text. */
protected ArrayList lines = new ArrayList();
/** Translation offset X. */
protected double translateX = 0.0;
/** Translation offset Y. */
protected double translateY = 0.0;
/** Default constructor for PSWTTest. */
public PSWTText() {
this(DEFAULT_TEXT, DEFAULT_FONT);
}
/**
* PSWTTest constructor with initial text.
*
* @param str The initial text.
*/
public PSWTText(final String str) {
this(str, DEFAULT_FONT);
}
/**
* PSWTTest constructor with initial text and font.
*
* @param str The initial text.
* @param font The font for this PSWTText component.
*/
public PSWTText(final String str, final Font font) {
setText(str);
this.font = font;
recomputeBounds();
}
/**
* Returns the current pen color.
*
* @return current pen color
*/
public Color getPenColor() {
return penColor;
}
/**
* Sets the current pen color.
*
* @param color use this color.
*/
public void setPenColor(final Color color) {
penColor = color;
repaint();
}
/**
* Returns the current pen paint.
*
* @return the current pen paint
*/
public Paint getPenPaint() {
return penColor;
}
/**
* Sets the current pen paint.
*
* @param aPaint use this paint.
*/
public void setPenPaint(final Paint aPaint) {
penColor = (Color) aPaint;
}
/**
* Returns the current background color.
*
* @return the current background color
*/
public Color getBackgroundColor() {
return (Color) getPaint();
}
/**
* Sets the current background color.
*
* @param color use this color.
*/
public void setBackgroundColor(final Color color) {
super.setPaint(color);
}
/**
* Sets whether the text should be drawn in transparent mode, i.e., whether
* the background should be drawn or not.
*
* @param transparent the new transparency of the background
*/
public void setTransparent(final boolean transparent) {
this.transparent = transparent;
}
/**
* Returns whether the text should be drawn using the transparent mode,
* i.e., whether the background should be drawn or not.
*
* @return true if background will not be drawn
*/
public boolean isTransparent() {
return transparent;
}
/**
* Returns the current greek threshold. Below this magnification text is
* rendered as 'greek'.
*
* @return magnification at which the text will not be drawn and a blank
* rectangle will appear instead
*/
public double getGreekThreshold() {
return greekThreshold;
}
/**
* Sets the current greek threshold. Below this magnification text is
* rendered as 'greek'.
*
* @param threshold compared to renderContext magnification.
*/
public void setGreekThreshold(final double threshold) {
greekThreshold = threshold;
repaint();
}
/**
* Returns the current font.
*
* @return current font in node
*/
public Font getFont() {
return font;
}
/**
* Return the text within this text component. Multiline text is returned as
* a single string where each line is separated by a newline character.
* Single line text does not have any newline characters.
*
* @return string containing this node's text
*/
public String getText() {
StringBuffer result = new StringBuffer();
final Iterator lineIterator = lines.iterator();
while (lineIterator.hasNext()) {
result.append(lineIterator.next());
result.append('\n');
}
if (result.length() > 0) {
result.deleteCharAt(result.length() - 1);
}
return result.toString();
}
/**
* Sets the font for the text.
*
* Warning: Java has a serious bug in that it does not support very
* small fonts. In particular, fonts that are less than about a pixel high
* just don't work. Since in Jazz, it is common to create objects of
* arbitrary sizes, and then scale them, an application can easily create a
* text object with a very small font by accident. The workaround for this
* bug is to create a larger font for the text object, and then scale the
* node down correspondingly.
*
* @param aFont use this font.
*/
public void setFont(final Font aFont) {
font = aFont;
recomputeBounds();
}
/**
* Sets the text of this visual component to str. Multiple lines of text are
* separated by a newline character.
*
* @param str use this string.
*/
public void setText(final String str) {
int pos = 0;
int index;
boolean done = false;
lines.clear();
do {
index = str.indexOf('\n', pos);
if (index == -1) {
lines.add(str.substring(pos));
done = true;
}
else {
lines.add(str.substring(pos, index));
pos = index + 1;
}
} while (!done);
recomputeBounds();
}
/**
* Set text translation offset X.
*
* @param x the X translation.
*/
public void setTranslateX(final double x) {
setTranslation(x, translateY);
}
/**
* Get the X offset translation.
*
* @return the X translation.
*/
public double getTranslateX() {
return translateX;
}
/**
* Set text translation offset Y.
*
* @param y the Y translation.
*/
public void setTranslateY(final double y) {
setTranslation(translateX, y);
}
/**
* Get the Y offset translation.
*
* @return the Y translation.
*/
public double getTranslateY() {
return translateY;
}
/**
* Set the text translation offset to the specified position.
*
* @param x the X component of translation
* @param y the Y component of translation
*/
public void setTranslation(final double x, final double y) {
translateX = x;
translateY = y;
recomputeBounds();
}
/**
* Set the text translation offset to point p.
*
* @param p The translation offset.
*/
public void setTranslation(final Point2D p) {
setTranslation(p.getX(), p.getY());
}
/**
* Get the text translation offset.
*
* @return The translation offset.
*/
public Point2D getTranslation() {
final Point2D p = new Point2D.Double(translateX, translateY);
return p;
}
/**
* Renders the text object.
*
* The transform, clip, and composite will be set appropriately when this
* object is rendered. It is up to this object to restore the transform,
* clip, and composite of the Graphics2D if this node changes any of them.
* However, the color, font, and stroke are unspecified by Jazz. This object
* should set those things if they are used, but they do not need to be
* restored.
*
* @param ppc Contains information about current render.
*/
public void paint(final PPaintContext ppc) {
if (lines.isEmpty()) {
return;
}
final Graphics2D g2 = ppc.getGraphics();
AffineTransform at = null;
boolean translated = false;
if (translateX != 0.0 || translateY != 0.0) {
at = g2.getTransform();
g2.translate(translateX, translateY);
translated = true;
}
final double renderedFontSize = font.getSize() * ppc.getScale();
// If font is too small then render it as "greek"
if (renderedFontSize < greekThreshold) {
paintAsGreek(ppc);
}
else {
paintAsText(ppc);
}
if (translated) {
g2.setTransform(at);
}
}
/**
* Paints this object as greek.
*
* @param ppc The graphics context to paint into.
*/
public void paintAsGreek(final PPaintContext ppc) {
final Graphics2D g2 = ppc.getGraphics();
if (greekColor != null) {
g2.setBackground(greekColor);
((SWTGraphics2D) g2).fillRect(0, 0, getWidth(), getHeight());
}
}
/**
* Paints this object normally (show it's text). Note that the entire text
* gets rendered so that it's upper left corner appears at the origin of
* this local object.
*
* @param ppc The graphics context to paint into.
*/
public void paintAsText(final PPaintContext ppc) {
final SWTGraphics2D sg2 = (SWTGraphics2D) ppc.getGraphics();
if (!transparent) {
if (getPaint() == null) {
sg2.setBackground(Color.WHITE);
}
else {
sg2.setBackground((Color) getPaint());
}
sg2.fillRect(0, 0, (int) getWidth(), (int) getHeight());
}
sg2.translate(padding, padding);
sg2.setColor(penColor);
sg2.setFont(font);
String line;
double y = 0;
final FontMetrics fontMetrics = sg2.getSWTFontMetrics();
final Iterator lineIterator = lines.iterator();
while (lineIterator.hasNext()) {
line = (String) lineIterator.next();
if (line.length() != 0) {
sg2.drawString(line, 0, y, true);
}
y += fontMetrics.getHeight();
}
sg2.translate(-padding, -padding);
}
/**
* Recalculates this node's bounding box by examining it's text content.
*/
protected void recomputeBounds() {
final GC gc = new GC(Display.getDefault());
final Point newBounds;
if (isTextEmpty()) {
// If no text, then we want to have the bounds of a space character,
// so get those bounds here
newBounds = gc.stringExtent(" ");
}
else {
newBounds = calculateTextBounds(gc);
}
gc.dispose();
setBounds(translateX, translateY, newBounds.x + 2 * DEFAULT_PADDING, newBounds.y + 2 * DEFAULT_PADDING);
}
/**
* Determines if this node's text is essentially empty.
*
* @return true if the text is the empty string
*/
private boolean isTextEmpty() {
return lines.isEmpty() || lines.size() == 1 && ((String) lines.get(0)).equals("");
}
/**
* Calculates the bounds of the text in the box as measured by the given
* graphics context and font metrics.
*
* @param gc graphics context from which the measurements are done
* @return point representing the dimensions of the text's bounds
*/
private Point calculateTextBounds(final GC gc) {
final SWTGraphics2D g2 = new SWTGraphics2D(gc, Display.getDefault());
g2.setFont(font);
final FontMetrics fm = g2.getSWTFontMetrics();
final Point textBounds = new Point(0, 0);
boolean firstLine = true;
final Iterator lineIterator = lines.iterator();
while (lineIterator.hasNext()) {
String line = (String) lineIterator.next();
Point lineBounds = gc.stringExtent(line);
if (firstLine) {
textBounds.x = lineBounds.x;
textBounds.y += fm.getAscent() + fm.getDescent() + fm.getLeading();
firstLine = false;
}
else {
textBounds.x = Math.max(lineBounds.x, textBounds.x);
textBounds.y += fm.getHeight();
}
}
return textBounds;
}
/** {@inheritDoc} */
protected void internalUpdateBounds(final double x, final double y, final double width, final double height) {
recomputeBounds();
}
}