/*
* org.openmicroscopy.shoola.util.roi.figures.MeasureTextArea
*
*------------------------------------------------------------------------------
* Copyright (C) 2006-2007 University of Dundee. All rights reserved.
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
*------------------------------------------------------------------------------
*/
package org.openmicroscopy.shoola.util.roi.figures;
//Java imports
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.text.CharacterIterator;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.StringTokenizer;
//Third-party libraries
import org.jhotdraw.draw.AbstractAttributedDecoratedFigure;
import org.jhotdraw.draw.AttributeKeys;
import org.jhotdraw.draw.FontSizeHandle;
import org.jhotdraw.draw.Handle;
import org.jhotdraw.draw.TextAreaTool;
import org.jhotdraw.draw.TextHolderFigure;
import org.jhotdraw.draw.Tool;
import org.jhotdraw.geom.Insets2D;
import org.jhotdraw.xml.DOMInput;
import org.jhotdraw.xml.DOMOutput;
//Application-internal dependencies
import org.openmicroscopy.shoola.util.roi.model.annotation.MeasurementAttributes;
/**
* Area with measurement.
*
* @author Jean-Marie Burel
* <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a>
* @author Donald MacDonald
* <a href="mailto:donald@lifesci.dundee.ac.uk">donald@lifesci.dundee.ac.uk</a>
* @version 3.0
* <small>
* (<b>Internal version:</b> $Revision: $Date: $)
* </small>
* @since OME3.0
*/
public class MeasureTextArea
extends AbstractAttributedDecoratedFigure
implements TextHolderFigure
{
/** The bounds of the area. */
private Rectangle2D.Double bounds = new Rectangle2D.Double();
/** Flag indicating if the area can be edited. */
private boolean editable = true;
/** Copies of the layout. */
transient private TextLayout textLayout;
/** Creates a new instance. */
public MeasureTextArea()
{
this(ROIFigure.DEFAULT_TEXT);
}
/**
* Creates a new instance.
*
* @param text The text to display.
*/
public MeasureTextArea(String text)
{
setText(text);
setAttribute(MeasurementAttributes.FONT_FACE, ROIFigure.DEFAULT_FONT);
setAttribute(MeasurementAttributes.FONT_SIZE,
new Double(ROIFigure.FONT_SIZE));
}
/**
* Overridden to draw the text.
* @see #drawText(Graphics2D)
*/
protected void drawText(Graphics2D g)
{
if (getText() != null || isEditable()) {
Font font = getFont();
boolean isUnderlined = MeasurementAttributes.FONT_UNDERLINE.get(
this);
Insets2D.Double insets = getInsets();
Rectangle2D.Double textRect = new Rectangle2D.Double(
bounds.x + insets.left,
bounds.y + insets.top,
bounds.width - insets.left - insets.right,
bounds.height - insets.top - insets.bottom
);
float leftMargin = (float) textRect.x;
float rightMargin = (float) Math.max(leftMargin + 1,
textRect.x + textRect.width);
float verticalPos = (float) textRect.y;
float maxVerticalPos = (float) (textRect.y + textRect.height);
if (leftMargin < rightMargin) {
float tabWidth = (float) (getTabSize() *
g.getFontMetrics(font).charWidth('m'));
float[] tabStops = new float[(int) (textRect.width / tabWidth)];
for (int i=0; i < tabStops.length; i++) {
tabStops[i] = (float) (textRect.x +
(int) (tabWidth * (i + 1)));
}
if (getText() != null) {
Shape savedClipArea = g.getClip();
g.clip(textRect);
String[] paragraphs = getText().split("\n");//Strings.split(getText(), '\n');
for (int i = 0; i < paragraphs.length; i++) {
if (paragraphs[i].length() == 0) paragraphs[i] = " ";
AttributedString as = new AttributedString(paragraphs[i]);
as.addAttribute(TextAttribute.FONT, font);
if (isUnderlined) {
as.addAttribute(TextAttribute.UNDERLINE,
TextAttribute.UNDERLINE_LOW_ONE_PIXEL);
}
int tabCount = new StringTokenizer(
paragraphs[i], "\t").countTokens() - 1;
verticalPos = drawParagraph(g, as.getIterator(),
verticalPos, maxVerticalPos, leftMargin,
rightMargin, tabStops, tabCount);
if (verticalPos > maxVerticalPos) {
break;
}
}
g.setClip(savedClipArea);
}
}
if (leftMargin >= rightMargin || verticalPos >
textRect.y + textRect.height) {
// g.setColor(Color.red);
//g.draw(new Line2D.Double(textRect.x, textRect.y + textRect.height - 1, textRect.x + textRect.width - 1, textRect.y + textRect.height - 1));
}
}
}
/**
* Draws a paragraph of text at the specified y location and returns
* the y position for the next paragraph.
*/
private float drawParagraph(Graphics2D g,
AttributedCharacterIterator styledText, float verticalPos,
float maxVerticalPos, float leftMargin, float rightMargin,
float[] tabStops, int tabCount)
{
// assume styledText is an AttributedCharacterIterator, and the number
// of tabs in styledText is tabCount
int[] tabLocations = new int[tabCount+1];
int i = 0;
for (char c = styledText.first(); c != CharacterIterator.DONE;
c = styledText.next()) {
if (c == '\t') {
tabLocations[i++] = styledText.getIndex();
}
}
tabLocations[tabCount] = styledText.getEndIndex() - 1;
// Now tabLocations has an entry for every tab's offset in
// the text. For convenience, the last entry is tabLocations
// is the offset of the last character in the text.
LineBreakMeasurer measurer = new LineBreakMeasurer(styledText,
getFontRenderContext());
int currentTab = 0;
while (measurer.getPosition() < styledText.getEndIndex() &&
verticalPos <= maxVerticalPos) {
// Lay out and draw each line. All segments on a line
// must be computed before any drawing can occur, since
// we must know the largest ascent on the line.
// TextLayouts are computed and stored in a List;
// their horizontal positions are stored in a parallel
// List.
// lineContainsText is true after first segment is drawn
boolean lineContainsText = false;
boolean lineComplete = false;
float maxAscent = 0, maxDescent = 0;
float horizontalPos = leftMargin;
LinkedList<TextLayout> layouts = new LinkedList<TextLayout>();
LinkedList<Float> penPositions = new LinkedList<Float>();
while (!lineComplete && verticalPos <= maxVerticalPos) {
float wrappingWidth = rightMargin - horizontalPos;
TextLayout layout = null;
layout =
measurer.nextLayout(wrappingWidth,
tabLocations[currentTab]+1,
lineContainsText);
// layout can be null if lineContainsText is true
if (layout != null) {
layouts.add(layout);
penPositions.add(horizontalPos);
horizontalPos += layout.getAdvance();
maxAscent = Math.max(maxAscent, layout.getAscent());
maxDescent = Math.max(maxDescent,
layout.getDescent() + layout.getLeading());
} else {
lineComplete = true;
}
lineContainsText = true;
if (measurer.getPosition() == tabLocations[currentTab]+1) {
currentTab++;
}
if (measurer.getPosition() == styledText.getEndIndex())
lineComplete = true;
else if (tabStops.length == 0 || horizontalPos >=
tabStops[tabStops.length-1])
lineComplete = true;
if (!lineComplete) {
// move to next tab stop
int j;
for (j=0; horizontalPos >= tabStops[j]; j++) {}
horizontalPos = tabStops[j];
}
}
verticalPos += maxAscent;
Iterator<TextLayout> layoutEnum = layouts.iterator();
Iterator<Float> positionEnum = penPositions.iterator();
// now iterate through layouts and draw them
while (layoutEnum.hasNext()) {
TextLayout nextLayout = layoutEnum.next();
float nextPosition = positionEnum.next();
nextLayout.draw(g, nextPosition, verticalPos);
}
verticalPos += maxDescent;
}
return verticalPos;
}
protected void drawFill(Graphics2D g) {
g.fill(bounds);
}
protected void drawStroke(Graphics2D g) {
//g.draw(bounds);
}
// SHAPE AND BOUNDS
public void basicSetBounds(Point2D.Double anchor, Point2D.Double lead) {
bounds.x = Math.min(anchor.x, lead.x);
bounds.y = Math.min(anchor.y, lead.y);
bounds.width = Math.max(1, Math.abs(lead.x - anchor.x));
bounds.height = Math.max(1, Math.abs(lead.y - anchor.y));
textLayout = null;
}
public void basicTransform(AffineTransform tx) {
Point2D.Double anchor = getStartPoint();
Point2D.Double lead = getEndPoint();
basicSetBounds(
(Point2D.Double) tx.transform(anchor, anchor),
(Point2D.Double) tx.transform(lead, lead)
);
}
public boolean figureContains(Point2D.Double p) {
return bounds.contains(p);
}
public Rectangle2D.Double getBounds() {
return (Rectangle2D.Double) bounds.getBounds2D();
}
public void restoreTransformTo(Object geometry) {
Rectangle2D.Double r = (Rectangle2D.Double) geometry;
bounds.x = r.x;
bounds.y = r.y;
bounds.width = r.width;
bounds.height = r.height;
}
public Object getTransformRestoreData() {
return bounds.clone();
}
// ATTRIBUTES
/**
* Gets the text shown by the text figure.
*/
public String getText() {
return (String) getAttribute(MeasurementAttributes.TEXT);
}
/**
* Returns the insets used to draw text.
*/
public Insets2D.Double getInsets() {
double sw = Math.ceil(MeasurementAttributes.STROKE_WIDTH.get(this) / 2);
Insets2D.Double insets = new Insets2D.Double(4,4,4,4);
return new Insets2D.Double(insets.top+sw,insets.left+sw,insets.bottom+sw,insets.right+sw);
}
public int getTabSize() {
return 8;
}
/**
* Sets the text shown by the text figure.
*/
public void setText(String newText) {
setAttribute(MeasurementAttributes.TEXT, newText);
}
public int getTextColumns() {
return (getText() == null) ? 4 : Math.max(getText().length(), 4);
}
public Font getFont() {
return AttributeKeys.getFont(this);
}
public Color getTextColor() {
return MeasurementAttributes.TEXT_COLOR.get(this);
}
public Color getFillColor() {
return MeasurementAttributes.FILL_COLOR.get(this);
}
public void setFontSize(float size) {
MeasurementAttributes.FONT_SIZE.set(this, new Double(size));
}
public float getFontSize() {
return MeasurementAttributes.FONT_SIZE.get(this).floatValue();
}
// EDITING
public boolean isEditable() { return editable; }
public void setEditable(boolean b) { this.editable = b; }
/**
* Returns a specialized tool for the given coordinate.
* <p>Returns null, if no specialized tool is available.
*/
public Tool getTool(Point2D.Double p) {
return (isEditable() && contains(p)) ? new TextAreaTool(this) : null;
}
public TextHolderFigure getLabelFor() { return this; }
// CONNECTING
// COMPOSITE FIGURES
// CLONING
public MeasureTextArea clone() {
MeasureTextArea that = (MeasureTextArea) super.clone();
that.bounds = (Rectangle2D.Double) this.bounds.clone();
return that;
}
// EVENT HANDLING
public Collection<Handle> createHandles(int detailLevel) {
LinkedList<Handle> handles = (LinkedList<Handle>)
super.createHandles(detailLevel);
if (detailLevel == 0) {
handles.add(new FontSizeHandle(this));
}
return handles;
}
protected void validate() {
super.validate();
textLayout = null;
}
protected void readBounds(DOMInput in) throws IOException {
bounds.x = in.getAttribute("x",0d);
bounds.y = in.getAttribute("y",0d);
bounds.width = in.getAttribute("w",0d);
bounds.height = in.getAttribute("h",0d);
}
protected void writeBounds(DOMOutput out) throws IOException {
out.addAttribute("x",bounds.x);
out.addAttribute("y",bounds.y);
out.addAttribute("w",bounds.width);
out.addAttribute("h",bounds.height);
}
public void read(DOMInput in) throws IOException {
readBounds(in);
readAttributes(in);
textLayout = null;
}
public void write(DOMOutput out) throws IOException {
writeBounds(out);
writeAttributes(out);
}
/* (non-Javadoc)
* @see org.jhotdraw.draw.TextHolderFigure#isTextOverflow()
*/
public boolean isTextOverflow() { return false; }
/* (non-Javadoc)
* @see org.jhotdraw.draw.Figure#transform(java.awt.geom.AffineTransform)
*/
public void transform(AffineTransform tx) { }
}