/*******************************************************************************
* Copyright (c) 2016 Weasis Team and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Nicolas Roduit - initial API and implementation
*******************************************************************************/
package org.weasis.core.ui.model.graphic;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.TreeMap;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlIDREF;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.weasis.core.api.gui.Image2DViewer;
import org.weasis.core.api.gui.util.ActionW;
import org.weasis.core.api.gui.util.DecFormater;
import org.weasis.core.api.gui.util.GeomUtil;
import org.weasis.core.api.gui.util.MathUtil;
import org.weasis.core.api.image.util.Unit;
import org.weasis.core.ui.editor.image.ViewCanvas;
import org.weasis.core.ui.model.layer.GraphicLayer;
import org.weasis.core.ui.model.layer.LayerType;
import org.weasis.core.ui.model.utils.bean.AdvancedShape;
import org.weasis.core.ui.model.utils.bean.MeasureItem;
import org.weasis.core.ui.model.utils.bean.Measurement;
import org.weasis.core.ui.model.utils.exceptions.InvalidShapeException;
import org.weasis.core.ui.model.utils.imp.DefaultGraphicLabel;
import org.weasis.core.ui.model.utils.imp.DefaultUUID;
import org.weasis.core.ui.serialize.ColorModelAdapter;
import org.weasis.core.ui.serialize.PointAdapter;
import org.weasis.core.ui.util.MouseEventDouble;
@XmlAccessorType(XmlAccessType.NONE)
public abstract class AbstractGraphic extends DefaultUUID implements Graphic {
private static final long serialVersionUID = -8152071576417041112L;
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractGraphic.class);
protected static final String NULL_MSG = "Null is not allowed"; //$NON-NLS-1$
protected Integer pointNumber;
protected List<Point2D.Double> pts;
protected Paint colorPaint = DEFAULT_COLOR;
protected Float lineThickness = DEFAULT_LINE_THICKNESS;
protected Boolean labelVisible = DEFAULT_LABEL_VISISIBLE;
protected Boolean filled = DEFAULT_FILLED;
protected Integer classID;
protected GraphicLabel graphicLabel;
protected LayerType layerType = LayerType.DRAW;
protected Shape shape;
protected Boolean selected = DEFAULT_SELECTED;
protected Boolean variablePointsNumber = Boolean.FALSE;
protected PropertyChangeSupport pcs = new PropertyChangeSupport(this);
private GraphicLayer layer;
public AbstractGraphic(Integer pointNumber) {
setPointNumber(pointNumber);
this.variablePointsNumber = Objects.isNull(pointNumber) || pointNumber < 0;
setPts(null);
}
public AbstractGraphic(AbstractGraphic graphic) {
this.layerType = graphic.layerType;
setPointNumber(graphic.pointNumber);
setColorPaint(graphic.colorPaint);
setLineThickness(graphic.lineThickness);
setLabelVisible(graphic.labelVisible);
setFilled(graphic.filled);
setClassID(graphic.classID);
this.graphicLabel = graphic.graphicLabel == null ? null : graphic.graphicLabel.copy();
this.variablePointsNumber = Objects.isNull(graphic.pointNumber) || graphic.pointNumber < 0;
List<Point2D.Double> ptsList = graphic.pts.stream().filter(Objects::nonNull)
.map(g -> (Point2D.Double) g.clone()).collect(Collectors.toList());
try {
initCopy(graphic);
buildGraphic(ptsList);
} catch (InvalidShapeException e) {
LOGGER.error("Building graphic", e); //$NON-NLS-1$
}
}
@Override
public String toString() {
return getUIName();
}
/**
* Returns the total number of points. If the value is null or negative then return 10 as the default value
*
* @return total number of points
*/
@Override
public Integer getPtsNumber() {
return pointNumber;
}
@Override
public void setPointNumber(Integer pointNumber) {
Objects.requireNonNull(pointNumber, NULL_MSG);
this.pointNumber = pointNumber;
}
@XmlElementWrapper(name = "pts", required = false)
@XmlElement(name = "pt")
@XmlJavaTypeAdapter(PointAdapter.Point2DAdapter.class)
@Override
public List<Point2D.Double> getPts() {
return pts;
}
@Override
public void setPts(List<Point2D.Double> pts) {
this.pts = Optional.ofNullable(pts).orElseGet(
() -> new ArrayList<>(Optional.ofNullable(getPtsNumber()).filter(v -> v >= 0).orElse(DEFAULT_PTS_SIZE)));
}
@Override
public Graphic buildGraphic(List<Point2D.Double> pts) throws InvalidShapeException {
setPts(pts);
if (!pts.isEmpty()) {
prepareShape();
}
return this;
}
protected abstract void prepareShape() throws InvalidShapeException;
protected void initCopy(Graphic graphic) {
// Do noting at this level. Final graphics with new serializable fields must implement this method
}
@Override
public Boolean getVariablePointsNumber() {
return variablePointsNumber;
}
@Override
public void setVariablePointsNumber(Boolean variablePointsNumber) {
Objects.requireNonNull(variablePointsNumber, NULL_MSG);
this.variablePointsNumber = variablePointsNumber;
}
@XmlElement(name = "paint", required = false)
@XmlJavaTypeAdapter(ColorModelAdapter.PaintAdapter.class)
@Override
public Paint getColorPaint() {
return colorPaint;
}
public void setColorPaint(Paint colorPaint) {
this.colorPaint = Optional.ofNullable(colorPaint).orElse(DEFAULT_COLOR);
}
@XmlAttribute(name = "thickness", required = false)
@Override
public Float getLineThickness() {
return lineThickness;
}
@Override
public void setLineThickness(Float lineThickness) {
if (!Objects.equals(this.lineThickness, lineThickness)) {
this.lineThickness = Optional.ofNullable(lineThickness).orElse(DEFAULT_LINE_THICKNESS);
if (shape instanceof AdvancedShape) {
((AdvancedShape) shape).getShapeList().stream()
.forEachOrdered(bs -> bs.changelineThickness(lineThickness));
}
fireDrawingChanged();
}
}
@XmlAttribute(name = "showLabel", required = false)
@Override
public Boolean getLabelVisible() {
return labelVisible;
}
@Override
public void setLabelVisible(Boolean labelVisible) {
if (!Objects.equals(this.labelVisible, labelVisible)) {
this.labelVisible = Optional.ofNullable(labelVisible).orElse(DEFAULT_LABEL_VISISIBLE);
fireLabelChanged();
}
}
@XmlAttribute(name = "fill", required = false)
@Override
public Boolean getFilled() {
return filled;
}
@Override
public void setFilled(Boolean filled) {
if (!Objects.equals(this.filled, filled) && this instanceof GraphicArea) {
this.filled = Optional.ofNullable(filled).orElse(DEFAULT_FILLED);
fireDrawingChanged();
}
}
@XmlAttribute(name = "classId", required = false)
@Override
public Integer getClassID() {
return classID;
}
@Override
public void setClassID(Integer classID) {
this.classID = classID;
}
@Override
public Shape getShape() {
return shape;
}
@Override
public Boolean getSelected() {
return selected;
}
@Override
public void setSelected(Boolean selected) {
if (!Objects.equals(this.selected, selected)) {
this.selected = Optional.ofNullable(selected).orElse(DEFAULT_SELECTED);
fireDrawingChanged();
fireLabelChanged();
}
}
@XmlElement(name = "graphicLabel", required = false)
@Override
public GraphicLabel getGraphicLabel() {
return graphicLabel;
}
public void setGraphicLabel(GraphicLabel graphicLabel) {
this.graphicLabel = graphicLabel;
}
@Override
public void setLayer(GraphicLayer layer) {
Objects.requireNonNull(layer, NULL_MSG);
this.layer = layer;
// Adapt the default layerType
setLayerType(layer.getType());
}
@XmlIDREF
@XmlElement(name = "layer")
@Override
public GraphicLayer getLayer() {
return layer;
}
@Override
public String getDescription() {
return ""; //$NON-NLS-1$
}
@Override
public Area getArea(AffineTransform transform) {
if (Objects.isNull(shape)) {
return new Area();
}
if (shape instanceof AdvancedShape) {
return ((AdvancedShape) shape).getArea(transform);
} else {
double growingSize = Math.max(SELECTION_SIZE, HANDLE_SIZE);
growingSize = Math.max(growingSize, lineThickness);
growingSize /= GeomUtil.extractScalingFactor(transform);
Stroke boundingStroke = new BasicStroke((float) growingSize, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
return new Area(boundingStroke.createStrokedShape(shape));
}
}
@Override
public Boolean intersects(Rectangle rectangle, AffineTransform transform) {
return Optional.ofNullable(rectangle).map(getArea(transform)::intersects).orElse(false);
}
@Override
public Rectangle getBounds(AffineTransform transform) {
if (Objects.isNull(shape)) {
return null;
}
if (shape instanceof AdvancedShape) {
((AdvancedShape) shape).setAffineTransform(transform);
}
Rectangle2D bounds = shape.getBounds2D();
double growingSize = lineThickness / 2.0;
growingSize /= GeomUtil.extractScalingFactor(transform);
GeomUtil.growRectangle(bounds, growingSize);
return Optional.ofNullable(bounds).map(b -> b.getBounds()).orElse(null);
}
@Override
public Rectangle getRepaintBounds(AffineTransform transform) {
return getRepaintBounds(shape, transform);
}
@Override
public Rectangle getTransformedBounds(Shape shape, AffineTransform transform) {
Rectangle rectangle = getRepaintBounds(shape, transform);
if (Objects.nonNull(transform) && Objects.nonNull(rectangle)) {
rectangle = transform.createTransformedShape(rectangle).getBounds();
}
return rectangle;
}
@Override
public Rectangle getTransformedBounds(GraphicLabel label, AffineTransform transform) {
return Optional.ofNullable(label).map(l -> l.getTransformedBounds(transform).getBounds()).orElse(null);
}
@Override
public void setLabel(String[] labels, ViewCanvas<?> view2d) {
Consumer<Shape> applyShape = s -> {
Rectangle2D rect;
if (s instanceof AdvancedShape && !((AdvancedShape) s).shapeList.isEmpty()) {
// Assuming first shape is the user drawing path, else stands for decoration
Shape generalPath = ((AdvancedShape) s).shapeList.get(0).getShape();
rect = generalPath.getBounds2D();
} else {
rect = s.getBounds2D();
}
double xPos = rect.getX() + rect.getWidth() + 3;
double yPos = rect.getY() + rect.getHeight() * 0.5;
this.setLabel(labels, view2d, new Point2D.Double(xPos, yPos));
};
Optional.ofNullable(shape).ifPresent(applyShape);
}
@Override
public void updateLabel(Object source, ViewCanvas<?> view2d) {
boolean releasedEvent = false;
if (source instanceof MouseEvent) {
releasedEvent = ((MouseEvent) source).getID() == MouseEvent.MOUSE_RELEASED;
} else if (source instanceof Boolean) {
releasedEvent = (Boolean) source;
}
this.updateLabel(view2d, null, releasedEvent);
}
@Override
public void paint(Graphics2D g2d, AffineTransform transform) {
Paint oldPaint = g2d.getPaint();
Stroke oldStroke = g2d.getStroke();
if (shape instanceof AdvancedShape) {
((AdvancedShape) shape).paint(g2d, transform);
} else if (shape != null) {
Shape drawingShape = (transform == null) ? shape : transform.createTransformedShape(shape);
g2d.setPaint(colorPaint);
g2d.setStroke(getStroke(lineThickness));
g2d.draw(drawingShape);
if (getFilled()) {
g2d.fill(drawingShape);
}
}
// // Graphics DEBUG
// if (transform != null) {
// g2d.setPaint(Color.CYAN);
// g2d.draw(transform.createTransformedShape(getBounds(transform)));
// }
// if (transform != null) {
// g2d.setPaint(Color.RED);
// g2d.draw(transform.createTransformedShape(getArea(transform)));
// }
// if (transform != null) {
// g2d.setPaint(Color.BLUE);
// g2d.draw(transform.createTransformedShape(getRepaintBounds(transform)));
// }
// // Graphics DEBUG
g2d.setStroke(oldStroke);
g2d.setPaint(oldPaint);
if (getSelected()) {
paintHandles(g2d, transform);
}
paintLabel(g2d, transform);
}
@Override
public void paintLabel(Graphics2D g2d, AffineTransform transform) {
if (isLabelDisplayable()) {
graphicLabel.paint(g2d, transform, selected);
}
}
@Override
public void addPropertyChangeListener(PropertyChangeListener propertychangelistener) {
for (PropertyChangeListener listener : pcs.getPropertyChangeListeners()) {
if (listener == propertychangelistener) {
return;
}
}
pcs.addPropertyChangeListener(propertychangelistener);
}
@Override
public void removePropertyChangeListener(PropertyChangeListener propertychangelistener) {
pcs.removePropertyChangeListener(propertychangelistener);
}
@Override
public void removeAllPropertyChangeListener() {
for (PropertyChangeListener listener : pcs.getPropertyChangeListeners()) {
pcs.removePropertyChangeListener(listener);
}
}
@Override
public void toFront() {
if (isGraphicComplete()) {
firePropertyChange(ACTION_TO_FRONT, null, this);
}
}
@Override
public void toBack() {
if (isGraphicComplete()) {
firePropertyChange(ACTION_TO_BACK, null, this);
}
}
@Override
public void fireRemoveAction() {
if (isGraphicComplete()) {
firePropertyChange(ACTION_REMOVE, null, this);
}
}
@Override
public int getKeyCode() {
return 0;
}
@Override
public int getModifier() {
return 0;
}
@Override
public Graphic deepCopy() {
Graphic newGraphic = this.copy();
if (newGraphic == null) {
return null;
}
for (Point2D p : pts) {
newGraphic.getPts().add(p != null ? (Point2D.Double) p.clone() : null);
}
newGraphic.buildShape();
return newGraphic;
}
@Override
public void fireRemoveAndRepaintAction() {
if (isGraphicComplete()) {
firePropertyChange(ACTION_REMOVE_REPAINT, null, this);
}
}
@Override
public Stroke getStroke(Float lineThickness) {
return new BasicStroke(Optional.ofNullable(lineThickness).orElse(DEFAULT_LINE_THICKNESS), BasicStroke.CAP_BUTT,
BasicStroke.JOIN_ROUND);
}
public Stroke getDashStroke(Float lineThickness) {
return new BasicStroke(Optional.ofNullable(lineThickness).orElse(DEFAULT_LINE_THICKNESS), BasicStroke.CAP_BUTT,
BasicStroke.JOIN_MITER, 10f, new float[] { 5.0f, 5.0f }, 0f);
}
protected Boolean isLabelDisplayable() {
return labelVisible && graphicLabel != null && graphicLabel.getLabelBounds() != null;
}
@Override
public Boolean isGraphicComplete() {
return Objects.equals(pts.size(), pointNumber);
}
public Point2D.Double getHandlePoint(int index) {
Predicate<List<Point2D.Double>> validateIndex = list -> list.size() > index;
Function<List<Point2D.Double>, Point2D.Double> getPoint = list -> list.get(index);
Function<Point2D.Double, Point2D.Double> cloneValue = point -> (Point2D.Double) point.clone();
return Optional.of(pts).filter(validateIndex).map(getPoint).map(cloneValue).orElse(null);
}
public List<Point2D> getHandlePointList() {
return pts.stream().map(p -> (Point2D.Double) p.clone()).collect(Collectors.toList());
}
public void setHandlePoint(int index, Point2D.Double newPoint) {
Optional.ofNullable(pts).ifPresent(list -> {
if (index >= 0 && index <= list.size()) {
if (index == list.size()) {
list.add(newPoint);
} else {
list.set(index, newPoint);
}
}
});
}
public Integer getHandlePointListSize() {
return pts.size();
}
@Override
public Integer getHandleSize() {
return HANDLE_SIZE;
}
@Override
public Area getArea(MouseEvent mouseEvent) {
AffineTransform transform = getAffineTransform(mouseEvent);
return getArea(transform);
}
protected AffineTransform getAffineTransform(MouseEvent mouseevent) {
if (mouseevent != null && mouseevent.getSource() instanceof Image2DViewer) {
return ((Image2DViewer<?>) mouseevent.getSource()).getAffineTransform();
}
return null;
}
public Rectangle getBounds(MouseEvent mouseEvent) {
AffineTransform transform = getAffineTransform(mouseEvent);
return getBounds(transform);
}
/**
*
* @return Bounding rectangle which size has to be modified according to the given transform with handle drawings
* and lineThikness taken in consideration<br>
* This assumes that handle drawing size do not change with different scaling of views. Hence, real
* coordinates of bounding rectangle are modified consequently<br>
*
* @since v1.1.0 - new in Graphic interface
*/
public Rectangle getRepaintBounds(Shape shape, AffineTransform transform) {
if (Objects.isNull(shape)) {
return null;
}
if (shape instanceof AdvancedShape) {
((AdvancedShape) shape).setAffineTransform(transform);
}
Rectangle2D bounds = shape.getBounds2D();
// Add pixel tolerance to ensure that the graphic is correctly repainted
double growingSize = Math.max(HANDLE_SIZE * 1.5 / 2.0, lineThickness / 2.0) + 2;
growingSize /= GeomUtil.extractScalingFactor(transform);
GeomUtil.growRectangle(bounds, growingSize);
return (bounds != null) ? bounds.getBounds() : null;
}
@Override
public Rectangle getRepaintBounds(MouseEvent mouseEvent) {
AffineTransform transform = getAffineTransform(mouseEvent);
return getRepaintBounds(shape, transform);
}
/**
* @return selected handle point index if exist, otherwise -1
*/
@Override
public int getHandlePointIndex(MouseEventDouble mouseEvent) {
int nearestHandlePtIndex = -1;
final Point2D mousePoint = Optional.ofNullable(mouseEvent).map(evt -> evt.getImageCoordinates()).orElse(null);
if (mousePoint != null && !pts.isEmpty() && !layer.getLocked()) {
double minHandleDistance = Double.MAX_VALUE;
double maxHandleDistance =
HANDLE_SIZE * 1.5 / GeomUtil.extractScalingFactor(getAffineTransform(mouseEvent));
for (int index = 0; index < pts.size(); index++) {
Point2D handlePoint = pts.get(index);
double handleDistance =
Optional.ofNullable(handlePoint).map(mousePoint::distance).orElse(Double.MAX_VALUE);
if (handleDistance <= maxHandleDistance && handleDistance < minHandleDistance) {
minHandleDistance = handleDistance;
nearestHandlePtIndex = index;
}
}
}
return nearestHandlePtIndex;
}
public List<Integer> getHandlePointIndexList(MouseEventDouble mouseEvent) {
Map<Double, Integer> indexByDistanceMap = new TreeMap<>();
final Point2D mousePoint = Optional.ofNullable(mouseEvent).map(evt -> evt.getImageCoordinates()).orElse(null);
if (mousePoint != null && !pts.isEmpty() && !layer.getLocked()) {
double maxHandleDistance =
HANDLE_SIZE * 1.5 / GeomUtil.extractScalingFactor(getAffineTransform(mouseEvent));
for (int index = 0; index < pts.size(); index++) {
Point2D handlePoint = pts.get(index);
double handleDistance = (handlePoint != null) ? mousePoint.distance(handlePoint) : Double.MAX_VALUE;
if (handleDistance <= maxHandleDistance) {
indexByDistanceMap.put(handleDistance, index);
}
}
}
return (!indexByDistanceMap.isEmpty()) ? new ArrayList<>(indexByDistanceMap.values()) : null;
}
@Override
public boolean isOnGraphicLabel(MouseEventDouble mouseevent) {
if (Objects.isNull(mouseevent)) {
return false;
}
AffineTransform transform = getAffineTransform(mouseevent);
if (transform != null && isLabelDisplayable()) {
Area labelArea = graphicLabel.getArea(transform);
if (labelArea != null && labelArea.contains(mouseevent.getImageCoordinates())) {
return true;
}
}
return false;
}
@Override
@SuppressWarnings("rawtypes")
public ViewCanvas getDefaultView2d(MouseEvent mouseevent) {
if (mouseevent != null && mouseevent.getSource() instanceof ViewCanvas) {
return (ViewCanvas) mouseevent.getSource();
}
return null;
}
@Override
public void setShape(Shape newShape, MouseEvent mouseevent) {
Shape oldShape = this.shape;
this.shape = newShape;
fireDrawingChanged(oldShape);
}
@Override
public void setPaint(Color newPaintColor) {
if (this.colorPaint == null || newPaintColor == null || !this.colorPaint.equals(newPaintColor)) {
this.colorPaint = newPaintColor;
fireDrawingChanged();
}
}
protected void fireDrawingChanged() {
fireDrawingChanged(null);
}
protected void fireDrawingChanged(Shape oldShape) {
firePropertyChange("bounds", oldShape, shape); //$NON-NLS-1$
}
protected void firePropertyChange(String s, Object obj, Object obj1) {
pcs.firePropertyChange(s, obj, obj1);
}
protected void firePropertyChange(String s, int i, int j) {
pcs.firePropertyChange(s, i, j);
}
protected void firePropertyChange(String s, boolean flag, boolean flag1) {
pcs.firePropertyChange(s, flag, flag1);
}
protected void fireLabelChanged() {
fireLabelChanged(null);
}
protected void fireLabelChanged(GraphicLabel oldLabel) {
firePropertyChange("graphicLabel", oldLabel, graphicLabel); //$NON-NLS-1$
}
@Override
public void setLabel(GraphicLabel label) {
GraphicLabel oldLabel = Optional.ofNullable(graphicLabel).map(gl -> gl.copy()).orElse(null);
graphicLabel = label;
fireLabelChanged(oldLabel);
}
public void setLabel(String[] labels, ViewCanvas<?> view2d, Point2D pos) {
GraphicLabel oldLabel = Optional.ofNullable(graphicLabel).map(gl -> gl.copy()).orElse(null);
if (labels == null || labels.length == 0) {
graphicLabel = null;
fireLabelChanged(oldLabel);
} else if (pos == null) {
setLabel(labels, view2d);
} else {
if (graphicLabel == null) {
graphicLabel = new DefaultGraphicLabel();
}
graphicLabel.setLabel(view2d, pos.getX(), pos.getY(), labels);
fireLabelChanged(oldLabel);
}
}
@Override
public void moveLabel(Double deltaX, Double deltaY) {
if (isLabelDisplayable() && (MathUtil.isDifferentFromZero(deltaX) || MathUtil.isDifferentFromZero(deltaY))) {
GraphicLabel oldLabel = graphicLabel.copy();
graphicLabel.move(deltaX, deltaY);
fireLabelChanged(oldLabel);
}
}
public void updateLabel(ViewCanvas<?> view2d, Point2D pos, boolean releasedEvent) {
List<Graphic> selectedGraphics =
view2d == null ? Collections.emptyList() : view2d.getGraphicManager().getSelectedGraphics();
boolean isMultiSelection = selectedGraphics.size() > 1;
List<MeasureItem> measList = null;
String[] labels = null;
// If isMultiSelection is false, it should return all enable computed measurements when
// quickComputing is enable or when releasedEvent is true
if ((labelVisible || !isMultiSelection) && getLayerType() == LayerType.MEASURE) {
Unit displayUnit = view2d == null ? null : (Unit) view2d.getActionValue(ActionW.SPATIAL_UNIT.cmd());
measList =
computeMeasurements(view2d == null ? null : view2d.getMeasurableLayer(), releasedEvent, displayUnit);
}
if (labelVisible && measList != null && !measList.isEmpty()) {
List<String> labelList = new ArrayList<>(measList.size());
for (MeasureItem item : measList) {
if (item != null) {
Measurement measurement = item.getMeasurement();
if (measurement != null && measurement.getGraphicLabel()) {
StringBuilder sb = new StringBuilder();
String name = measurement.getName();
Object value = item.getValue();
String unit = item.getUnit();
if (name != null) {
sb.append(name);
if (item.getLabelExtension() != null) {
sb.append(item.getLabelExtension());
}
sb.append(" : "); //$NON-NLS-1$
if (value instanceof Number) {
sb.append(DecFormater.oneDecimal((Number) value));
if (unit != null) {
sb.append(" ").append(unit); //$NON-NLS-1$
}
} else if (value != null) {
sb.append(value.toString());
}
}
labelList.add(sb.toString());
}
}
}
if (!labelList.isEmpty()) {
labels = labelList.toArray(new String[labelList.size()]);
}
}
if (labels == null && view2d == null && graphicLabel != null) {
labels = graphicLabel.getLabels();
}
setLabel(labels, view2d, pos);
// update MeasureTool on the fly without calling again getMeasurements
if (!isMultiSelection && view2d != null) {
for (GraphicSelectionListener gfxListener : view2d.getGraphicManager().getGraphicSelectionListeners()) {
gfxListener.updateMeasuredItems(measList);
}
}
}
protected void paintHandles(Graphics2D g2d, AffineTransform transform) {
if (!pts.isEmpty()) {
double size = HANDLE_SIZE;
double halfSize = size / 2;
ArrayList<Point2D> handlePts = new ArrayList<>(pts.size());
for (Point2D pt : pts) {
if (pt != null) {
handlePts.add(new Point2D.Double(pt.getX(), pt.getY()));
}
}
Point2D.Double[] handlePtArray = handlePts.toArray(new Point2D.Double[handlePts.size()]);
transform.transform(handlePtArray, 0, handlePtArray, 0, handlePtArray.length);
Paint oldPaint = g2d.getPaint();
Stroke oldStroke = g2d.getStroke();
g2d.setPaint(Color.black);
for (Point2D point : handlePtArray) {
g2d.fill(new Rectangle2D.Double(point.getX() - halfSize, point.getY() - halfSize, size, size));
}
g2d.setPaint(Color.white);
g2d.setStroke(new BasicStroke(1.0f));
for (Point2D point : handlePtArray) {
g2d.draw(new Rectangle2D.Double(point.getX() - halfSize, point.getY() - halfSize, size, size));
}
g2d.setPaint(oldPaint);
g2d.setStroke(oldStroke);
}
}
/**
* Can be overridden to estimate what is a valid shape that can be fully computed and drawn
*
* @return True when not handle points equals each another. <br>
*/
@Override
public boolean isShapeValid() {
if (!isGraphicComplete()) {
return false;
}
int lastPointIndex = pts.size() - 1;
while (lastPointIndex > 0) {
Point2D checkPoint = pts.get(lastPointIndex);
ListIterator<Point2D.Double> listIt = pts.listIterator(lastPointIndex--);
while (listIt.hasPrevious()) {
if (checkPoint != null && checkPoint.equals(listIt.previous())) {
return false;
}
}
}
return true;
}
protected void fireMoveAction() {
if (isGraphicComplete()) {
firePropertyChange("move", null, this); //$NON-NLS-1$
}
}
@Override
public LayerType getLayerType() {
return layerType;
}
@Override
public void setLayerType(LayerType layerType) {
this.layerType = Objects.requireNonNull(layerType, NULL_MSG);
}
static class Adapter extends XmlAdapter<AbstractGraphic, Graphic> {
@Override
public Graphic unmarshal(AbstractGraphic v) throws Exception {
v.buildGraphic(v.getPts());
return v;
}
@Override
public AbstractGraphic marshal(Graphic v) throws Exception {
return (AbstractGraphic) v;
}
}
}