/*
* @(#)TextFigure.java
*
* Copyright (c) 1996-2010 The authors and contributors of JHotDraw.
* You may not use, copy or modify this file, except in compliance with the
* accompanying license terms.
*/
package org.jhotdraw.draw;
import edu.umd.cs.findbugs.annotations.Nullable;
import org.jhotdraw.draw.tool.Tool;
import org.jhotdraw.draw.tool.TextEditingTool;
import org.jhotdraw.draw.handle.FontSizeHandle;
import org.jhotdraw.util.*;
import java.awt.*;
import java.awt.font.*;
import java.awt.geom.*;
import java.util.*;
import java.io.*;
import org.jhotdraw.draw.handle.BoundsOutlineHandle;
import org.jhotdraw.draw.handle.Handle;
import org.jhotdraw.draw.handle.MoveHandle;
import org.jhotdraw.draw.locator.RelativeLocator;
import static org.jhotdraw.draw.AttributeKeys.*;
import org.jhotdraw.geom.*;
import org.jhotdraw.xml.DOMInput;
import org.jhotdraw.xml.DOMOutput;
/**
* A {@code TextHolderFigure} which holds a single line of text.
* <p>
* A DrawingEditor should provide the
* {@link org.jhotdraw.draw.tool.TextCreationTool} to create a
* {@code TextFigure}.
*
* @author Werner Randelshofer
* @version $Id$
*/
public class TextFigure extends AbstractAttributedDecoratedFigure
implements TextHolderFigure {
private static final long serialVersionUID = 1L;
protected Point2D.Double origin = new Point2D.Double();
protected boolean editable = true;
// cache of the TextFigure's layout
@Nullable transient protected TextLayout textLayout;
/** Creates a new instance. */
public TextFigure() {
this(ResourceBundleUtil.getBundle("org.jhotdraw.draw.Labels").
getString("TextFigure.defaultText"));
}
public TextFigure(String text) {
setText(text);
}
// DRAWING
@Override
protected void drawStroke(java.awt.Graphics2D g) {
}
@Override
protected void drawFill(java.awt.Graphics2D g) {
}
@Override
protected void drawText(java.awt.Graphics2D g) {
if (getText() != null || isEditable()) {
TextLayout layout = getTextLayout();
layout.draw(g, (float) origin.x, (float) (origin.y + layout.getAscent()));
}
}
// SHAPE AND BOUNDS
@Override
public void transform(AffineTransform tx) {
tx.transform(origin, origin);
}
@Override
public void setBounds(Point2D.Double anchor, Point2D.Double lead) {
origin = new Point2D.Double(anchor.x, anchor.y);
}
@Override
public boolean figureContains(Point2D.Double p) {
if (getBounds().contains(p)) {
return true;
}
return false;
}
protected TextLayout getTextLayout() {
if (textLayout == null) {
String text = getText();
if (text == null || text.length() == 0) {
text = " ";
}
FontRenderContext frc = getFontRenderContext();
HashMap<TextAttribute, Object> textAttributes = new HashMap<TextAttribute, Object>();
textAttributes.put(TextAttribute.FONT, getFont());
if (get(FONT_UNDERLINE)) {
textAttributes.put(TextAttribute.UNDERLINE, TextAttribute.UNDERLINE_LOW_ONE_PIXEL);
}
textLayout = new TextLayout(text, textAttributes, frc);
}
return textLayout;
}
@Override
public Rectangle2D.Double getBounds() {
TextLayout layout = getTextLayout();
Rectangle2D.Double r = new Rectangle2D.Double(origin.x, origin.y, layout.getAdvance(),
layout.getAscent() + layout.getDescent());
return r;
}
@Override
public Dimension2DDouble getPreferredSize() {
Rectangle2D.Double b = getBounds();
return new Dimension2DDouble(b.width, b.height);
}
@Override
public double getBaseline() {
TextLayout layout = getTextLayout();
return origin.y + layout.getAscent() - getBounds().y;
}
/**
* Gets the drawing area without taking the decorator into account.
*/
@Override
protected Rectangle2D.Double getFigureDrawingArea() {
if (getText() == null) {
return getBounds();
} else {
TextLayout layout = getTextLayout();
Rectangle2D.Double r = new Rectangle2D.Double(
origin.x, origin.y, layout.getAdvance(), layout.getAscent());
Rectangle2D lBounds = layout.getBounds();
if (!lBounds.isEmpty() && !Double.isNaN(lBounds.getX())) {
r.add(new Rectangle2D.Double(
lBounds.getX() + origin.x,
(lBounds.getY() + origin.y + layout.getAscent()),
lBounds.getWidth(),
lBounds.getHeight()));
}
// grow by two pixels to take antialiasing into account
Geom.grow(r, 2d, 2d);
return r;
}
}
@Override
public void restoreTransformTo(Object geometry) {
Point2D.Double p = (Point2D.Double) geometry;
origin.x = p.x;
origin.y = p.y;
}
@Override
public Object getTransformRestoreData() {
return origin.clone();
}
// ATTRIBUTES
/**
* Gets the text shown by the text figure.
*/
@Override
public String getText() {
return get(TEXT);
}
/**
* Sets the text shown by the text figure.
* This is a convenience method for calling {@code set(TEXT,newText)}.
*/
@Override
public void setText(String newText) {
set(TEXT, newText);
}
@Override
public int getTextColumns() {
//return (getText() == null) ? 4 : Math.max(getText().length(), 4);
return 4;
}
/**
* Gets the number of characters used to expand tabs.
*/
@Override
public int getTabSize() {
return 8;
}
@Override
public TextHolderFigure getLabelFor() {
return this;
}
@Override
public Insets2D.Double getInsets() {
return new Insets2D.Double();
}
@Override
public Font getFont() {
return AttributeKeys.getFont(this);
}
@Override
public Color getTextColor() {
return get(TEXT_COLOR);
}
@Override
public Color getFillColor() {
return get(FILL_COLOR);
}
@Override
public void setFontSize(float size) {
set(FONT_SIZE, new Double(size));
}
@Override
public float getFontSize() {
return get(FONT_SIZE).floatValue();
}
// EDITING
@Override
public boolean isEditable() {
return editable;
}
public void setEditable(boolean b) {
this.editable = b;
}
@Override
public Collection<Handle> createHandles(int detailLevel) {
LinkedList<Handle> handles = new LinkedList<Handle>();
switch (detailLevel) {
case -1:
handles.add(new BoundsOutlineHandle(this, false, true));
break;
case 0:
handles.add(new BoundsOutlineHandle(this));
handles.add(new MoveHandle(this, RelativeLocator.northWest()));
handles.add(new MoveHandle(this, RelativeLocator.northEast()));
handles.add(new MoveHandle(this, RelativeLocator.southWest()));
handles.add(new MoveHandle(this, RelativeLocator.southEast()));
handles.add(new FontSizeHandle(this));
break;
}
return handles;
}
/**
* Returns a specialized tool for the given coordinate.
* <p>Returns null, if no specialized tool is available.
*/
@Override
public Tool getTool(Point2D.Double p) {
if (isEditable() && contains(p)) {
TextEditingTool t = new TextEditingTool(this);
return t;
}
return null;
}
// CONNECTING
// COMPOSITE FIGURES
// CLONING
// EVENT HANDLING
@Override
public void invalidate() {
super.invalidate();
textLayout = null;
}
@Override
protected void validate() {
super.validate();
textLayout = null;
}
@Override
public void read(DOMInput in) throws IOException {
setBounds(
new Point2D.Double(in.getAttribute("x", 0d), in.getAttribute("y", 0d)),
new Point2D.Double(0, 0));
readAttributes(in);
readDecorator(in);
invalidate();
}
@Override
public void write(DOMOutput out) throws IOException {
Rectangle2D.Double b = getBounds();
out.addAttribute("x", b.x);
out.addAttribute("y", b.y);
writeAttributes(out);
writeDecorator(out);
}
@Override
public TextFigure clone() {
TextFigure that = (TextFigure) super.clone();
that.origin = (Point2D.Double) this.origin.clone();
that.textLayout = null;
return that;
}
@Override
public boolean isTextOverflow() {
return false;
}
}