/******************************************************************************* * 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.imp.line; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.Line2D; import java.awt.geom.Path2D; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.Collections; import java.util.List; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlType; import org.weasis.core.api.gui.util.GeomUtil; import org.weasis.core.api.gui.util.MathUtil; import org.weasis.core.api.image.measure.MeasurementsAdapter; import org.weasis.core.api.image.util.MeasurableLayer; import org.weasis.core.api.image.util.Unit; import org.weasis.core.ui.Messages; import org.weasis.core.ui.model.graphic.AbstractDragGraphic; 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.util.MouseEventDouble; @XmlType(name = "perpendicularLine") @XmlRootElement(name = "perpendicularLine") public class PerpendicularLineGraphic extends AbstractDragGraphic { private static final long serialVersionUID = -7056437654935777004L; public static final Integer POINTS_NUMBER = 4; public static final Icon ICON = new ImageIcon(PerpendicularLineGraphic.class.getResource("/icon/22x22/draw-perpendicular.png")); //$NON-NLS-1$ public static final Measurement LINE_LENGTH = new Measurement(Messages.getString("measure.length"), 1, true, true, true); //$NON-NLS-1$ public static final Measurement ORIENTATION = new Measurement(Messages.getString("measure.orientation"), 2, true, true, false); //$NON-NLS-1$ public static final Measurement AZIMUTH = new Measurement(Messages.getString("measure.azimuth"), 3, true, true, false); //$NON-NLS-1$ protected static final List<Measurement> MEASUREMENT_LIST = new ArrayList<>(); static { MEASUREMENT_LIST.add(LINE_LENGTH); MEASUREMENT_LIST.add(ORIENTATION); MEASUREMENT_LIST.add(AZIMUTH); } // Let AB & CD two perpendicular line segments with D being the projected point C on AB protected Point2D.Double ptA; protected Point2D.Double ptB; protected Point2D.Double ptC; protected Point2D.Double ptD; // estimate if line segments are valid or not protected boolean lineABvalid; protected boolean lineCDvalid; public PerpendicularLineGraphic() { super(POINTS_NUMBER); } public PerpendicularLineGraphic(PerpendicularLineGraphic graphic) { super(graphic); } @Override public PerpendicularLineGraphic copy() { return new PerpendicularLineGraphic(this); } @Override public Icon getIcon() { return ICON; } @Override public String getUIName() { return Messages.getString("measure.perpendicular"); //$NON-NLS-1$ } @Override protected void prepareShape() throws InvalidShapeException { if (!isShapeValid()) { throw new InvalidShapeException("This shape cannot be drawn"); //$NON-NLS-1$ } buildShape(null); } @Override public Integer moveAndResizeOnDrawing(Integer handlePointIndex, Double deltaX, Double deltaY, MouseEventDouble mouseEvent) { List<Point2D> prevHandlePointList = getHandlePointList(); handlePointIndex = super.moveAndResizeOnDrawing(handlePointIndex, deltaX, deltaY, mouseEvent); if (handlePointIndex >= 0 && handlePointIndex < getHandlePointListSize()) { updateTool(); if (handlePointIndex == 0 || handlePointIndex == 1) { // drag point is A or B Point2D prevPtA = (!prevHandlePointList.isEmpty()) ? prevHandlePointList.get(0) : null; Point2D prevPtB = (prevHandlePointList.size() > 1) ? prevHandlePointList.get(1) : null; if (lineABvalid && GeomUtil.isLineValid(prevPtA, prevPtB) && ptC != null && ptD != null) { // compute rotation from previous to actual position double theta = GeomUtil.getAngleRad(prevPtA, prevPtB) - GeomUtil.getAngleRad(ptA, ptB); Point2D anchor = (handlePointIndex == 0) ? ptB : ptA; // anchor is opposite point of A or B AffineTransform rotate = AffineTransform.getRotateInstance(theta, anchor.getX(), anchor.getY()); rotate.transform(ptC, ptC); rotate.transform(ptD, ptD); setHandlePoint(2, ptC); setHandlePoint(3, ptD); } } else if (handlePointIndex == 2) { // drag point is C if (lineABvalid && ptC != null) { ptD = GeomUtil.getPerpendicularPointToLine(ptA, ptB, ptC); setHandlePoint(3, ptD); } } else if (handlePointIndex == 3) { // drag point is D Point2D prevPtD = (prevHandlePointList.size() > 3) ? prevHandlePointList.get(3) : null; if (lineABvalid && ptD != null && prevPtD != null && ptC != null) { ptD = GeomUtil.getPerpendicularPointToLine(ptA, ptB, ptD); AffineTransform translate = AffineTransform.getTranslateInstance(ptD.getX() - prevPtD.getX(), ptD.getY() - prevPtD.getY()); translate.transform(ptC, ptC); setHandlePoint(2, ptC); setHandlePoint(3, ptD); } } } return handlePointIndex; } @Override public void buildShape(MouseEventDouble mouseEvent) { updateTool(); Shape newShape = null; Path2D path = new Path2D.Double(Path2D.WIND_NON_ZERO, 2); if (lineABvalid) { path.append(new Line2D.Double(ptA, ptB), false); } if (lineCDvalid) { path.append(new Line2D.Double(ptC, ptD), false); } if (lineABvalid && lineCDvalid) { newShape = new AdvancedShape(this, 3); AdvancedShape aShape = (AdvancedShape) newShape; aShape.addShape(path); if (!ptD.equals(ptA) && !ptD.equals(ptB)) { // Check D is outside of AB segment if (MathUtil.isEqual(Math.signum(GeomUtil.getAngleDeg(ptD, ptA)), Math.signum(GeomUtil.getAngleDeg(ptD, ptB)))) { Point2D ptE = ptD.distance(ptA) < ptD.distance(ptB) ? ptA : ptB; aShape.addShape(new Line2D.Double(ptD, ptE), getDashStroke(1.0f), true); } } double cornerLength = 10; double dMin = Math.min(ptD.distance(ptC), Math.max(ptD.distance(ptA), ptD.distance(ptB))) * 2 / 3; double scalingMin = cornerLength / dMin; Point2D f = GeomUtil.getMidPoint(ptA, ptB); Shape cornerShape = GeomUtil.getCornerShape(f, ptD, ptC, cornerLength); if (cornerShape != null) { aShape.addScaleInvShape(cornerShape, ptD, scalingMin, getStroke(1.0f), true); } } else if (path.getCurrentPoint() != null) { newShape = path; } setShape(newShape, mouseEvent); updateLabel(mouseEvent, getDefaultView2d(mouseEvent)); } @Override public List<MeasureItem> computeMeasurements(MeasurableLayer layer, boolean releaseEvent, Unit displayUnit) { if (layer != null && layer.hasContent() && isShapeValid()) { MeasurementsAdapter adapter = layer.getMeasurementAdapter(displayUnit); if (adapter != null) { ArrayList<MeasureItem> measVal = new ArrayList<>(3); if (LINE_LENGTH.getComputed()) { measVal.add( new MeasureItem(LINE_LENGTH, ptC.distance(ptD) * adapter.getCalibRatio(), adapter.getUnit())); } if (ORIENTATION.getComputed()) { measVal.add(new MeasureItem(ORIENTATION, MathUtil.getOrientation(ptC, ptD), Messages.getString("measure.deg"))); //$NON-NLS-1$ } if (AZIMUTH.getComputed()) { measVal.add( new MeasureItem(AZIMUTH, MathUtil.getAzimuth(ptC, ptD), Messages.getString("measure.deg"))); //$NON-NLS-1$ } return measVal; } } return Collections.emptyList(); } @Override public boolean isShapeValid() { updateTool(); return lineABvalid && lineCDvalid; } protected void updateTool() { ptA = getHandlePoint(0); ptB = getHandlePoint(1); ptC = getHandlePoint(2); ptD = getHandlePoint(3); lineABvalid = ptA != null && ptB != null && !ptB.equals(ptA); lineCDvalid = ptC != null && ptD != null && !ptC.equals(ptD); } @Override public List<Measurement> getMeasurementList() { return MEASUREMENT_LIST; } }