/* ****************************************************************************** * Copyright (c) 2006-2012 XMind Ltd. and others. * * This file is a part of XMind 3. XMind releases 3 and * above are dual-licensed under the Eclipse Public License (EPL), * which is available at http://www.eclipse.org/legal/epl-v10.html * and the GNU Lesser General Public License (LGPL), * which is available at http://www.gnu.org/licenses/lgpl.html * See http://www.xmind.net/license.html for details. * * Contributors: * XMind Ltd. - initial API and implementation *******************************************************************************/ package org.xmind.ui.internal.decorations; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.PositionConstants; import org.eclipse.draw2d.geometry.Point; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.widgets.Display; import org.xmind.gef.draw2d.geometry.Geometry; import org.xmind.gef.draw2d.geometry.PrecisionPoint; import org.xmind.gef.draw2d.graphics.GraphicsUtils; import org.xmind.gef.draw2d.graphics.Path; import org.xmind.gef.graphicalpolicy.IStructure; import org.xmind.ui.branch.IBranchStructureExtension; import org.xmind.ui.decorations.AbstractBoundaryDecoration; import org.xmind.ui.mindmap.IBoundaryPart; import org.xmind.ui.mindmap.IBranchPart; import org.xmind.ui.mindmap.ITopicPart; public class PolygonBoundaryDecoration extends AbstractBoundaryDecoration { private class ValueComparator implements Comparator<ITopicPart> { private Map<ITopicPart, Integer> base; public ValueComparator(Map<ITopicPart, Integer> base) { this.base = base; } public int compare(ITopicPart t1, ITopicPart t2) { if (base.get(t1) > base.get(t2)) return 1; return -1; } } private IBoundaryPart boundary; public PolygonBoundaryDecoration() { super(); } public PolygonBoundaryDecoration(String id) { super(id); } public PolygonBoundaryDecoration(IBoundaryPart boundary, String id) { super(id); this.boundary = boundary; } protected void sketch(IFigure figure, Path shape, Rectangle box, int purpose) { List<Point> points = calcPathPoints(box, boundary); if (points.size() > 4) { shape.moveTo(points.get(0)); for (int i = 1; i < points.size(); i++) shape.lineTo(points.get(i)); shape.lineTo(points.get(0)); shape.close(); } else { shape.addRectangle(box); } } public PrecisionPoint getAnchorLocation(IFigure figure, double refX, double refY, double expansion) { Rectangle bounds = figure.getBounds(); PrecisionPoint p1 = new PrecisionPoint(bounds.x + bounds.width / 2, bounds.y + bounds.height / 2); PrecisionPoint p2 = Geometry.getChopBoxLocation(refX, refY, getOutlineBox(figure), expansion); return calcAnchorLocation(figure, p1, p2); } private PrecisionPoint calcAnchorLocation(IFigure figure, PrecisionPoint p1, PrecisionPoint p2) { if (p1.getDistance(p2) < (getLineWidth() == 0 ? 1 : getLineWidth())) return p2; PrecisionPoint p3 = new PrecisionPoint((p1.x + p2.x) / 2, (p1.y + p2.y) / 2); if (containsPoint(figure, (float) p3.x, (float) p3.y)) return calcAnchorLocation(figure, p3, p2); else return calcAnchorLocation(figure, p1, p3); } private boolean containsPoint(IFigure figure, float x, float y) { checkValidation(figure); GC gc = GraphicsUtils.getAdvanced().getGC(); gc.setLineWidth(getCheckingLineWidth()); Path shape = new Path(Display.getCurrent()); sketch(figure, shape, getOutlineBox(figure), FILL); boolean ret = shape.contains(x, y, gc, false); shape.close(); shape.dispose(); return ret; } protected List<Point> calcPathPoints(Rectangle box, IBoundaryPart boundary) { List<IBranchPart> enclosingBranches = boundary.getEnclosingBranches(); IBranchPart branch; if (!enclosingBranches.isEmpty()) branch = enclosingBranches.get(0); else branch = boundary.getOwnedBranch(); int childDirection = calcChildDirection(branch); List<ITopicPart> topics = collectTopics(boundary); List<Point> points = new ArrayList<Point>(); switch (childDirection) { case PositionConstants.WEST: collectPointsWestDire(points, topics, box); break; case PositionConstants.EAST: collcetPointsEastDire(points, topics, box); break; case PositionConstants.SOUTH: collectPointsSouthDire(points, topics, box); break; case PositionConstants.NORTH: collectPointsNorthDire(points, topics, box); break; } return points; } private void collectPointsWestDire(List<Point> points, List<ITopicPart> topics, Rectangle box) { int lineWidth1 = getLineWidth(); int lineWidth2 = lineWidth1 > 1 ? lineWidth1 - 1 : lineWidth1; Map<ITopicPart, Integer> headMap = new HashMap<ITopicPart, Integer>(); for (ITopicPart topic : topics) { Rectangle bounds = topic.getFigure().getBounds(); ITopicPart t = findTopicByValue(headMap, bounds.x); if (t != null) { if (bounds.y < t.getFigure().getBounds().y) { headMap.remove(t); headMap.put(topic, bounds.x); } } else { headMap.put(topic, bounds.x); } } List<Point> headPoints = new ArrayList<Point>(); for (ITopicPart topic : sortTopicPart(headMap)) headPoints.add(topic.getFigure().getBounds().getTopLeft() .getTranslated(-MARGIN_WIDTH + lineWidth2, -MARGIN_HEIGHT + lineWidth2)); Map<ITopicPart, Integer> tailMap = new HashMap<ITopicPart, Integer>(); for (ITopicPart topic : topics) { Rectangle bounds = topic.getFigure().getBounds(); ITopicPart t = findTopicByValue(tailMap, bounds.x); if (t != null) { if (bounds.bottom() > t.getFigure().getBounds().bottom()) { tailMap.remove(t); tailMap.put(topic, bounds.x); } } else { tailMap.put(topic, bounds.x); } } List<Point> tailPoints = new ArrayList<Point>(); for (ITopicPart topic : sortTopicPart(tailMap)) tailPoints.add(topic.getFigure().getBounds().getBottomLeft() .getTranslated(-MARGIN_WIDTH + lineWidth2, MARGIN_HEIGHT - lineWidth2)); Point tr = box.getTopRight(); Point br = box.getBottomRight(); initPoints(points, headPoints.get(0), tr, br, tailPoints.get(0)); calcPointsWestOrEast(points, tr, br, headPoints, tailPoints); } private void collcetPointsEastDire(List<Point> points, List<ITopicPart> topics, Rectangle box) { int lineWidth1 = getLineWidth(); int lineWidth2 = lineWidth1 > 1 ? lineWidth1 - 1 : lineWidth1; Map<ITopicPart, Integer> headMap = new HashMap<ITopicPart, Integer>(); for (ITopicPart topic : topics) { Rectangle bounds = topic.getFigure().getBounds(); ITopicPart t = findTopicByValue(headMap, -bounds.right()); if (t != null) { if (bounds.y < t.getFigure().getBounds().y) { headMap.remove(t); headMap.put(topic, -bounds.right()); } } else { headMap.put(topic, -bounds.right()); } } List<Point> headPoints = new ArrayList<Point>(); for (ITopicPart topic : sortTopicPart(headMap)) headPoints.add(topic.getFigure().getBounds().getTopRight() .getTranslated(MARGIN_WIDTH - lineWidth1, -MARGIN_HEIGHT + lineWidth2)); Map<ITopicPart, Integer> tailMap = new HashMap<ITopicPart, Integer>(); for (ITopicPart topic : topics) { Rectangle bounds = topic.getFigure().getBounds(); ITopicPart t = findTopicByValue(tailMap, -bounds.right()); if (t != null) { if (bounds.bottom() > t.getFigure().getBounds().bottom()) { tailMap.remove(t); tailMap.put(topic, -bounds.right()); } } else { tailMap.put(topic, -bounds.right()); } } List<Point> tailPoints = new ArrayList<Point>(); for (ITopicPart topic : sortTopicPart(tailMap)) tailPoints.add(topic.getFigure().getBounds().getBottomRight() .getTranslated(MARGIN_WIDTH - lineWidth1, MARGIN_HEIGHT - (lineWidth1 > 1 ? lineWidth1 - 1 : lineWidth1))); Point tl = box.getTopLeft(); Point bl = box.getBottomLeft(); initPoints(points, headPoints.get(0), tl, bl, tailPoints.get(0)); calcPointsWestOrEast(points, tl, bl, headPoints, tailPoints); } private void calcPointsWestOrEast(List<Point> points, Point crl1, Point crl2, List<Point> headPoints, List<Point> tailPoints) { int upSize = 2; if (headPoints.get(0).y > crl1.y) { for (int i = headPoints.size() - 1; i > 0; i--) { Point point = headPoints.get(i); Point firstPoint = points.get(0); Point secPoint = points.get(1); if (firstPoint.y == secPoint.y && !firstPoint.equals(secPoint)) break; if (!secPoint.equals(crl1) && secPoint.y == point.y) { points.remove(1); points.add(1, point); } else if (isUpperLine(firstPoint, secPoint, point)) { points.add(1, point); upSize++; } } } for (int i = 1; i < upSize - 1; i++) { if (!isUpperLine(points.get(i - 1), points.get(i + 1), points.get(i))) { points.remove(i--); upSize--; if (i > 1) i--; } } if (tailPoints.get(0).y < crl2.y) { for (int i = tailPoints.size() - 1; i > 0; i--) { Point point = tailPoints.get(i); Point lastPoint = points.get(points.size() - 1); Point penPoint = points.get(points.size() - 2); if (lastPoint.y == penPoint.y) break; if (!penPoint.equals(crl2) && penPoint.y == point.y) { points.remove(points.size() - 2); points.add(points.size() - 1, point); } else if (!isUpperLine(penPoint, lastPoint, point)) { points.add(points.size() - 1, point); } } } for (int i = upSize + 1; i < points.size() - 1; i++) { if (isUpperLine(points.get(i - 1), points.get(i + 1), points.get(i))) { points.remove(i--); if (i > upSize) i--; } } } private void collectPointsSouthDire(List<Point> points, List<ITopicPart> topics, Rectangle box) { int lineWidth1 = getLineWidth(); int lineWidth2 = lineWidth1 > 1 ? lineWidth1 - 1 : lineWidth1; Map<ITopicPart, Integer> headMap = new HashMap<ITopicPart, Integer>(); for (ITopicPart topic : topics) { Rectangle bounds = topic.getFigure().getBounds(); ITopicPart t = findTopicByValue(headMap, -bounds.bottom()); if (t != null) { if (bounds.x < t.getFigure().getBounds().x) { headMap.remove(t); headMap.put(topic, -bounds.bottom()); } } else { headMap.put(topic, -bounds.bottom()); } } List<Point> headPoints = new ArrayList<Point>(); for (ITopicPart topic : sortTopicPart(headMap)) headPoints.add(topic.getFigure().getBounds().getBottomLeft() .getTranslated(-MARGIN_WIDTH + lineWidth2, MARGIN_HEIGHT - lineWidth2)); Map<ITopicPart, Integer> tailMap = new HashMap<ITopicPart, Integer>(); for (ITopicPart topic : topics) { Rectangle bounds = topic.getFigure().getBounds(); ITopicPart t = findTopicByValue(tailMap, -bounds.bottom()); if (t != null) { if (bounds.right() > t.getFigure().getBounds().right()) { tailMap.remove(t); tailMap.put(topic, -bounds.bottom()); } } else { tailMap.put(topic, -bounds.bottom()); } } List<Point> tailPoints = new ArrayList<Point>(); for (ITopicPart topic : sortTopicPart(tailMap)) tailPoints.add(topic.getFigure().getBounds().getBottomRight() .getTranslated(MARGIN_WIDTH - lineWidth1, MARGIN_HEIGHT - lineWidth2)); Point tl = box.getTopLeft(); Point tr = box.getTopRight(); initPoints(points, headPoints.get(0), tl, tr, tailPoints.get(0)); calcPointsSouthOrNorth(points, tl, tr, headPoints, tailPoints); } private void collectPointsNorthDire(List<Point> points, List<ITopicPart> topics, Rectangle box) { int lineWidth1 = getLineWidth(); int lineWidth2 = lineWidth1 > 1 ? lineWidth1 - 1 : lineWidth1; Map<ITopicPart, Integer> headMap = new HashMap<ITopicPart, Integer>(); for (ITopicPart topic : topics) { Rectangle bounds = topic.getFigure().getBounds(); ITopicPart t = findTopicByValue(headMap, bounds.y); if (t != null) { if (bounds.x < t.getFigure().getBounds().x) { headMap.remove(t); headMap.put(topic, bounds.y); } } else { headMap.put(topic, bounds.y); } } List<Point> headPoints = new ArrayList<Point>(); for (ITopicPart topic : sortTopicPart(headMap)) headPoints.add(topic.getFigure().getBounds().getTopLeft() .getTranslated(-MARGIN_WIDTH + lineWidth2, -MARGIN_HEIGHT + lineWidth1)); Map<ITopicPart, Integer> tailMap = new HashMap<ITopicPart, Integer>(); for (ITopicPart topic : topics) { Rectangle bounds = topic.getFigure().getBounds(); ITopicPart t = findTopicByValue(tailMap, bounds.y); if (t != null) { if (bounds.right() > t.getFigure().getBounds().right()) { tailMap.remove(t); tailMap.put(topic, bounds.y); } } else { tailMap.put(topic, bounds.y); } } List<Point> tailPoints = new ArrayList<Point>(); for (ITopicPart topic : sortTopicPart(tailMap)) tailPoints.add(topic.getFigure().getBounds().getTopRight() .getTranslated(MARGIN_WIDTH - lineWidth1, -MARGIN_HEIGHT + lineWidth1)); Point bl = box.getBottomLeft(); Point br = box.getBottomRight(); initPoints(points, headPoints.get(0), bl, br, tailPoints.get(0)); calcPointsSouthOrNorth(points, bl, br, headPoints, tailPoints); } private void calcPointsSouthOrNorth(List<Point> points, Point crl1, Point crl2, List<Point> headPoints, List<Point> tailPoints) { int leftSize = 2; if (headPoints.get(0).x > crl1.x) { for (int i = headPoints.size() - 1; i > 0; i--) { Point point = headPoints.get(i); Point firstPoint = points.get(0); Point secPoint = points.get(1); if (firstPoint.x == secPoint.x) break; if (!(secPoint.equals(crl1)) && secPoint.x == point.x) { points.remove(1); points.add(1, point); } else { if (isLeftLine(firstPoint, secPoint, point)) { points.add(1, point); leftSize++; } } } } for (int i = 1; i < leftSize - 1; i++) { if (!isLeftLine(points.get(i - 1), points.get(i + 1), points.get(i))) { points.remove(i--); leftSize--; if (i > 1) i--; } } if (tailPoints.get(0).x < crl2.x) { for (int i = tailPoints.size() - 1; i > 0; i--) { Point point = tailPoints.get(i); Point lastPoint = points.get(points.size() - 1); Point penPoint = points.get(points.size() - 2); if (lastPoint.x == penPoint.x) break; if (!penPoint.equals(crl2) && penPoint.x == point.x) { points.remove(points.size() - 2); points.add(points.size() - 1, point); } else { if (!isLeftLine(penPoint, lastPoint, point)) { points.add(points.size() - 1, point); } } } } for (int i = leftSize + 1; i < points.size() - 1; i++) { if (isLeftLine(points.get(i - 1), points.get(i + 1), points.get(i))) { points.remove(i--); if (i > leftSize) i--; } } } private void initPoints(List<Point> points, Point p1, Point p2, Point p3, Point p4) { points.add(p1); points.add(p2); points.add(p3); points.add(p4); } private int calcChildDirection(IBranchPart branch) { IStructure structure = branch.getBranchPolicy().getStructure(branch); if (structure instanceof IBranchStructureExtension) return ((IBranchStructureExtension) structure) .getChildTargetOrientation(branch.getParentBranch(), branch); return PositionConstants.NONE; } private List<ITopicPart> collectTopics(IBoundaryPart boundary) { List<ITopicPart> topics = new ArrayList<ITopicPart>(); List<IBranchPart> branches = boundary.getEnclosingBranches(); if (!branches.isEmpty()) { for (IBranchPart branch : branches) { topics.add(branch.getTopicPart()); addAllTopics(branch, topics); } } else { IBranchPart branch = boundary.getOwnedBranch(); topics.add(branch.getTopicPart()); addAllTopics(branch, topics); } return topics; } private void addAllTopics(IBranchPart branch, List<ITopicPart> topics) { for (IBranchPart sub : branch.getSubBranches()) { topics.add(sub.getTopicPart()); addAllTopics(sub, topics); } for (IBranchPart callout : branch.getCalloutBranches()) { topics.add(callout.getTopicPart()); addAllTopics(callout, topics); } for (IBranchPart summary : branch.getSummaryBranches()) { topics.add(summary.getTopicPart()); addAllTopics(summary, topics); } } private boolean isUpperLine(Point p1, Point p2, Point p3) { if (p1.y < p3.y) return false; float dx1 = Math.abs(p2.x - p1.x); float dy1 = Math.abs(p2.y - p1.y); float dx2 = Math.abs(p3.x - p1.x); float dy2 = Math.abs(p3.y - p1.y); return dy1 / dx1 < dy2 / dx2; } private boolean isLeftLine(Point p1, Point p2, Point p3) { if (p1.x < p3.x) return false; float dx1 = Math.abs(p2.x - p1.x); float dy1 = Math.abs(p2.y - p1.y); float dx2 = Math.abs(p3.x - p1.x); float dy2 = Math.abs(p3.y - p1.y); return dx1 / dy1 < dx2 / dy2; } private Set<ITopicPart> sortTopicPart(Map<ITopicPart, Integer> topicMap) { HashMap<ITopicPart, Integer> map = new HashMap<ITopicPart, Integer>(); ValueComparator bvc = new ValueComparator(map); TreeMap<ITopicPart, Integer> sortedMap = new TreeMap<ITopicPart, Integer>( bvc); map.putAll(topicMap); sortedMap.putAll(map); return sortedMap.keySet(); } private ITopicPart findTopicByValue(Map<ITopicPart, Integer> map, Integer value) { for (Entry<ITopicPart, Integer> entry : map.entrySet()) { if (value == entry.getValue() || (value != null && value.equals(entry.getValue()))) return entry.getKey(); } return null; } }