/* ****************************************************************************** * 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.fishbone.structures; import java.util.List; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.PositionConstants; import org.eclipse.draw2d.geometry.Dimension; import org.eclipse.draw2d.geometry.Insets; import org.eclipse.draw2d.geometry.Point; import org.eclipse.draw2d.geometry.Rectangle; import org.xmind.gef.GEF; import org.xmind.gef.draw2d.IAnchor; import org.xmind.gef.draw2d.IReferencedFigure; import org.xmind.gef.draw2d.ReferencedLayoutData; import org.xmind.gef.draw2d.geometry.Geometry; import org.xmind.gef.draw2d.geometry.IPrecisionTransformer; import org.xmind.gef.draw2d.geometry.PrecisionDimension; import org.xmind.gef.draw2d.geometry.PrecisionInsets; import org.xmind.gef.draw2d.geometry.PrecisionPoint; import org.xmind.gef.draw2d.geometry.PrecisionRectangle; import org.xmind.gef.graphicalpolicy.IStructure; import org.xmind.gef.part.IPart; import org.xmind.ui.branch.AbstractBranchStructure; import org.xmind.ui.branch.BoundaryLayoutHelper; import org.xmind.ui.branch.IBranchPolicy; import org.xmind.ui.branch.IInsertion; import org.xmind.ui.branch.Insertion; import org.xmind.ui.internal.figures.TopicFigure; import org.xmind.ui.internal.fishbone.Fishbone; import org.xmind.ui.internal.fishbone.decorations.MainFishboneBranchDecoration; import org.xmind.ui.mindmap.IBranchPart; import org.xmind.ui.mindmap.IBranchRangePart; import org.xmind.ui.mindmap.INodePart; import org.xmind.ui.mindmap.IPlusMinusPart; import org.xmind.ui.mindmap.ISummaryPart; import org.xmind.ui.mindmap.ITopicPart; import org.xmind.ui.mindmap.MindMapUI; import org.xmind.ui.tools.ParentSearchKey; @SuppressWarnings("restriction") public class MainFishboneStructure extends AbstractBranchStructure { private static final double sin = Math.sin(Math .toRadians(Fishbone.RotateAngle)); private static final double cos = Math.cos(Math .toRadians(Fishbone.RotateAngle)); private static final double fMajor = 0.5d; private static final double fMinor = 5d; private final IMainDirection direction; public MainFishboneStructure(IMainDirection direction) { this.direction = direction; } protected void addExtraSpaces(IBranchPart branch, ReferencedLayoutData data) { super.addExtraSpaces(branch, data); Insets insets = new Insets(); if (direction == IMainDirection.LeftHeaded) { insets.right = MainFishboneBranchDecoration.TailLength + 5; } else { insets.left = MainFishboneBranchDecoration.TailLength + 5; } data.addMargins(insets); } protected void doFillPlusMinus(IBranchPart branch, IPlusMinusPart plusMinus, LayoutInfo info) { Point ref = info.getReference(); MainFishboneData fd = getCastedData(branch); fd.setOrigin(ref); Rectangle topicBounds = info.getCheckedClientArea(); topicBounds = fd.hf.tr(topicBounds); IFigure figure = plusMinus.getFigure(); Dimension size = figure.getPreferredSize(); int x = topicBounds.right() + 1; int y = ref.y - size.height / 2; Rectangle r = new Rectangle(x, y, size.width, size.height); info.put(figure, fd.hf.rr(r)); } protected void doFillSubBranches(IBranchPart branch, List<IBranchPart> subBranches, LayoutInfo info) { double mainSpacing = getMajorSpacing(branch) * fMajor; double subSpacing = getMinorSpacing(branch) * fMinor; Point ref = info.getReference(); MainFishboneData fd = getCastedData(branch); fd.setOrigin(ref); Rectangle refBounds = info.getCheckedClientArea(); refBounds = fd.hf.tr(refBounds); fd.upSide.start(refBounds.right() + mainSpacing); fd.downSide.start(fd.upSide.right + subSpacing); int num = subBranches.size(); IInsertion insertion = getCurrentInsertion(branch); BoundaryLayoutHelper helper = getBoundaryLayoutHelper(branch); // int y = ref.y; Side lastSide = fd.downSide; double width = 0.0; for (int i = 0; i < num; i++) { IBranchPart subBranch = subBranches.get(i); IFigure subBranchFigure = subBranch.getFigure(); Insets ins = helper.getInsets(subBranch); boolean upwards = getCastedData(branch).isUpwardBranch(i); Side side = upwards ? fd.upSide : fd.downSide; IPrecisionTransformer pvf = fd.pvf; pvf.setEnabled(!upwards); // PrecisionInsets fChildBorder = pvf.ti(fd.phf // .ti(new PrecisionInsets(subBranchFigure.getInsets()))); PrecisionInsets fChildBorder = pvf.ti(fd.phf .ti(new PrecisionInsets(ins))); if (fChildBorder.left != 0) { side.useRight = false; } PrecisionRectangle childBounds = new PrecisionRectangle(); double joint; double rotatedBottom; IBranchPolicy branchPolicy = subBranch.getBranchPolicy(); IStructure sa = branchPolicy.getStructure(subBranch); FishboneData sub = getFishboneData(subBranch, sa); if (sub != null) { PrecisionInsets fChildBranchRotated = pvf.ti(fd.phf .ti(sub.rBranchRefIns)); PrecisionInsets fChildBranchNormal = fd.phf .ti(sub.branchRefIns); PrecisionInsets fChildTopicNormal = fd.phf.ti(sub.topicRefIns); double left = fChildBranchRotated.left + fChildBorder.left; double bottom = fChildBranchRotated.bottom + mainSpacing + fChildBorder.bottom; double jointOff = left - (bottom * cos - fChildTopicNormal.bottom) / sin; if (side.useRight) { double rotatedSpacing = (fChildBranchNormal.top + fChildTopicNormal.bottom) / sin; joint = side.rotatedBottom + rotatedSpacing; } else { joint = Math.max(side.right, side.right + jointOff); } if (!upwards || i > 0) { joint = Math.max(joint, lastSide.lastJoint + subSpacing); } Dimension size = subBranchFigure.getPreferredSize(); childBounds.setSize(size.width, size.height); childBounds.x = joint - jointOff; if (side == fd.upSide) { childBounds.y = ref.y - size.height - mainSpacing - fChildBorder.bottom; } else { childBounds.y = ref.y + mainSpacing + fChildBorder.bottom;/////////// } if (fChildBorder.getHeight() != 0) if (width <= childBounds.right()) width = childBounds.right(); rotatedBottom = joint + (fChildBranchNormal.bottom - fChildTopicNormal.bottom) / sin + subSpacing; } else { PrecisionInsets childBranchNormal = new PrecisionInsets( ((IReferencedFigure) subBranchFigure) .getReferenceDescription()); PrecisionInsets fChildBranchNormal = pvf.ti(fd.phf .ti(childBranchNormal)); double bottom = fChildBranchNormal.bottom + mainSpacing; double rotatedSpacing = fChildBranchNormal.top * cos / sin; if (side.useRight) { joint = side.rotatedBottom + rotatedSpacing; } else { joint = side.right; } if (!upwards || i > 0) { joint = Math.max(joint, lastSide.lastJoint + subSpacing); } double jointOff = bottom * cos / sin; Dimension size = subBranchFigure.getPreferredSize(); childBounds.setSize(size.width, size.height); childBounds.x = joint + jointOff; if (side == fd.upSide) { childBounds.y = ref.y - size.height - mainSpacing - fChildBorder.bottom;//// } else { childBounds.y = ref.y + mainSpacing + fChildBorder.bottom;// } if (fChildBorder.getHeight() != 0) if (width <= childBounds.right()) width = childBounds.right(); rotatedBottom = joint + fChildBranchNormal.getWidth() + fChildBranchNormal.bottom * cos / sin + subSpacing; } if (insertion != null && i >= insertion.getIndex()) { childBounds.x += insertion.getSize().width; } IPrecisionTransformer phf = fd.phf; PrecisionRectangle precRect = phf.rr(childBounds); Rectangle rect = precRect.toDraw2DRectangle(); info.put(subBranchFigure, rect); side.lastJoint = joint; side.useRight = fChildBorder.right == 0; side.rotatedBottom = rotatedBottom; if (side.useRight) side.right = childBounds.right() + subSpacing / 3; else side.right = width + subSpacing / 3; lastSide = side; } } private FishboneData getFishboneData(IBranchPart subBranch, IStructure sa) { if (sa instanceof SubFishboneStructure) { return ((SubFishboneStructure) sa).getCastedData(subBranch) .getFishboneData(); } return null; } protected Object createStructureData(IBranchPart branch) { return new MainFishboneData(branch, direction.isTransformerEnabled()); } protected boolean isValidStructureData(IBranchPart branch, Object data) { return super.isValidStructureData(branch, data) && (data instanceof MainFishboneData); } protected MainFishboneData getCastedData(IBranchPart branch) { return (MainFishboneData) super.getStructureData(branch); } public int calcChildDistance(IBranchPart branch, ParentSearchKey key) { Point childRef = key.getFigure().getReference(); Point branchRef = getReference(branch); MainFishboneData fd = getCastedData(branch); fd.setOrigin(branchRef); PrecisionPoint source = calcSourceAnchorLocation(branch); int jointOffset = calcChildJointOffset(branch, key.getFeedback(), source); if (jointOffset > 0) { int range; Rectangle childrenNodesBounds = getChildrenNodesBounds(branch); if (childrenNodesBounds != null) { Rectangle r = fd.hf.tr(childrenNodesBounds); range = r.right() - branchRef.x + MindMapUI.SEARCH_RANGE / 2; } else { range = MindMapUI.SEARCH_RANGE; } if (jointOffset < range) { int dy = Math.abs(childRef.y - branchRef.y); if (dy < MindMapUI.SEARCH_RANGE) return dy; } } return super.calcChildDistance(branch, key); } private PrecisionPoint calcSourceAnchorLocation(IBranchPart branch) { return ((INodePart) branch.getTopicPart()).getSourceAnchor(branch) .getLocation(getSourceOrientation(branch), 0); } private Rectangle getChildrenNodesBounds(IBranchPart branch) { Rectangle r = null; for (IBranchPart subbranch : branch.getSubBranches()) { r = Geometry.union(r, subbranch.getTopicPart().getFigure() .getBounds()); } return r; } private int calcChildJointOffset(IBranchPart branch, IBranchPart child, PrecisionPoint source) { double angle = calcChildRotateAngle(branch, child); PrecisionPoint target = calcChildTargetLocation(branch, child, source); PrecisionDimension d = target.getDifference(source); int w = (int) Math.floor(d.height / Math.tan(Math.toRadians(angle)) + 0.0000001); int offset = (int) Math.floor(d.width - w + 0.0000001); if (direction == IMainDirection.RightHeaded) return -offset; return offset; } private PrecisionPoint calcChildTargetLocation(IBranchPart branch, IBranchPart child, PrecisionPoint source) { ITopicPart topic = child.getTopicPart(); if (topic instanceof INodePart) { IAnchor anchor = ((INodePart) topic).getTargetAnchor(branch); if (anchor != null) { return anchor.getLocation( getChildTargetOrientation(branch, child), 0); } } return new PrecisionPoint(getReference(child)); } public boolean isChildUpwards(IBranchPart branch, IBranchPart child) { return isChildUpwards(branch, child, branch.getSubBranches().indexOf(child)); } private boolean isChildUpwards(IBranchPart branch, IBranchPart child, int childIndex) { if (childIndex < 0) { Point branchRef = getReference(branch); Point childRef = getReference(child); return childRef.y < branchRef.y; } return getCastedData(branch).isUpwardBranch(childIndex); } private Point getReference(IBranchPart branch) { ITopicPart topic = branch.getTopicPart(); if (topic != null) return ((IReferencedFigure) topic.getFigure()).getReference(); return ((IReferencedFigure) branch.getFigure()).getReference(); } private double calcChildRotateAngle(IBranchPart branch, IBranchPart child) { return isChildUpwards(branch, child) ? direction.getUpRotated() .getRotateAngle() : direction.getDownRotated().getRotateAngle(); // return direction.getChildRotateAngle(isChildUpwards(branch, child)); } protected int calcInsIndex(IBranchPart branch, ParentSearchKey key, boolean withDisabled) { if (branch.getSubBranches().isEmpty() || branch.isFolded()) return withDisabled ? 0 : -1; PrecisionPoint source = calcSourceAnchorLocation(branch); int jointOffset = calcChildJointOffset(branch, key.getFeedback(), source); List<IBranchPart> subBranches = branch.getSubBranches(); int num = subBranches.size(); IInsertion lastInsertion = getCurrentInsertion(branch); int ret = 0; for (int i = 0; i < num; i++) { IBranchPart subBranch = subBranches.get(i); int j = calcChildJointOffset(branch, subBranch, source); if (lastInsertion != null && i >= lastInsertion.getIndex()) { j -= lastInsertion.getSize().width; } int hint = j; if (jointOffset < hint) return ret; if (withDisabled || subBranch.getFigure().isEnabled()) ret++; } return withDisabled ? num : -1; } private static final Dimension INSERTION_SIZE = new Dimension(30, 1); public IInsertion calcInsertion(IBranchPart branch, ParentSearchKey key) { return new Insertion(branch, calcInsIndex(branch, key, true), INSERTION_SIZE); } public IPart calcChildNavigation(IBranchPart branch, IBranchPart sourceChild, String navReqType, boolean sequential) { if (GEF.REQ_NAV_DOWN.equals(navReqType) || GEF.REQ_NAV_UP.equals(navReqType)) { int childIndex = sourceChild.getBranchIndex(); boolean upwards = isChildUpwards(branch, sourceChild, childIndex); if ((upwards && GEF.REQ_NAV_DOWN.equals(navReqType)) || (!upwards && GEF.REQ_NAV_UP.equals(navReqType))) { for (int i = childIndex - 1; i >= 0; i--) { IBranchPart sub = branch.getSubBranches().get(i); if (isChildUpwards(branch, sub, i) != upwards) return sub.getTopicPart(); } return getSubTopicPart(branch, childIndex + 1); } } else { boolean next = (direction == IMainDirection.LeftHeaded && GEF.REQ_NAV_RIGHT .equals(navReqType)) || (direction == IMainDirection.RightHeaded && GEF.REQ_NAV_LEFT .equals(navReqType)); boolean prev = (direction == IMainDirection.LeftHeaded && GEF.REQ_NAV_LEFT .equals(navReqType)) || (direction == IMainDirection.RightHeaded && GEF.REQ_NAV_RIGHT .equals(navReqType)); if (next || prev) { int childIndex = sourceChild.getBranchIndex(); boolean upwards = isChildUpwards(branch, sourceChild, childIndex); for (int i = prev ? childIndex - 1 : childIndex + 1; prev ? i >= 0 : i < branch.getSubBranches().size();) { IBranchPart sub = branch.getSubBranches().get(i); if (isChildUpwards(branch, sourceChild, i) == upwards) { return sub.getTopicPart(); } if (prev) { i--; } else { i++; } } if (prev && !sequential) { IPart prevTopic = getSubTopicPart(branch, childIndex - 1); if (prevTopic != null) return prevTopic; return branch.getTopicPart(); } return getSubTopicPart(branch, childIndex + 1); } } return super.calcChildNavigation(branch, sourceChild, navReqType, sequential); } public IPart calcNavigation(IBranchPart branch, String navReqType) { if (direction == IMainDirection.RightHeaded) { if (GEF.REQ_NAV_LEFT.equals(navReqType)) { return getSubTopicPart(branch, 0); } } else { if (GEF.REQ_NAV_RIGHT.equals(navReqType)) { return getSubTopicPart(branch, 0); } } return super.calcNavigation(branch, navReqType); } public int getChildTargetOrientation(IBranchPart branch, IBranchPart subBranch) { int index = branch.getSubBranches().indexOf(subBranch); if (index < 0) return direction == IMainDirection.RightHeaded ? PositionConstants.EAST : PositionConstants.WEST; if (isChildUpwards(branch, subBranch, index)) { if (direction == IMainDirection.RightHeaded) return PositionConstants.EAST; return PositionConstants.WEST; } if (direction == IMainDirection.RightHeaded) return PositionConstants.WEST; return PositionConstants.EAST; } public int getSourceOrientation(IBranchPart branch) { if (direction == IMainDirection.RightHeaded) return PositionConstants.WEST; return PositionConstants.EAST; } public int getRangeGrowthDirection(IBranchPart branch, IBranchRangePart range) { if (direction == IMainDirection.RightHeaded) return PositionConstants.WEST; return PositionConstants.EAST; } public int getSummaryDirection(IBranchPart branch, ISummaryPart summary) { List<IBranchPart> enclosing = summary.getEnclosingBranches(); if (!enclosing.isEmpty()) { if (getCastedData(branch).isUpwardBranch( enclosing.get(0).getBranchIndex())) return PositionConstants.NORTH; return PositionConstants.SOUTH; } return PositionConstants.NORTH; } public int getQuickMoveOffset(IBranchPart branch, IBranchPart child, int direction) { if (direction == PositionConstants.EAST) { if (this.direction == IMainDirection.RightHeaded) return -1; return 1; } else if (direction == PositionConstants.WEST) { if (this.direction == IMainDirection.RightHeaded) return 1; return -1; } return super.getQuickMoveOffset(branch, child, direction); } @Override protected Point calcInsertPosition(IBranchPart branch, IBranchPart child, ParentSearchKey key) { List<IBranchPart> subBranches = branch.getSubBranches(); if (subBranches.isEmpty()) return calcFirstChildPosition(branch, key); int index = calcInsIndex(branch, key, true); if (index == subBranches.size()) { return calcInventPosition(subBranches.get(index - 1), null, key, false); } return calcInventPosition(subBranches.get(index), null, key, true); } @Override protected Point calcMovePosition(IBranchPart branch, IBranchPart child, ParentSearchKey key) { List<IBranchPart> subBranches = branch.getSubBranches(); List<Integer> disables = getDisableBranches(branch); int index = calcInsIndex(branch, key, true); int oldIndex = getOldIndex(branch, child); if (disables != null) { if (disables.contains(index - 1)) { index--; oldIndex = index; } else if (disables.contains(index)) { oldIndex = index; } } Dimension inventSize = key.getInvent().getSize(); boolean upWard = (index + 1) % 2 == 0; boolean leftWard = IMainDirection.LeftHeaded.equals(direction); if (index == oldIndex) { IBranchPart sub = subBranches.get(index); Dimension size = sub.getTopicPart().getFigure().getSize(); double w = (size.height * sin - size.width * cos) / (sin * sin - cos * cos); double deltaX = size.width * 0.5d - w * cos + inventSize.width * 0.5d + inventSize.width * cos * 0.5d; double deltaY = upWard ? (-size.height + inventSize.width * sin) * 0.5d : (size.height - inventSize.width * sin) * 0.5d; return getReference(sub).getTranslated(leftWard ? deltaX : -deltaX, deltaY); } return calcInsertPosition(branch, child, key); } @Override protected Point calcInventPosition(IBranchPart orientation, IBranchPart assist, ParentSearchKey key, boolean isBeforeOrientation) { Dimension inventSize = key.getInvent().getSize(); Dimension insSize = key.getFigure().getSize(); double deltaY = inventSize.width * sin * 0.5d; boolean upwards = isChildUpwards(orientation.getParentBranch(), orientation); double y = upwards ^ isBeforeOrientation ? deltaY : -deltaY; double x; if (isBeforeOrientation) { double offset = calcBeforeOffset(orientation); double deltaX = (insSize.height / sin - inventSize.width * cos - inventSize.width) * 0.5d; x = offset + (direction.equals(IMainDirection.RightHeaded) ? deltaX : -deltaX); } else { Rectangle pBounds = orientation.getParentBranch().getFigure() .getBounds(); x = direction.equals(IMainDirection.RightHeaded) ? pBounds.x : pBounds.right(); } return new Point().getTranslated(x, y); } private double calcBeforeOffset(IBranchPart branch) { double offset = 0.0d; double cot = cos / sin; List<IBranchPart> callouts = branch.getCalloutBranches(); if (callouts == null || callouts.isEmpty()) { IFigure figure = branch.getTopicPart().getFigure(); Rectangle bounds = figure.getBounds(); double height = ((TopicFigure) figure) .getNormalPreferredBounds(new Point()).height; double delta = height * cos * cot; offset = direction.equals(IMainDirection.RightHeaded) ? bounds .right() + delta : bounds.x - delta; } else { Rectangle bounds = branch.getFigure().getBounds(); offset = direction.equals(IMainDirection.RightHeaded) ? bounds .right() : bounds.x; for (IBranchPart callout : callouts) { Rectangle calloutBounds = callout.getFigure().getBounds(); if (direction.equals(ISubDirection.NE)) { Point tl = calloutBounds.getTopLeft(); offset = Math.min(offset, tl.x - (-tl.y) * cot); } else if (direction.equals(ISubDirection.SE)) { Point bl = calloutBounds.getBottomLeft(); offset = Math.min(offset, bl.x - (bl.y) * cot); } else if (direction.equals(ISubDirection.NW)) { Point tr = calloutBounds.getTopRight(); offset = Math.max(offset, tr.x + (tr.y) * cot); } else { Point br = calloutBounds.getBottomRight(); offset = Math.max(offset, br.x + (br.y) * cot); } } } return offset; } @Override protected Point calcFirstChildPosition(IBranchPart branch, ParentSearchKey key) { double minorSpacing = getMinorSpacing(branch) * fMinor; double x = branch.getFigure().getSize().width * 0.5d + key.getFigure().getSize().width * 0.5d; return getFigureLocation(branch.getFigure()).getTranslated( IMainDirection.LeftHeaded.equals(direction) ? x : -x, -minorSpacing); } }