package org.xmind.ui.internal.branch; import java.util.HashSet; import java.util.List; import java.util.Set; 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.IReferencedFigure; import org.xmind.gef.part.IPart; import org.xmind.ui.branch.AbstractBranchStructure; import org.xmind.ui.branch.IFreeableBranchStructureExtension; import org.xmind.ui.branch.IInsertion; import org.xmind.ui.branch.ILockableBranchStructureExtension; import org.xmind.ui.branch.Insertion; import org.xmind.ui.mindmap.IBranchPart; import org.xmind.ui.mindmap.IBranchRangePart; import org.xmind.ui.mindmap.IPlusMinusPart; import org.xmind.ui.mindmap.ISummaryPart; import org.xmind.ui.mindmap.ITopicPart; import org.xmind.ui.tools.ParentSearchKey; import org.xmind.ui.util.MindMapUtils; public abstract class BaseRadialStructure extends AbstractBranchStructure implements ILockableBranchStructureExtension, IFreeableBranchStructureExtension { protected static class RadiationInsertion extends Insertion { boolean right; public RadiationInsertion(IBranchPart parent, int index, Dimension size, boolean right) { super(parent, index, size); this.right = right; } public void pushIn() { super.pushIn(); } public void pullOut() { super.pullOut(); } public boolean equals(Object obj) { if (obj == this) return true; if (obj == null || !(obj instanceof RadiationInsertion)) return false; RadiationInsertion that = (RadiationInsertion) obj; return super.equals(obj) && this.right == that.right; } } public static final String CACHE_NUMBER_RIGHT_BRANCHES = RadialStructure.class .getName() + ".numberRightBranches"; //$NON-NLS-1$ public Set<IBranchPart> calculatingBranches = new HashSet<IBranchPart>(); public BaseRadialStructure() { super(); } protected boolean isValidStructureData(IBranchPart branch, Object data) { return super.isValidStructureData(branch, data) && (data instanceof RadialData); } protected Object createStructureData(IBranchPart branch) { return new RadialData(branch); } protected RadialData getRadialData(IBranchPart branch) { return (RadialData) this.getStructureData(branch); } public boolean isChildLeft(IBranchPart branch, IBranchPart child) { if (branch == null) return true; if (branch.isCentral()) { Point pos = (Point) MindMapUtils.getCache(child, IBranchPart.CACHE_PREF_POSITION); if (pos != null) { return RadialUtils.isLeft(0, pos.x); } } if (calculatingBranches.contains(branch)) { // avoid recursively calling return false; } calculatingBranches.add(branch); boolean left; int index = branch.getSubBranches().indexOf(child); if (index >= 0) { left = index >= getRadialData(branch).getNumRight(); } else if (branch.getSummaryBranches().contains(child)) { left = isSummaryChildLeft(branch, child); } else if (branch.getCalloutBranches().contains(child)) { left = isChildLeft(branch.getParentBranch(), branch); } else { left = RadialUtils.isLeft(getReference(branch).x, getReference(child).x); } calculatingBranches.remove(branch); return left; } public boolean isSummaryChildLeft(IBranchPart branch, IBranchPart child) { ISummaryPart summary = MindMapUtils.findAttachedSummary(branch, child); if (summary != null) { int direction = getSummaryDirection(branch, summary); return direction == PositionConstants.WEST; } return false; } public int getSummaryDirection(IBranchPart branch, ISummaryPart summary) { List<IBranchPart> enclosingBranches = summary.getEnclosingBranches(); if (!enclosingBranches.isEmpty()) { IBranchPart subBranch = enclosingBranches.get(0); int index = subBranch.getBranchIndex(); if (index >= 0) { if (index >= getRadialData(branch).getNumRight()) { return PositionConstants.WEST; } } } return PositionConstants.EAST; } public int getRangeGrowthDirection(IBranchPart branch, IBranchRangePart range) { return PositionConstants.SOUTH; } public Point getReference(IBranchPart branch) { ITopicPart topic = branch.getTopicPart(); if (topic != null) { return ((IReferencedFigure) topic.getFigure()).getReference(); } return ((IReferencedFigure) branch.getFigure()).getReference(); } protected void doFillPlusMinus(IBranchPart branch, IPlusMinusPart plusMinus, LayoutInfo info) { Point ref = info.getReference(); IFigure fig = plusMinus.getFigure(); Rectangle topicBounds = info.getCheckedClientArea(); Dimension pmSize = fig.getPreferredSize(); int pmx = ref.x - pmSize.width / 2; int pmy = topicBounds.bottom(); Rectangle pmBounds = new Rectangle(pmx, pmy, pmSize.width, pmSize.height); info.put(fig, pmBounds); } protected Dimension getOffset(IBranchPart subBranch) { Point pos = (Point) MindMapUtils.getCache(subBranch, IBranchPart.CACHE_PREF_POSITION); if (pos != null) { return new Dimension(pos.x, pos.y); } return null; } public IPart calcNavigation(IBranchPart branch, String navReqType) { if (GEF.REQ_NAV_RIGHT.equals(navReqType)) { if (!branch.getSubBranches().isEmpty()) return getSubTopicPart(branch, 0); } else if (GEF.REQ_NAV_LEFT.equals(navReqType)) { int numSecond = getRadialData(branch).getNumLeft(); if (numSecond > 0) { int numFirst = getRadialData(branch).getNumRight(); return getSubTopicPart(branch, numFirst); } } return super.calcNavigation(branch, navReqType); } public int getChildTargetOrientation(IBranchPart branch, IBranchPart subBranch) { return isChildLeft(branch, subBranch) ? PositionConstants.EAST : PositionConstants.WEST; } public int getSourceOrientation(IBranchPart branch) { return PositionConstants.NONE; } public boolean isChildrenFreeable(IBranchPart branch) { return branch.isCentral(); } public int calcChildDistance(IBranchPart branch, ParentSearchKey key) { Point parentRef = getReference(branch); Point childRef = key.getFigure().getReference(); Rectangle childBounds = key.getFigure().getBounds(); Point childAnc = RadialUtils.isLeft(parentRef.x, childRef.x) ? childBounds.getRight() : childBounds.getLeft(); int d = (int) childAnc.getDistance(parentRef); Dimension ovalSize = getRadialData(branch).getOvalSize(); int r = Math.max(ovalSize.width, ovalSize.height); if (d <= r + 50) return d; if (d <= r + 300) return d * d; return super.calcChildDistance(branch, key); } public IInsertion calcInsertion(IBranchPart branch, ParentSearchKey key) { Point ref; ITopicPart topic = branch.getTopicPart(); if (topic != null) { ref = ((IReferencedFigure) topic.getFigure()).getReference(); } else { ref = ((IReferencedFigure) branch.getFigure()).getReference(); } return new RadiationInsertion(branch, calcInsIndex(branch, key, true), calcInsSize(branch, key), calcInsSide(branch, ref, key)); } public void lock(IBranchPart branch) { MindMapUtils.setCache(branch, CACHE_NUMBER_RIGHT_BRANCHES, getRadialData(branch).getNumRight()); } public void unlock(IBranchPart branch) { MindMapUtils.flushCache(branch, CACHE_NUMBER_RIGHT_BRANCHES); } protected int calcInsIndex(IBranchPart branch, ParentSearchKey key, boolean withDisabled) { List<IBranchPart> subBranches = branch.getSubBranches(); if (subBranches.isEmpty()) return withDisabled ? 0 : -1; if (branch.isFolded()) return withDisabled ? 0 : -1; ITopicPart topic = branch.getTopicPart(); if (topic == null) return withDisabled ? 0 : -1; Point childRef = key.getFigure().getReference(); Point ref = ((IReferencedFigure) topic.getFigure()).getReference(); RadialData cache = getRadialData(branch); int numRight = cache.getNumRight(); int[] childrenSpacings = cache.getChildrenSpacings(); int num = subBranches.size(); boolean right = true; Dimension insSize = calcInsSize(branch, key); int insHeight = insSize.height; boolean insRight = calcInsSide(branch, ref, key); int startY = ref.y; int y = startY - cache.getRightSumSpacing() / 2; if (insRight) { y -= insHeight / 2; } int ret = 0; for (int i = 0; i < num; i++) { if (i == numRight) { y = startY - cache.getLeftSumSpacing() / 2; if (!insRight) { y -= insHeight / 2; } right = false; } IBranchPart subbranch = subBranches.get(i); IFigure subFigure = subbranch.getFigure(); Insets refIns = RadialUtils.getRefInsets(subFigure, right); int hint = y - refIns.top + (refIns.getHeight() + insHeight) / 2; if (i < numRight) { if (insRight && childRef.y < hint) return ret; if (withDisabled || subFigure.isEnabled()) ret++; if (i == numRight - 1 && childRef.x > ref.x && childRef.y >= hint) return ret; } else { if (!insRight && childRef.y < hint) { return ret; } if (withDisabled || subFigure.isEnabled()) ret++; } y += childrenSpacings[i]; } return withDisabled ? num : -1; } private Dimension calcInsSize(IBranchPart branch, ParentSearchKey key) { return key.getFigure().getSize(); } private boolean calcInsSide(IBranchPart branch, Point branchRef, ParentSearchKey key) { Point childRef = key.getFigure().getReference(); return childRef.x > branchRef.x; // if Child on the right of Branch, return true; } protected RadiationInsertion getCurrentInsertion(IBranchPart branch) { IInsertion insertion = super.getCurrentInsertion(branch); return insertion instanceof RadiationInsertion ? (RadiationInsertion) insertion : null; } public int getQuickMoveOffset(IBranchPart branch, IBranchPart child, int direction) { if (direction == PositionConstants.SOUTH) return 1; if (direction == PositionConstants.NORTH) return -1; return super.getQuickMoveOffset(branch, child, direction); } @Override protected Point calcInsertPosition(IBranchPart branch, IBranchPart child, ParentSearchKey key) { List<IBranchPart> subBranches = branch.getSubBranches(); Point firstLoc = calcFirstChildPosition(branch, key); if (subBranches.isEmpty()) return firstLoc; int index = calcInsIndex(branch, key, true); RadialData cache = getRadialData(branch); int subSize = subBranches.size(); int right = cache.getNumRight(); int left = subSize - right; Dimension insSize = key.getFigure().getSize(); Dimension inventSize = key.getInvent().getSize(); IBranchPart first = subBranches.get(0); Rectangle fBounds = first.getFigure().getBounds(); if (index == 0) { int x = fBounds.x + inventSize.width / 2; int y = fBounds.y - (insSize.height + inventSize.height) / 2; return new Point(x, y); } if (index == subSize) { if (subSize == 1 && isWithinThreshold(first)) { if (fBounds.bottom() > 0) { int x = fBounds.x + inventSize.width / 2; int y = fBounds.bottom() + (insSize.height + inventSize.height) / 2; return new Point(x, y); } else { return new Point(firstLoc.x, -firstLoc.y); } } if (left == 0) return firstLoc.getNegated(); IBranchPart sub = subBranches.get(subBranches.size() - 1); Rectangle bounds = sub.getFigure().getBounds(); if (left == 1 && bounds.y > 0) return new Point(-firstLoc.x, firstLoc.y); int x = bounds.right() - inventSize.width / 2; int y = bounds.y - (insSize.height + inventSize.height) / 2; return new Point(x, y); } if (index == right) { boolean isRight = (left == 1 && right == 1) || isRight(subBranches, child, right); IBranchPart sub = isRight ? subBranches.get(index - 1) : subBranches.get(index); Rectangle bounds = sub.getFigure().getBounds(); int x; if (isRight) x = bounds.x + inventSize.width / 2; else x = bounds.right() - inventSize.width / 2; int y = bounds.bottom() + (insSize.height + inventSize.height) / 2; return new Point(x, y); } boolean isRight = index < right; return calcInventPosition(subBranches.get(isRight ? index - 1 : index), subBranches.get(isRight ? index : index - 1), key, isRight); } @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; } } int right = getRadialData(branch).getNumRight(); Dimension inventSize = key.getInvent().getSize(); if (index == oldIndex) { if (index == right - 1 && key.getCursorPos().x < 0 && (!subBranches.get(right).getFigure().isEnabled())) index += 1; int delta = getTopicSize(subBranches.get(index)).width / 2 - inventSize.width / 2; int deltaX = index < right ? -delta : delta; return getReference(subBranches.get(index)).getTranslated(deltaX, 0); } return calcInsertPosition(branch, child, key); } protected Point calcFirstChildPosition(IBranchPart branch, ParentSearchKey key) { int y = -(getMinorSpacing(branch) * 3 / 4 + 8) * 4; int x = getRadialData(branch).getX(y, true); return getReference(branch).getTranslated(x, y) .getTranslated(key.getInvent().getSize().width / 2, 0); } protected boolean isWithinThreshold(IBranchPart subBranch) { // 200 Threshold return getBranchWeight(subBranch) < (int) (200 * (Math.log(2) + 1)); } protected int getBranchWeight(IBranchPart branch) { // 10 minorSpacing if (branch == null) return 0; IFigure figure = branch.getFigure(); if (figure == null) return 0; return figure.getPreferredSize().height + (10 * 3 / 4 + 8) * 2; } @Override protected Point calcInventPosition(IBranchPart orientation, IBranchPart assist, ParentSearchKey key, boolean isRightOrUp) { Dimension inventSize = key.getInvent().getSize(); Rectangle oriBounds = orientation.getFigure().getBounds(); Rectangle assBounds = assist.getFigure().getBounds(); int x; if (isRightOrUp) x = (oriBounds.x + assBounds.x) / 2 + inventSize.width / 2; else x = (oriBounds.right() + assBounds.right()) / 2 - inventSize.width / 2; int y = (oriBounds.bottom() + assBounds.y) / 2; Point loc = new Point(x, y); IBranchPart parent = orientation.getParentBranch(); IFigure figure = parent.getTopicPart().getFigure(); Rectangle bounds = figure.getBounds(); if (isRightOrUp) { if (bounds.contains(x - inventSize.width / 2, y - inventSize.height / 2) || bounds.contains(x - inventSize.width / 2, y + inventSize.height / 2)) return loc.getTranslated( bounds.right() - (loc.x - inventSize.width / 2) + getMajorSpacing(parent), 0); } else { if (bounds.contains(x + inventSize.width / 2, y - inventSize.height / 2) || bounds.contains(x + inventSize.width / 2, y + inventSize.height / 2)) return loc.getTranslated((loc.x - inventSize.width / 2) - bounds.x - getMajorSpacing(parent), 0); } return loc; } protected boolean isRight(List<IBranchPart> subBranches, IBranchPart child, int right) { int rightWeight = 0; int leftWeight = 0; for (int i = 0; i < right; i++) rightWeight += getBranchWeight(subBranches.get(i)); for (int i = right; i < subBranches.size(); i++) leftWeight += getBranchWeight(subBranches.get(i)); int insWeight = getBranchWeight(child); int newRightWeight = rightWeight + insWeight; int halfWeight = (rightWeight + leftWeight) / 2; return (newRightWeight <= (newRightWeight + leftWeight) / 2) || (rightWeight <= leftWeight && (newRightWeight - halfWeight > halfWeight - rightWeight)); } }