/*
* @(#)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 research.figure;
import research.*;
import research.connector.BoxConnector;
import research.connector.ChopBoxConnector;
import research.connector.Connector;
import research.store.StorableOutput;
import research.store.StorableInput;
import java.util.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.io.*;
import research.util.ColorMap;
import javax.swing.*;
/**
* A text figure.
*
* @version <$CURRENT_VERSION$>
* @see research.tool.TextTool
*/
public class TextFigure
extends AbstractFigure
implements FigureChangeListener, TextHolder {
public static final int CENTER = 0;
public static final int NORTH = 1;
public static final int SOUTH = 2;
public static final int WEST = 3;
public static final int EAST = 4;
// cache of the TextFigure's size
transient private boolean fSizeIsDirty = true;
transient private int fWidth;
transient private int fHeight;
private int minWidth = 0;
private int leftInset = 0;
private int rightInset = 0;
public final static String TEXT = "text";
private boolean fIsReadOnly;
private Figure fObservedFigure = null;
private OffsetLocator fLocator = null;
private transient JTextField textAgent = null;
protected JTextField getTextAgent() {
if (textAgent == null) {
textAgent = new JTextField();
textAgent.setEditable(false);
textAgent.setEnabled(false);
textAgent.setDisabledTextColor(textAgent.getForeground());
textAgent.setCaretColor(textAgent.getBackground());
textAgent.setBorder(BorderFactory.createEmptyBorder());
textAgent.setOpaque(false);
}
return textAgent;
}
/*
* Serialization support.
*/
private static final long serialVersionUID = 4599820785949456124L;
private int textFigureSerializedDataVersion = 1;
public TextFigure() {
setAttribute("font", new Font("Dialog", Font.PLAIN, 14));
setAttribute("anchor", new Point(0, 0));
setAttribute("fillColor", ColorMap.color("None"));
setAttribute("textColor", Color.black);
setAttribute("frameColor", ColorMap.color("None"));
setAttribute("insets", new Insets(1, 6, 1, 6));
setAttribute("minWidth", 0);
setAttribute("leftInset", 0);
setAttribute("rightInset", 0);
setAttribute(TEXT, "");
fSizeIsDirty = true;
initConnectors();
}
protected void initConnectors() {
connector = new Connector[5];
connector[NORTH] = new BoxConnector(this, BoxConnector.NORTH);
connector[SOUTH] = new BoxConnector(this, BoxConnector.SOUTH);
connector[WEST] = new BoxConnector(this, BoxConnector.WEST);
connector[EAST] = new BoxConnector(this, BoxConnector.EAST);
connector[CENTER] = new ChopBoxConnector(this);
}
@Override
public void setAttribute(String name, Object value){
this.willChange();
super._setAttribute(name, value);
if(name != null){
if(name.equals("minWidth")){
minWidth = (Integer) value;
} else if(name.equals("leftInset")){
leftInset = (Integer) value;
} else if(name.equals("rightInset")){
rightInset = (Integer) value;
}
}
this.changed();
}
@Override
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) {
Point anchor = (Point) this.getAttribute("anchor");
anchor.x += x;
anchor.y += y;
}
public void basicDisplayBox(Point newOrigin, Point newCorner) {
Point anchor = (Point) this.getAttribute("anchor");
anchor.x = newOrigin.x;
anchor.y = newOrigin.y;
}
public int getMinWidth() {
return minWidth;
}
public Rectangle getDisplayBox() {
Dimension extent = textExtent();
Insets insets = (Insets) this.getAttribute("insets");
Point anchor = (Point) this.getAttribute("anchor");
return new Rectangle(anchor.x, anchor.y,
extent.width + insets.left + insets.right,
extent.height + insets.top + insets.bottom);
}
public Rectangle getDisplayBox(Rectangle rect) {
if (rect == null)
rect = new Rectangle();
Dimension extent = textExtent();
Insets insets = (Insets) this.getAttribute("insets");
Point anchor = (Point) this.getAttribute("anchor");
rect.setBounds(anchor.x, anchor.y,
extent.width + insets.left + insets.right,
extent.height + insets.top + insets.bottom);
return rect;
}
public Rectangle textDisplayBox() {
Dimension extent = textExtent();
Insets insets = (Insets) this.getAttribute("insets");
Point anchor = (Point) this.getAttribute("anchor");
return new Rectangle(anchor.x + insets.left, anchor.y + insets.top, extent.width, extent.height);
}
/**
* 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 (Font) this.getAttribute("font");
}
/**
* Sets the font.
*/
public void setFont(Font newFont) {
if (newFont == null)
return;
willChange();
setAttribute("font", newFont);
markDirty();
changed();
}
/**
* Updates the location whenever the figure changes itself.
*/
public void changed() {
super.changed();
updateLocation();
}
/**
* Gets the text shown by the text figure.
*/
public String getText() {
return (String) getAttribute(TEXT);
}
/**
* Sets the text shown by the text figure.
*/
public void setText(String newText) {
if (newText == null)
return;
if (!getText().equals(newText)) {
willChange();
setAttribute(TEXT, newText);
int realWidth = getDisplayBox().width - leftInset - rightInset;
if (realWidth <= minWidth) {
Insets insets = (Insets) this.getAttribute("insets");
insets.left -= leftInset;
insets.right -= rightInset;
setAttribute("leftInset", (minWidth - realWidth) / 2);
setAttribute("rightInset", minWidth - realWidth - leftInset);
insets.left += leftInset;
insets.right += rightInset;
} else {
if ((leftInset > 0) || (rightInset > 0)) {
Insets insets = (Insets) this.getAttribute("insets");
insets.left -= leftInset;
insets.right -= rightInset;
setAttribute("leftInset", 0);
setAttribute("rightInset", 0);
}
}
markDirty();
changed();
}
}
/**
* Tests whether the figure accepts typing.
*/
public boolean acceptsTyping() {
return !fIsReadOnly;
}
@Override
public void drawBackground(Graphics g) {
Rectangle r = getDisplayBox();
g.fillRect(r.x, r.y, r.width, r.height);
}
@Override
public void drawFrame(Graphics g) {
Rectangle box = getDisplayBox();
g.drawRect(box.x, box.y, box.width, box.height);
}
@Override
public void drawContent(Graphics g) {
Color text = getTextColor();
if (!ColorMap.isTransparent(text)) {
Color _old = g.getColor();
g.setColor(text);
drawText(g);
g.setColor(_old);
}
}
protected void drawText(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
if ((g2.getTransform().getScaleX() == 1) && (g2.getTransform().getScaleY() == 1)) {
Rectangle rect = g.getClipBounds();
Rectangle textBox = textDisplayBox();
getTextAgent().setBounds(textBox);
getTextAgent().setForeground(g.getColor());
getTextAgent().setDisabledTextColor(g.getColor());
getTextAgent().getUI().getRootView(getTextAgent()).paint(g, textBox);
g.setClip(rect);
} else {
//����ַ�������ʾ����
Rectangle textBox = textDisplayBox();
//��þɵı任����
AffineTransform oldAT = g2.getTransform();
//���ñ任����Ϊ����
g2.setTransform(AffineTransform.getTranslateInstance(0, 0));
FontRenderContext frc = g2.getFontRenderContext();
Point2D pen = textBox.getLocation();
getTextAgent().setText(getText());
String currentString = getTextAgent().getText();
TextLayout layout = null;
if (currentString.length() > 0) {
layout = new TextLayout(currentString, (Font) getAttribute("font"), frc);
pen.setLocation(pen.getX(), pen.getY() + layout.getAscent());
Shape shape = layout.getOutline(oldAT);
double deltaScale = textBox.width * oldAT.getScaleX() / shape.getBounds().width;
if (1 - deltaScale > 0) {
AffineTransform adjust = (AffineTransform) oldAT.clone();
adjust.scale(deltaScale, 1);
shape = layout.getOutline(adjust);
}
AffineTransform at = AffineTransform.getTranslateInstance(pen.getX() * oldAT.getScaleX(), pen.getY() * oldAT.getScaleY());
shape = at.createTransformedShape(shape);
g2.fill(shape);
}
g2.setTransform(oldAT);
}
}
protected Dimension textExtent() {
if (!fSizeIsDirty) {
return new Dimension(fWidth, fHeight);
}
getTextAgent().setFont((Font) getAttribute("font"));
getTextAgent().setText(getText());
Dimension dim = getTextAgent().getPreferredSize();
fWidth = dim.width;
fHeight = dim.height;
return dim;
/**
Font font = (Font) this.getAttribute("font");
FontMetrics metrics = Toolkit.getDefaultToolkit().getFontMetrics(font);
fWidth = metrics.stringWidth(getText());
fHeight = metrics.getHeight();
fSizeIsDirty = false;
return new Dimension(fWidth, fHeight);
**/
}
protected 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 NullHandle(this, RelativeLocator.southWest()));
//handles.addElement(new FontSizeHandle(this, RelativeLocator.southWest()));
return handles;
}
@Override
public Connector connectorAt(int x, int y) {
Rectangle rect = getDisplayBox();
if (!rect.contains(x, y)) return null;
int index = findLocation(x, y);
if (index < 0) return null;
Connector[] array = getConnectors();
return array[index];
}
private int findLocation(int x, int y) {
Rectangle rect = getDisplayBox();
if (x < rect.x + rect.width / 3) {
if (y < rect.y + rect.height / 3) {
return -1;
} else if (y < rect.y + rect.height - rect.height / 3) {
return WEST;
} else {
return -1;
}
} else if (x < rect.x + rect.width - rect.width / 3) {
if (y < rect.y + rect.height / 3) {
return NORTH;
} else if (y < rect.y + rect.height - rect.height / 3) {
return CENTER;
} else {
return SOUTH;
}
} else {
if (y < rect.y + rect.height / 3) {
return -1;
} else if (y < rect.y + rect.height - rect.height / 3) {
return EAST;
} else {
return -1;
}
}
}
@Override
public void write(StorableOutput dw) {
super.write(dw);
dw.writeBoolean(fIsReadOnly);
dw.writeStorable(fObservedFigure);
dw.writeStorable(fLocator);
}
@Override
public void read(StorableInput dr) throws IOException {
super.read(dr);
markDirty();
fIsReadOnly = dr.readBoolean();
fObservedFigure = (Figure) dr.readStorable();
if (fObservedFigure != null) {
fObservedFigure.addFigureChangeListener(this);
}
fLocator = (OffsetLocator) dr.readStorable();
Object _minWidth = getAttribute("minWidth");
if (_minWidth != null)
minWidth = (Integer) _minWidth;
else
setAttribute("minWidth", 0);
Object _leftInset = getAttribute("leftInset");
if (_leftInset != null)
leftInset = (Integer) _leftInset;
else
setAttribute("leftInset", 0);
Object _rightInset = getAttribute("rightInset");
if (_rightInset != null)
rightInset = (Integer) _rightInset;
else
setAttribute("rightInset", 0);
}
/**
* This method is used to implement the clone mtehod of AttributeFigure.
* @param s
* @throws ClassNotFoundException
* @throws IOException
*/
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 boolean isConnected() {
return (fObservedFigure != null);
}
public Figure getConnectedFigure() {
return fObservedFigure;
}
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);
Point anchor = (Point) this.getAttribute("anchor");
p.x -= size().width / 2 + anchor.x;
p.y -= size().height / 2 + anchor.y;
if (p.x != 0 || p.y != 0) {
willChange();
basicMoveBy(p.x, p.y);
changed();
}
}
}
@Override
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;
}
}