/*
* org.openmicroscopy.shoola.util.roi.figures.MeasureEllipseFigure
*
*------------------------------------------------------------------------------
* Copyright (C) 2006-2014 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;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;
import org.jhotdraw.draw.AbstractAttributedFigure;
import org.jhotdraw.draw.AttributeKeys;
import org.jhotdraw.draw.FigureListener;
import org.openmicroscopy.shoola.util.roi.model.annotation.AnnotationKeys;
import org.openmicroscopy.shoola.util.roi.model.annotation.MeasurementAttributes;
import org.openmicroscopy.shoola.util.roi.figures.ROIFigure;
import org.openmicroscopy.shoola.util.roi.model.ROI;
import org.openmicroscopy.shoola.util.roi.model.ROIShape;
import org.openmicroscopy.shoola.util.roi.model.util.MeasurementUnits;
import org.openmicroscopy.shoola.util.roi.model.util.UnitPoint;
import org.openmicroscopy.shoola.util.ui.UIUtilities;
import org.openmicroscopy.shoola.util.ui.drawingtools.figures.EllipseTextFigure;
import org.openmicroscopy.shoola.util.ui.drawingtools.figures.FigureUtil;
import omero.model.Length;
import omero.model.LengthI;
import omero.model.enums.UnitsLength;
/**
* Ellipse figure 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
* @since OME3.0
*/
public class MeasureEllipseFigure
extends EllipseTextFigure
implements ROIFigure
{
/** Flag indicating the figure can/cannot be deleted.*/
private boolean deletable;
/** Flag indicating the figure can/cannot be annotated.*/
private boolean annotatable;
/** Flag indicating the figure can/cannot be edited.*/
private boolean editable;
/** Is this figure read only. */
private boolean readOnly;
/** Is this figure a client object. */
private boolean clientObject;
/** has the figure been modified. */
private boolean dirty;
/** Bounds of the measurement. */
private Rectangle2D measurementBounds;
/** The ROI containing the ROIFigure which in turn contains this Figure. */
protected ROI roi;
/** The ROIFigure contains this Figure. */
protected ROIShape shape;
/** The Measurement units, and values of the image. */
private MeasurementUnits units;
/**
* The status of the figure i.e. {@link ROIFigure#IDLE} or
* {@link ROIFigure#MOVING}.
*/
private int status;
/** Flag indicating if the user can move or resize the shape.*/
private boolean interactable;
/** Creates a new instance. */
public MeasureEllipseFigure()
{
this(DEFAULT_TEXT);
}
/**
* Creates a new instance.
*
* @param text The text of the ellipse.
*/
public MeasureEllipseFigure(String text)
{
this(text, 0, 0, 0, 0, false, true, true, true, true);
}
/**
* Creates a new instance.
*
* @param readOnly Pass <code>true</code> if the ROI is read only,
* <code>false</code> otherwise.
* @param isClientObject the figure is a client object.
* @param editable Flag indicating the figure can/cannot be edited.
* @param deletable Flag indicating the figure can/cannot be deleted.
* @param annotatable Flag indicating the figure can/cannot be annotated.
*/
public MeasureEllipseFigure(boolean readOnly, boolean isClientObject,
boolean editable, boolean deletable, boolean annotatable)
{
this(ROIFigure.DEFAULT_TEXT, 0, 0, 0, 0, readOnly, isClientObject,
editable, deletable, annotatable);
}
/**
* Creates a new instance.
*
* @param x The x-coordinate of the figure.
* @param y The y-coordinate of the figure.
* @param width The width of the figure.
* @param height The height of the figure.
*/
public MeasureEllipseFigure(double x, double y, double width, double height)
{
this(ROIFigure.DEFAULT_TEXT, x, y, width, height, false, true,
true, true, true);
}
/**
* Creates a new instance.
*
* @param text The text of the ellipse.
* @param x The x-coordinate of the figure.
* @param y The y-coordinate of the figure.
* @param width The width of the figure.
* @param height The height of the figure.
* @param readOnly The figure is read only.
* @param clientObject the figure is a client object.
* @param editable Flag indicating the figure can/cannot be edited.
* @param deletable Flag indicating the figure can/cannot be deleted.
* @param annotatable Flag indicating the figure can/cannot be annotated.
*/
public MeasureEllipseFigure(String text, double x, double y, double width,
double height, boolean readOnly, boolean clientObject,
boolean editable, boolean deletable, boolean annotatable)
{
super(text, x, y, width, height);
setAttributeEnabled(MeasurementAttributes.TEXT_COLOR, true);
setAttribute(MeasurementAttributes.FONT_FACE, DEFAULT_FONT);
setAttribute(MeasurementAttributes.FONT_SIZE, new Double(FONT_SIZE));
setAttribute(MeasurementAttributes.SCALE_PROPORTIONALLY, Boolean.FALSE);
shape = null;
roi = null;
status = IDLE;
setReadOnly(readOnly);
setClientObject(clientObject);
this.deletable = deletable;
this.annotatable = annotatable;
this.editable = editable;
interactable = true;
}
/**
* Returns the x-coordinate of the figure,
* convert to the size in the reference units
*
* @return See above.
*/
public Length getMeasurementX()
{
return transformX(getX());
}
/**
* Returns the y-coordinate of the figure,
* convert to the size in the reference units
*
* @return See above.
*/
public Length getMeasurementY()
{
return transformX(getY());
}
/**
* Returns the width of the figure,
* convert to microns if <code>isInMicrons</code> is <code>true</code>.
*
* @return See above.
*/
public Length getMeasurementWidth()
{
return transformX(getWidth());
}
/**
* Returns the height of the figure,
* convert to microns if <code>isInMicrons</code> is <code>true</code>.
*
* @return See above.
*/
public Length getMeasurementHeight()
{
return transformX(getHeight());
}
/**
* Returns the centre of the figure,
* convert to microns if <code>isInMicrons</code> is <code>true</code>.
*
* @return See above.
*/
public UnitPoint getMeasurementCentre()
{
Point2D p = getCentre();
return new UnitPoint(transformX(p.getX()), transformY(p.getY()));
}
/**
* Returns the y-coordinate of the figure.
*
* @return See above.
*/
public double getX()
{
AffineTransform t = AttributeKeys.TRANSFORM.get(this);
if (t == null) return ellipse.getX();
Point2D src = new Point2D.Double(ellipse.getX(), ellipse.getY());
Point2D dest = new Point2D.Double();
t.transform(src, dest);
return dest.getX();
}
/**
* Returns the y-coordinate of the figure.
*
* @return See above.
*/
public double getY()
{
AffineTransform t = AttributeKeys.TRANSFORM.get(this);
if (t == null) return ellipse.getY();
Point2D src = new Point2D.Double(ellipse.getX(), ellipse.getY());
Point2D dest = new Point2D.Double();
t.transform(src, dest);
return dest.getY();
}
/**
* Returns the width of the figure.
*
* @return See above.
*/
public double getWidth() { return super.getWidth(); }
/**
* Returns the height of the figure.
*
* @return See above.
*/
public double getHeight() { return super.getHeight(); }
/**
* Draws the figure on the graphics context.
*
* @param g The graphics context.
*/
public void draw(Graphics2D g)
{
super.draw(g);
if (MeasurementAttributes.SHOWMEASUREMENT.get(this) ||
MeasurementAttributes.SHOWID.get(this))
{
Length a = getArea();
String ellipseArea = UIUtilities.formatValue(a, true);
Double sz = (Double) getAttribute(MeasurementAttributes.FONT_SIZE);
Font font = (Font) getAttribute(MeasurementAttributes.FONT_FACE);
if (font != null) g.setFont(font.deriveFont(sz.floatValue()));
else {
g.setFont(new Font(FONT_FAMILY, FONT_STYLE, sz.intValue()));
}
Rectangle2D stringBoundsbounds =
g.getFontMetrics().getStringBounds(ellipseArea, g);
measurementBounds =
new Rectangle2D.Double(getCentreX()
-stringBoundsbounds.getWidth()/2, this.getCentreY()
+stringBoundsbounds.getHeight()/2,
stringBoundsbounds.getWidth(),
stringBoundsbounds.getHeight());
if (MeasurementAttributes.SHOWMEASUREMENT.get(this))
{
g.setColor(
MeasurementAttributes.MEASUREMENTTEXT_COLOUR.get(this));
g.drawString(ellipseArea,
(int) measurementBounds.getX(),
(int) measurementBounds.getY());
}
if (MeasurementAttributes.SHOWID.get(this))
{
g.setColor(this.getTextColor());
measurementBounds =
g.getFontMetrics().getStringBounds(getROI().getID()+"", g);
measurementBounds = new Rectangle2D.Double(
getBounds().getCenterX()-
measurementBounds.getWidth()/2,
getBounds().getCenterY()+
measurementBounds.getHeight()/2,
measurementBounds.getWidth(),
measurementBounds.getHeight());
g.drawString(this.getROI().getID()+"",
(int) measurementBounds.getX(),
(int) measurementBounds.getY());
}
}
}
/**
* Calculates the bounds of the rendered figure, including the text
* rendered.
*
* @return See above.
*/
public Rectangle2D.Double getDrawingArea()
{
Rectangle2D.Double newBounds=super.getDrawingArea();
if (measurementBounds != null)
{
if (newBounds.getX() > measurementBounds.getX())
{
double diff = newBounds.x-measurementBounds.getX();
newBounds.x = measurementBounds.getX();
newBounds.width = newBounds.width+diff;
}
if (newBounds.getY() > measurementBounds.getY())
{
double diff = newBounds.y-measurementBounds.getY();
newBounds.y = measurementBounds.getY();
newBounds.height = newBounds.height+diff;
}
if (measurementBounds.getX()+
measurementBounds.getWidth() > newBounds.getX()
+newBounds.getWidth())
{
double diff =
measurementBounds.getX()+
measurementBounds.getWidth()-newBounds.getX()
+newBounds.getWidth();
newBounds.width = newBounds.width+diff;
}
if (measurementBounds.getY()+
measurementBounds.getHeight() > newBounds.getY()
+newBounds.getHeight())
{
double diff =
measurementBounds.getY()+
measurementBounds.getHeight()-newBounds.getY()
+newBounds.getHeight();
newBounds.height = newBounds.height+diff;
}
}
return newBounds;
}
/**
* Calculates the area of the figure.
*
* @return see above.
*/
public Length getArea()
{
Length h = getMeasurementHeight();
Length w = getMeasurementWidth();
double value = (h.getValue()/2)*(w.getValue()/2)*Math.PI;
return new LengthI(value, getUnit());
}
/**
* Calculates the perimeter of the figure.
*
* @return see above.
*/
public Length getPerimeter()
{
double value;
Length h = getMeasurementHeight();
Length w = getMeasurementWidth();
if (w.getValue() == h.getValue())
value = w.getValue()*2*Math.PI;
else {
double a = Math.max(w.getValue(), h.getValue());
double b = Math.min(w.getValue(), h.getValue());
// approximation of c for ellipse.
value = Math.PI*(3*a+3*b-Math.sqrt((a+3*b)*(b+3*a)));
}
return new LengthI(value, getUnit());
}
/**
* Calculate the centre of the figure.
* @return see above.
*/
public Point2D getCentre()
{
return new Point2D.Double(Math.round(getCentreX()), Math
.round(getCentreY()));
}
/**
* Implemented as specified by the {@link ROIFigure} interface.
* @see ROIFigure#getROI()
*/
public ROI getROI() { return roi; }
/**
* Implemented as specified by the {@link ROIFigure} interface.
* @see ROIFigure#getROIShape()
*/
public ROIShape getROIShape() { return shape; }
/**
* Implemented as specified by the {@link ROIFigure} interface.
* @see ROIFigure#setROI(ROI)
*/
public void setROI(ROI roi) { this.roi = roi; }
/**
* Implemented as specified by the {@link ROIFigure} interface.
* @see ROIFigure#setROIShape(ROIShape)
*/
public void setROIShape(ROIShape shape) { this.shape = shape; }
/**
* Implemented as specified by the {@link ROIFigure} interface.
* @see ROIFigure#getType()
*/
public void calculateMeasurements()
{
if (shape == null) return;
AnnotationKeys.AREA.set(shape, getArea());
AnnotationKeys.HEIGHT.set(shape, getMeasurementHeight());
AnnotationKeys.WIDTH.set(shape, getMeasurementWidth());
AnnotationKeys.PERIMETER.set(shape, getPerimeter());
AnnotationKeys.CENTREX.set(shape, getMeasurementCentre().x);
AnnotationKeys.CENTREY.set(shape, getMeasurementCentre().y);
}
/**
* Implemented as specified by the {@link ROIFigure} interface.
* @see ROIFigure#getType()
*/
public String getType() { return FigureUtil.ELLIPSE_TYPE; }
/**
* Implemented as specified by the {@link ROIFigure} interface.
* @see ROIFigure#setMeasurementUnits(MeasurementUnits)
*/
public void setMeasurementUnits(MeasurementUnits units)
{
this.units = units;
}
/**
* Implemented as specified by the {@link ROIFigure} interface.
* @see ROIFigure#getPoints()
*/
public List<Point> getPoints()
{
Shape transformedEllipse = getTransformedShape();
Rectangle2D r = transformedEllipse.getBounds2D();
//getP
List<Point> vector = new ArrayList<Point>
((int) r.getHeight()*(int) r.getWidth());
double xEnd = (r.getX()+r.getWidth());
double yEnd = (r.getY()+r.getHeight());
double startX = r.getX();
double startY = r.getY();
double x, y;
for (y = startY; y < yEnd; ++y)
for (x = startX; x < xEnd; ++x)
// if(containsMapped(x,y))
if (transformedEllipse.intersects(x, y, 0.001, 0.001))
vector.add(new Point((int) x, (int) y));
return vector;
}
/**
* Implemented as specified by the {@link ROIFigure} interface.
* @see ROIFigure#getSize()
*/
public int getSize()
{
Shape transformedEllipse = getTransformedShape();
Rectangle2D r = transformedEllipse.getBounds2D();
//getP
int total = 0;
double xEnd = (r.getX()+r.getWidth());
double yEnd = (r.getY()+r.getHeight());
double startX = r.getX();
double startY = r.getY();
double x, y;
for (y = startY; y < yEnd; ++y)
for (x = startX; x < xEnd; ++x)
if (transformedEllipse.intersects(x, y, 0.001, 0.001))
total++;
return total;
}
/**
* Overridden to stop updating shape if read only
* @see AbstractAttributedFigure#transform(AffineTransform)
*/
public void transform(AffineTransform tx)
{
if (!readOnly && interactable)
{
this.setObjectDirty(true);
super.transform(tx);
}
}
/**
* Overridden to stop updating shape if read only.
* @see AbstractAttributedFigure#setBounds(Point2D.Double, Point2D.Double)
*/
public void setBounds(Point2D.Double anchor, Point2D.Double lead)
{
if (!readOnly && interactable)
{
this.setObjectDirty(true);
super.setBounds(anchor, lead);
}
}
/**
* Overridden to return the correct handles.
* @see AbstractAttributedFigure#createHandles(int)
*/
/* cannot do that otherwise enter in an infinite loop
public Collection<Handle> createHandles(int detailLevel)
{
if (!readOnly)
return super.createHandles(detailLevel);
else
{
LinkedList<Handle> handles = new LinkedList<Handle>();
handles.add(new FigureSelectionHandle(this));
return handles;
}
}
*/
/**
* Invalidate the figure and remove the cachedTransformedShape, this means
* that the figures geometry has changed and it should be redrawn.
* @see AbstractAttributedFigure#invalidate()
*/
public void invalidate()
{
if (!readOnly)
super.invalidate();
}
/**
* Implemented as specified by the {@link ROIFigure} interface.
* @see ROIFigure#setStatus(int)
*/
public void setStatus(int status) { this.status = status; }
/**
* Implemented as specified by the {@link ROIFigure} interface.
* @see ROIFigure#getStatus()
*/
public int getStatus() { return status; }
/**
* Implemented as specified by the {@link ROIFigure} interface.
* @see ROIFigure#isReadOnly()
*/
public boolean isReadOnly() { return readOnly;}
/**
* Implemented as specified by the {@link ROIFigure} interface.
* @see ROIFigure#setReadOnly(boolean)
*/
public void setReadOnly(boolean readOnly)
{
this.readOnly = readOnly;
setEditable(!readOnly);
}
/**
* Implemented as specified by the {@link ROIFigure} interface
* @see ROIFigure#isClientObject()
*/
public boolean isClientObject() { return clientObject; }
/**
* Implemented as specified by the {@link ROIFigure} interface
* @see ROIFigure#setClientObject(boolean)
*/
public void setClientObject(boolean clientSide)
{
clientObject = clientSide;
}
/**
* Implemented as specified by the {@link ROIFigure} interface
* @see ROIFigure#isDirty()
*/
public boolean isDirty() { return dirty; }
/**
* Implemented as specified by the {@link ROIFigure} interface
* @see ROIFigure#setObjectDirty(boolean)
*/
public void setObjectDirty(boolean dirty) { this.dirty = dirty; }
/**
* Overridden to set the various flags.
* @see MeasureEllipseFigure#clone()
*/
public MeasureEllipseFigure clone()
{
MeasureEllipseFigure that = (MeasureEllipseFigure) super.clone();
that.setReadOnly(this.isReadOnly());
that.setClientObject(this.isClientObject());
that.setObjectDirty(true);
that.setInteractable(true);
return that;
}
/**
* Overridden to mark the object has dirty.
* @see MeasureEllipseFigure#setText(String)
*/
public void setText(String text)
{
super.setText(text);
this.setObjectDirty(true);
}
/**
* Implemented as specified by the {@link ROIFigure} interface
* @see ROIFigure#getFigureListeners()
*/
public List<FigureListener> getFigureListeners()
{
List<FigureListener> figListeners = new ArrayList<FigureListener>();
Object[] listeners = listenerList.getListenerList();
for (Object listener : listeners)
if (listener instanceof FigureListener)
figListeners.add((FigureListener) listener);
return figListeners;
}
/**
* Implemented as specified by the {@link ROIFigure} interface
* @see ROIFigure#canAnnotate()
*/
public boolean canAnnotate() { return annotatable; }
/**
* Implemented as specified by the {@link ROIFigure} interface
* @see ROIFigure#canDelete()
*/
public boolean canDelete() { return deletable; }
/**
* Implemented as specified by the {@link ROIFigure} interface
* @see ROIFigure#canAnnotate()
*/
public boolean canEdit() { return editable; }
/**
* Implemented as specified by the {@link ROIFigure} interface
* @see ROIFigure#setInteractable(boolean)
*/
public void setInteractable(boolean interactable)
{
this.interactable = interactable;
}
/**
* Implemented as specified by the {@link ROIFigure} interface
* @see ROIFigure#canInteract()
*/
public boolean canInteract() { return interactable; }
/**
* Transforms the given x pixel value into a unit object
* @param x A pixel value in x direction
*/
private Length transformX(double x) {
return transformX((int)x);
}
/**
* Transforms the given y pixel value into a unit object
* @param y A pixel value in y direction
*/
private Length transformY(double y) {
return transformY((int)y);
}
/**
* Transforms the given x pixel value into a unit object
* @param x A pixel value in x direction
*/
private Length transformX(int x) {
if(units.getPixelSizeX()!=null)
return new LengthI(x*units.getPixelSizeX().getValue(), units.getPixelSizeX().getUnit());
else
return new LengthI(x, UnitsLength.PIXEL);
}
/**
* Transforms the given x pixel value into a unit object
* @param x A pixel value in x direction
*/
private Length transformY(int y) {
if(units.getPixelSizeY()!=null)
return new LengthI(y*units.getPixelSizeY().getValue(), units.getPixelSizeY().getUnit());
else
return new LengthI(y, UnitsLength.PIXEL);
}
/**
* Get the unit which is used for the pixel sizes
*/
private UnitsLength getUnit() {
if(units.getPixelSizeX()!=null)
return units.getPixelSizeX().getUnit();
else
return UnitsLength.PIXEL;
}
}