TextFigure.java

/*
 * @(#)TextFigure.java
 *
 * Project:		JHotdraw - a GUI framework for technical drawings
 *				http://www.jhotdraw.org
 *				http://jhotdraw.sourceforge.net
 * Copyright:	© by the original author(s) and all contributors
 * License:		Lesser GNU Public License (LGPL)
 *				http://www.opensource.org/licenses/lgpl-license.html
 */

package CH.ifa.draw.figures;

import java.util.*;
import java.awt.*;
import java.io.*;
import CH.ifa.draw.framework.*;
import CH.ifa.draw.standard.*;
import CH.ifa.draw.util.*;

/**
 * A text figure.
 *
 * @see TextTool
 *
 * @version <$CURRENT_VERSION$>
 */
public  class TextFigure
		extends AttributeFigure
		implements FigureChangeListener, TextHolder {

	private int               fOriginX;
	private int               fOriginY;

	// cache of the TextFigure's size
	transient private boolean fSizeIsDirty = true;
	transient private int     fWidth;
	transient private int     fHeight;

	private String  fText;
	private Font    fFont;
	private boolean fIsReadOnly;

	private Figure  fObservedFigure = null;
	private OffsetLocator fLocator = null;

	private static String fgCurrentFontName  = "Helvetica";
	private static int    fgCurrentFontSize  = 12;
	private static int    fgCurrentFontStyle = Font.PLAIN;

	/*
	 * Serialization support.
	 */
	private static final long serialVersionUID = 4599820785949456124L;
	private int textFigureSerializedDataVersion = 1;

	public TextFigure() {
		fOriginX = 0;
		fOriginY = 0;
		fFont = createCurrentFont();
		setAttribute("FillColor", ColorMap.color("None"));
		fText = new String("");
		fSizeIsDirty = true;
	}

	public void moveBy(int x, int y) {
		willChange();
		basicMoveBy(x, y);
		if (fLocator != null) {
			fLocator.moveBy(x, y);
		}
		changed();
	}

	protected void basicMoveBy(int x, int y) {
		fOriginX += x;
		fOriginY += y;
	}

	public void basicDisplayBox(Point newOrigin, Point newCorner) {
		fOriginX = newOrigin.x;
		fOriginY = newOrigin.y;
	}

	public Rectangle displayBox() {
		Dimension extent = textExtent();
		return new Rectangle(fOriginX, fOriginY, extent.width, extent.height);
	}

	public Rectangle textDisplayBox() {
		return displayBox();
	}

	/**
	 * Tests whether this figure is read only.
	 */
	public boolean readOnly() {
		return fIsReadOnly;
	}

	/**
	 * Sets the read only status of the text figure.
	 */
	public void setReadOnly(boolean isReadOnly) {
		fIsReadOnly = isReadOnly;
	}

	/**
	 * Gets the font.
	 */
	public Font getFont() {
		return fFont;
	}

	/**
	 * Sets the font.
	 */
	public void setFont(Font newFont) {
		willChange();
		fFont = newFont;
		markDirty();
		changed();
	}

	/**
	 * Updates the location whenever the figure changes itself.
	 */
	public void changed() {
		super.changed();
		updateLocation();
	}

	/**
	 * A text figure understands the "FontSize", "FontStyle", and "FontName"
	 * attributes.
	 */
	public Object getAttribute(String name) {
		Font font = getFont();
		if (name.equals("FontSize")) {
			return new Integer(font.getSize());
		}
		if (name.equals("FontStyle")) {
			return new Integer(font.getStyle());
		}
		if (name.equals("FontName")) {
			return font.getName();
		}
		return super.getAttribute(name);
	}

	/**
	 * A text figure understands the "FontSize", "FontStyle", and "FontName"
	 * attributes.
	 */
	public void setAttribute(String name, Object value) {
		Font font = getFont();
		if (name.equals("FontSize")) {
			Integer s = (Integer)value;
			setFont(new Font(font.getName(), font.getStyle(), s.intValue()) );
		}
		else if (name.equals("FontStyle")) {
			Integer s = (Integer)value;
			int style = font.getStyle();
			if (s.intValue() == Font.PLAIN) {
				style = font.PLAIN;
			}
			else {
				style = style ^ s.intValue();
			}
			setFont(new Font(font.getName(), style, font.getSize()) );
		}
		else if (name.equals("FontName")) {
			String n = (String)value;
			setFont(new Font(n, font.getStyle(), font.getSize()) );
		}
		else {
			super.setAttribute(name, value);
		}
	}

	/**
	 * Gets the text shown by the text figure.
	 */
	public String getText() {
		return fText;
	}

	/**
	 * Sets the text shown by the text figure.
	 */
	public void setText(String newText) {
		if (!newText.equals(fText)) {
			willChange();
			fText = new String(newText);
			markDirty();
			changed();
		}
	}

	/**
	 * Tests whether the figure accepts typing.
	 */
	public boolean acceptsTyping() {
		return !fIsReadOnly;
	}

	public void drawBackground(Graphics g) {
		Rectangle r = displayBox();
		g.fillRect(r.x, r.y, r.width, r.height);
	}

	public void drawFrame(Graphics g) {
		g.setFont(fFont);
		g.setColor((Color) getAttribute("TextColor"));
		FontMetrics metrics = g.getFontMetrics(fFont);
		g.drawString(fText, fOriginX, fOriginY + metrics.getAscent());
	}

	private Dimension textExtent() {
		if (!fSizeIsDirty) {
			return new Dimension(fWidth, fHeight);
		}
		FontMetrics metrics = Toolkit.getDefaultToolkit().getFontMetrics(fFont);
		fWidth = metrics.stringWidth(fText);
		fHeight = metrics.getHeight();
		fSizeIsDirty = false;
		return new Dimension(metrics.stringWidth(fText), metrics.getHeight());
	}

	private void markDirty() {
		fSizeIsDirty = true;
	}

	/**
	 * Gets the number of columns to be overlaid when the figure is edited.
	 */
	public int overlayColumns() {
		int length = getText().length();
		int columns = 20;
		if (length != 0) {
			columns = getText().length()+ 3;
		}
		return columns;
	}

	public Vector handles() {
		Vector handles = new Vector();
		handles.addElement(new NullHandle(this, RelativeLocator.northWest()));
		handles.addElement(new NullHandle(this, RelativeLocator.northEast()));
		handles.addElement(new NullHandle(this, RelativeLocator.southEast()));
		handles.addElement(new FontSizeHandle(this, RelativeLocator.southWest()));
		return handles;
	}

	public void write(StorableOutput dw) {
		super.write(dw);
		dw.writeInt(fOriginX);
		dw.writeInt(fOriginY);
		dw.writeString(fText);
		dw.writeString(fFont.getName());
		dw.writeInt(fFont.getStyle());
		dw.writeInt(fFont.getSize());
		dw.writeBoolean(fIsReadOnly);
		dw.writeStorable(fObservedFigure);
		dw.writeStorable(fLocator);
	}

	public void read(StorableInput dr) throws IOException {
		super.read(dr);
		markDirty();
		fOriginX = dr.readInt();
		fOriginY = dr.readInt();
		fText = dr.readString();
		fFont = new Font(dr.readString(), dr.readInt(), dr.readInt());
		fIsReadOnly = dr.readBoolean();

		fObservedFigure = (Figure)dr.readStorable();
		if (fObservedFigure != null) {
			fObservedFigure.addFigureChangeListener(this);
		}
		fLocator = (OffsetLocator)dr.readStorable();
	}

	private void readObject(ObjectInputStream s) throws ClassNotFoundException, IOException {

		s.defaultReadObject();

		if (fObservedFigure != null) {
			fObservedFigure.addFigureChangeListener(this);
		}
		markDirty();
	}

	public void connect(Figure figure) {
		if (fObservedFigure != null) {
			fObservedFigure.removeFigureChangeListener(this);
		}

		fObservedFigure = figure;
		fLocator = new OffsetLocator(figure.connectedTextLocator(this));
		fObservedFigure.addFigureChangeListener(this);
		updateLocation();
	}

	public void figureChanged(FigureChangeEvent e) {
		updateLocation();
	}

	public void figureRemoved(FigureChangeEvent e) {
		if (listener() != null) {
			listener().figureRequestRemove(new FigureChangeEvent(this));
		}
	}

	public void figureRequestRemove(FigureChangeEvent e) {}
	public void figureInvalidated(FigureChangeEvent e) {}
	public void figureRequestUpdate(FigureChangeEvent e) {}

	/**
	 * Updates the location relative to the connected figure.
	 * The TextFigure is centered around the located point.
	 */
	protected void updateLocation() {
		if (fLocator != null) {
			Point p = fLocator.locate(fObservedFigure);

			p.x -= size().width/2 + fOriginX;
			p.y -= size().height/2 + fOriginY;
			if (p.x != 0 || p.y != 0) {
				willChange();
				basicMoveBy(p.x, p.y);
				changed();
			}
		}
	}

	public void release() {
		super.release();
		disconnect(fObservedFigure);
		fObservedFigure = null;
	}

	/**
	 * Disconnects a text holder from a connect figure.
	 */
	public void disconnect(Figure disconnectFigure) {
		if (disconnectFigure != null) {
			disconnectFigure.removeFigureChangeListener(this);
		}
		fLocator = null;
	}


	/**
	 * Creates the current font to be used for new text figures.
	 */
	static public Font createCurrentFont() {
		return new Font(fgCurrentFontName, fgCurrentFontStyle, fgCurrentFontSize);
	}

	/**
	 * Sets the current font name
	 */
	static public void setCurrentFontName(String name) {
		fgCurrentFontName = name;
	}

	/**
	 * Sets the current font size.
	 */
	static public void setCurrentFontSize(int size) {
		fgCurrentFontSize = size;
	}

	/**
	 * Sets the current font style.
	 */
	static public void setCurrentFontStyle(int style) {
		fgCurrentFontStyle = style;
	}
}