/* ****************************************************************************** * 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.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; 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)); } public int calcChildIndex(IBranchPart branch, ParentSearchKey key) { return calcChildIndex(branch, key, false); } private int calcChildIndex(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, calcChildIndex(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); } }