/*******************************************************************************
* 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.utils.bean;
import java.awt.BasicStroke;
import java.awt.Graphics2D;
import java.awt.Paint;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.weasis.core.api.gui.util.GeomUtil;
import org.weasis.core.api.gui.util.MathUtil;
import org.weasis.core.ui.model.graphic.Graphic;
public class AdvancedShape implements Shape {
private static final Logger LOGGER = LoggerFactory.getLogger(AdvancedShape.class);
/**
* First element should be considered as the main shape used for drawing of the main features of graphic.<br>
* For instance, this first shape defines measurement areas or path lines. Other shape are usually dedicated to
* decorative drawings, with or without invariant size according to the view.
*/
public List<BasicShape> shapeList;
protected AffineTransform transform;
private Graphic graphic;
public AdvancedShape(Graphic graphic, int initialShapeNumber) {
this.graphic = Objects.requireNonNull(graphic, "Graphic cannot be null!"); //$NON-NLS-1$
this.shapeList = new ArrayList<>(initialShapeNumber);
}
public List<BasicShape> getShapeList() {
return shapeList;
}
public BasicShape addShape(Shape shape) {
BasicShape s = new BasicShape(shape);
shapeList.add(s);
return s;
}
public BasicShape addShape(Shape shape, Stroke stroke, boolean fixedLineWidth) {
BasicShape s = new BasicShape(shape, stroke, fixedLineWidth);
shapeList.add(s);
return s;
}
ScaleInvariantShape addScaleInvShape(Shape shape, Point2D anchorPoint) {
return addScaleInvShape(shape, anchorPoint, graphic.getStroke(graphic.getLineThickness()), false);
}
public ScaleInvariantShape addScaleInvShape(Shape shape, Point2D anchorPoint, double scalingMin,
boolean fixedLineWidth) {
return addScaleInvShape(shape, anchorPoint, scalingMin, graphic.getStroke(graphic.getLineThickness()),
fixedLineWidth);
}
public InvariantShape addAllInvShape(Shape shape, Point2D anchorPoint, Stroke stroke, boolean fixedLineWidth) {
InvariantShape s = new InvariantShape(shape, stroke, anchorPoint, fixedLineWidth);
shapeList.add(s);
return s;
}
public LinkSegmentToInvariantShape addLinkSegmentToInvariantShape(Line2D line, Point2D anchorPoint, Shape invShape,
Stroke stroke, boolean fixedLineWidth) {
LinkSegmentToInvariantShape s =
new LinkSegmentToInvariantShape(line, stroke, anchorPoint, invShape, fixedLineWidth);
shapeList.add(s);
return s;
}
public ScaleInvariantShape addScaleInvShape(Shape shape, Point2D anchorPoint, double scalingMin, Stroke stroke,
boolean fixedLineWidth) {
ScaleInvariantShape s = new ScaleInvariantShape(shape, stroke, anchorPoint, scalingMin, fixedLineWidth);
shapeList.add(s);
return s;
}
public ScaleInvariantShape addScaleInvShape(Shape shape, Point2D anchorPoint, Stroke stroke,
boolean fixedLineWidth) {
ScaleInvariantShape s = new ScaleInvariantShape(shape, stroke, anchorPoint, fixedLineWidth);
shapeList.add(s);
return s;
}
public void setAffineTransform(AffineTransform transform) {
this.transform = transform;
}
public void paint(Graphics2D g2d, AffineTransform transform) {
setAffineTransform(transform);
Paint oldPaint = g2d.getPaint();
Stroke oldStroke = g2d.getStroke();
Paint paint = graphic.getColorPaint();
boolean filled = graphic.getFilled();
for (BasicShape item : shapeList) {
if (item.isVisible()) {
Shape drawingShape = item.getRealShape();
if (drawingShape != null) {
if (transform != null) {
drawingShape = transform.createTransformedShape(drawingShape);
}
Paint itemPaint = item.getColorPaint();
g2d.setPaint(itemPaint == null ? paint : itemPaint);
g2d.setStroke(item.stroke);
g2d.draw(drawingShape);
Boolean itemFilled = item.getFilled();
if (itemFilled == null ? filled : itemFilled) {
g2d.fill(drawingShape);
}
}
}
}
g2d.setPaint(oldPaint);
g2d.setStroke(oldStroke);
}
/**
*
* @return a shape which is by convention the first shape in the list which is dedicated to the user tool drawing
*/
public Shape getGeneralShape() {
if (!shapeList.isEmpty()) {
BasicShape s = shapeList.get(0);
if (s != null) {
return s.getRealShape();
}
}
return null;
}
@Override
public Rectangle getBounds() {
Rectangle rectangle = null;
for (BasicShape item : shapeList) {
Shape realShape = item.getRealShape();
Rectangle bounds = realShape != null ? realShape.getBounds() : null;
if (bounds != null) {
if (rectangle == null) {
rectangle = bounds;
} else {
rectangle.add(bounds);
}
}
}
return rectangle;
}
@Override
public Rectangle2D getBounds2D() {
Rectangle2D rectangle = null;
for (BasicShape item : shapeList) {
Shape realShape = item.getRealShape();
Rectangle2D bounds = realShape != null ? realShape.getBounds2D() : null;
if (bounds != null) {
if (rectangle == null) {
rectangle = bounds;
} else {
rectangle.add(bounds);
}
}
}
return rectangle;
}
@Override
public boolean contains(double x, double y) {
for (BasicShape item : shapeList) {
Shape realShape = item.getRealShape();
if (realShape != null && realShape.contains(x, y)) {
return true;
}
}
return false;
}
@Override
public boolean contains(Point2D p) {
for (BasicShape item : shapeList) {
Shape realShape = item.getRealShape();
if (realShape != null && realShape.contains(p)) {
return true;
}
}
return false;
}
@Override
public boolean contains(double x, double y, double w, double h) {
for (BasicShape item : shapeList) {
Shape realShape = item.getRealShape();
if (realShape != null && realShape.contains(x, y, w, h)) {
return true;
}
}
return false;
}
@Override
public boolean contains(Rectangle2D r) {
for (BasicShape item : shapeList) {
Shape realShape = item.getRealShape();
if (realShape != null && realShape.contains(r)) {
return true;
}
}
return false;
}
@Override
public boolean intersects(double x, double y, double w, double h) {
for (BasicShape item : shapeList) {
Shape realShape = item.getRealShape();
if (realShape != null && realShape.intersects(x, y, w, h)) {
return true;
}
}
return false;
}
@Override
public boolean intersects(Rectangle2D r) {
for (BasicShape item : shapeList) {
Shape realShape = item.getRealShape();
if (realShape != null && realShape.intersects(r)) {
return true;
}
}
return false;
}
@Override
public PathIterator getPathIterator(AffineTransform at) {
if (at != null) {
setAffineTransform(at);
}
return getFullPathShape().getPathIterator(at);
}
@Override
public PathIterator getPathIterator(AffineTransform at, double flatness) {
if (at != null) {
setAffineTransform(at);
}
return getFullPathShape().getPathIterator(at, flatness);
}
private Path2D getFullPathShape() {
Path2D pathShape = new Path2D.Double(Path2D.WIND_NON_ZERO);
for (BasicShape item : shapeList) {
Shape realShape = item.getRealShape();
if (realShape != null) {
pathShape.append(realShape, false);
}
}
return pathShape;
}
public Area getArea(AffineTransform transform) {
setAffineTransform(transform);
double scalingFactor = GeomUtil.extractScalingFactor(transform);
double growingSize = graphic.getHandleSize() * 2.0 / scalingFactor;
Stroke boundingStroke = new BasicStroke((float) growingSize, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
Area pathBoundingArea = new Area();
for (BasicShape item : shapeList) {
// Note : if shape is invalid, like a path with an odd number of curves, creating a new Area involves a
// "java.lang.InternalError". Because trapping the exception is too much time consuming it's the user
// responsibility of this not to happen
Shape strokedArea = null;
try {
Shape realShape = item.getRealShape();
if (realShape != null) {
Shape strokedShape = boundingStroke.createStrokedShape(realShape);
strokedArea = new Area(strokedShape);
}
} catch (Exception e) {
LOGGER.error("This shape cannot be drawn, the graphic is deleted.", e); //$NON-NLS-1$
graphic.fireRemoveAction();
}
if (strokedArea != null) {
pathBoundingArea.add(new Area(strokedArea));
}
}
return pathBoundingArea;
}
public class BasicShape {
final Shape shape;
final boolean fixedLineWidth;
Stroke stroke;
boolean visible;
Boolean filled;
Paint colorPaint;
public BasicShape(Shape shape) {
this(shape, graphic.getStroke(graphic.getLineThickness()), false);
}
public BasicShape(Shape shape, Stroke stroke, boolean fixedLineWidth) {
if (shape == null || stroke == null) {
throw new IllegalArgumentException();
}
this.shape = shape;
this.stroke = stroke;
this.fixedLineWidth = fixedLineWidth;
this.visible = true;
this.filled = null;
this.colorPaint = null;
}
public Paint getColorPaint() {
return colorPaint;
}
public void setColorPaint(Paint colorPaint) {
this.colorPaint = colorPaint;
}
public Boolean getFilled() {
return filled;
}
public Shape getRealShape() {
return shape;
}
public void changelineThickness(float width) {
if (!fixedLineWidth && stroke instanceof BasicStroke) {
BasicStroke s = (BasicStroke) stroke;
if (MathUtil.isDifferent(s.getLineWidth(), width)) {
stroke = new BasicStroke(width, s.getEndCap(), s.getLineJoin(), s.getMiterLimit(), s.getDashArray(),
s.getDashPhase());
}
}
}
public void setVisible(boolean visible) {
this.visible = visible;
}
public boolean isVisible() {
return visible;
}
public Shape getShape() {
return shape;
}
public boolean isFixedLineWidth() {
return fixedLineWidth;
}
public Stroke getStroke() {
return stroke;
}
public void setStroke(Stroke stroke) {
this.stroke = stroke;
}
public void setFilled(boolean filled) {
this.filled = filled;
}
}
/**
* Dedicated to drawings with invariant size around anchorPoint according to the view
*/
public class ScaleInvariantShape extends BasicShape {
final Point2D anchorPoint;
final double scalingMin;
public ScaleInvariantShape(Shape shape, Stroke stroke, Point2D anchorPoint, boolean fixedLineWidth) {
this(shape, stroke, anchorPoint, 0.0, fixedLineWidth);
}
public ScaleInvariantShape(Shape shape, Stroke stroke, Point2D anchorPoint, double scalingMin,
boolean fixedLineWidth) {
super(shape, stroke, fixedLineWidth);
if (anchorPoint == null) {
throw new IllegalArgumentException();
}
if (scalingMin < 0) {
throw new IllegalArgumentException();
}
this.anchorPoint = (Point2D) anchorPoint.clone();
this.scalingMin = scalingMin;
}
@Override
public Shape getRealShape() {
double scalingFactor = GeomUtil.extractScalingFactor(transform);
double scale = (scalingFactor < scalingMin) ? scalingMin : scalingFactor;
return MathUtil.isDifferentFromZero(scale) ? GeomUtil.getScaledShape(shape, 1 / scale, anchorPoint) : null;
}
}
/**
* Invariant to all the transformations except to flip (horizontal mirror)
*
* @version $Rev$ $Date$
*/
public class InvariantShape extends BasicShape {
final Point2D anchorPoint;
public InvariantShape(Shape shape, Stroke stroke, Point2D anchorPoint, boolean fixedLineWidth) {
super(shape, stroke, fixedLineWidth);
if (anchorPoint == null) {
throw new IllegalArgumentException();
}
this.anchorPoint = (Point2D) anchorPoint.clone();
}
@Override
public Shape getRealShape() {
if (transform == null) {
return shape;
}
AffineTransform invTransform = new AffineTransform(); // Identity transformation.
double scale = GeomUtil.extractScalingFactor(transform);
double angleRad = GeomUtil.extractAngleRad(transform);
boolean scaled = MathUtil.isDifferent(scale, 1.0);
boolean rotated = MathUtil.isDifferentFromZero(angleRad);
invTransform.translate(anchorPoint.getX(), anchorPoint.getY());
if (scaled) {
invTransform.scale(1 / scale, 1 / scale);
}
if (rotated) {
invTransform.rotate(-angleRad);
}
if ((transform.getType() & AffineTransform.TYPE_FLIP) != 0) {
invTransform.scale(-1.0, -1.0);
}
invTransform.translate(-anchorPoint.getX(), -anchorPoint.getY());
return invTransform.createTransformedShape(shape);
}
}
public class LinkSegmentToInvariantShape extends BasicShape {
final Point2D anchorPoint;
final Shape invShape;
public LinkSegmentToInvariantShape(Line2D line, Stroke stroke, Point2D anchorPoint, Shape invShape,
boolean fixedLineWidth) {
super(line, stroke, fixedLineWidth);
if (anchorPoint == null) {
throw new IllegalArgumentException();
}
this.anchorPoint = (Point2D) anchorPoint.clone();
this.invShape = invShape;
}
@Override
public Shape getRealShape() {
if (transform == null) {
return shape;
}
Line2D line = (Line2D) shape;
AffineTransform invTransform = new AffineTransform(); // Identity transformation.
double scale = GeomUtil.extractScalingFactor(transform);
double angleRad = GeomUtil.extractAngleRad(transform);
boolean scaled = MathUtil.isDifferent(scale, 1.0);
boolean rotated = MathUtil.isDifferentFromZero(angleRad);
invTransform.translate(anchorPoint.getX(), anchorPoint.getY());
if (scaled) {
invTransform.scale(1 / scale, 1 / scale);
}
if (rotated) {
invTransform.rotate(-angleRad);
}
invTransform.translate(-anchorPoint.getX(), -anchorPoint.getY());
Point2D p = null;
if (invShape instanceof Rectangle2D) {
// Find the intersection between the line and the text box
AffineTransform tr = new AffineTransform();
tr.translate(anchorPoint.getX(), anchorPoint.getY());
if (scaled) {
tr.scale(scale, scale);
}
if (rotated) {
tr.rotate(angleRad);
}
tr.translate(-anchorPoint.getX(), -anchorPoint.getY());
Point2D p2 = line.getP2();
tr.transform(p2, p2);
p = GeomUtil.getIntersectPoint(new Line2D.Double(anchorPoint, p2), (Rectangle2D) invShape);
}
Point2D invpt = invTransform.transform(p == null ? line.getP1() : p, null);
return new Line2D.Double(invpt, line.getP2());
}
}
}