Newer
Older
piccolo2d.java / swt / src / main / java / edu / umd / cs / piccolox / swt / PSWTText.java
@Allain Lalonde Allain Lalonde on 16 Oct 2009 14 KB Cleaning in PSWTText code a little.
/**
 * Copyright (C) 1998-1999 by University of Maryland, College Park, MD 20742, USA
 * All rights reserved.
 */
package edu.umd.cs.piccolox.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 edu.umd.cs.piccolo.PNode;
import edu.umd.cs.piccolo.util.PPaintContext;

/**
 * <b>PSWTText</b> 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.
 * <P>
 * <b>Warning:</b> Serialized and ZSerialized objects of this class will not be
 * compatible with future Jazz releases. The current serialization support is
 * appropriate for short term storage or RMI between applications running the
 * same version of Jazz. A future release of Jazz will provide support for long
 * term persistence.
 */
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_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.
     * <p>
     * <b>Warning:</b> 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-coord of translation
     * @param y the Y-coord 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.
     * <p>
     * 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();
    }

}