/* ******************************************************************************
* 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.IRotatableReferencedFigure;
import org.xmind.gef.draw2d.ReferencedLayoutData;
import org.xmind.gef.draw2d.geometry.Geometry;
import org.xmind.gef.draw2d.geometry.HorizontalFlipper;
import org.xmind.gef.draw2d.geometry.ITransformer;
import org.xmind.gef.draw2d.geometry.PrecisionDimension;
import org.xmind.gef.draw2d.geometry.PrecisionLine;
import org.xmind.gef.draw2d.geometry.PrecisionPoint;
import org.xmind.gef.draw2d.geometry.PrecisionRectangle;
import org.xmind.gef.draw2d.geometry.PrecisionLine.LineType;
import org.xmind.gef.draw2d.geometry.PrecisionLine.Side;
import org.xmind.gef.part.IPart;
import org.xmind.ui.branch.AbstractBranchStructure;
import org.xmind.ui.branch.BoundaryLayoutHelper;
import org.xmind.ui.branch.IInsertion;
import org.xmind.ui.branch.Insertion;
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 SubFishboneStructure extends AbstractBranchStructure {
private ISubDirection direction;
private ITransformer t = new HorizontalFlipper();
public SubFishboneStructure(ISubDirection direction) {
this.direction = direction;
this.t.setEnabled(false);
}
private double getPadding(IBranchPart branch) {
return getCastedData(branch).getPadding();
}
public void fillLayoutData(IBranchPart branch, ReferencedLayoutData data) {
super.fillLayoutData(branch, data);
data.addMargins(new Insets((int) Math.ceil(getPadding(branch))));
}
protected void doFillPlusMinus(IBranchPart branch,
IPlusMinusPart plusMinus, LayoutInfo info) {
Point ref = info.getReference();
SubFishboneData fd = getCastedData(branch);
fd.r1.setOrigin(ref.x, ref.y);
IFigure figure = plusMinus.getFigure();
Dimension size = figure.getPreferredSize();
PrecisionRectangle topicBounds = getNormalTopicBounds(branch, ref);
int orientation = getSourceOrientation(branch);
double halfWidth = size.width * 0.5d;
double halfHeight = size.height * 0.5d;
double centerX = orientation == PositionConstants.WEST ? topicBounds.x
- halfWidth : topicBounds.right() + halfWidth;
PrecisionPoint center = fd.r1.tp(new PrecisionPoint(centerX, ref.y));
Point loc = center.translate(-halfWidth, -halfHeight).toDraw2DPoint();
info.put(figure, new Rectangle(loc, size));
}
private PrecisionRectangle getNormalTopicBounds(IBranchPart branch,
Point ref) {
ITopicPart topic = branch.getTopicPart();
if (topic != null) {
IFigure figure = topic.getFigure();
if (figure instanceof IRotatableReferencedFigure)
return ((IRotatableReferencedFigure) figure)
.getNormalPreferredBounds(ref);
}
return new PrecisionRectangle(ref.x, ref.y, 0, 0);
}
@Override
protected BoundaryLayoutHelper getBoundaryLayoutHelper(IBranchPart branch) {
return super.getBoundaryLayoutHelper(branch);
}
protected void doFillSubBranches(IBranchPart branch,
List<IBranchPart> subBranches, LayoutInfo info) {
Point ref = info.getReference();
FishboneData fd = getCastedData(branch).getFishboneData();
for (IBranchPart subBranch : subBranches) {
IFigure figure = subBranch.getFigure();
Rectangle rect = fd.getChildPrefBounds(subBranch,
new PrecisionPoint(ref));
if (rect != null)
info.put(figure, rect);
}
}
protected Object createStructureData(IBranchPart branch) {
return new SubFishboneData(branch, direction);
}
protected boolean isValidStructureData(IBranchPart branch, Object data) {
return super.isValidStructureData(branch, data)
&& (data instanceof SubFishboneData);
}
protected SubFishboneData getCastedData(IBranchPart branch) {
return (SubFishboneData) super.getStructureData(branch);
}
public int calcChildDistance(IBranchPart branch, ParentSearchKey key) {
PrecisionLine boneLine = getBoneLine(branch);
PrecisionPoint source = calcSourceLocation(branch, boneLine);
PrecisionLine sourceRay = getBoneRay(source);
List<IBranchPart> subBranches = branch.getSubBranches();
boolean folded = branch.isFolded();
// PrecisionPoint offset = calcChildOffset(branch, key.getFeedback(),
// boneLine, sourceRay);
PrecisionLine childBoneLine = getChildBoneLine(branch, key
.getFeedback());
double offset = calcChildOffset(boneLine, sourceRay, childBoneLine);
if (offset > 0) {
double range;
if (!subBranches.isEmpty() && !folded) {
int lastIndex = direction.isChildrenTraverseReversed() ? 0
: subBranches.size() - 1;
IBranchPart lastChild = subBranches.get(lastIndex);
// PrecisionPoint lastOffset = calcChildOffset(branch, lastChild,
// boneLine, sourceRay);
double lastOffset = calcChildOffset(boneLine, sourceRay,
getChildBoneLine(branch, lastChild));
range = lastOffset + MindMapUI.SEARCH_RANGE;
} else {
range = MindMapUI.SEARCH_RANGE;
}
if (offset < range) {
double distance = calcChildDistance(boneLine, childBoneLine);
if (distance > 0 && distance < MindMapUI.SEARCH_RANGE)
return Math.max(1, (int) distance);
}
}
return super.calcChildDistance(branch, key);
}
private double calcChildDistance(PrecisionLine boneLine,
PrecisionLine childLine) {
PrecisionPoint target = childLine.getOrigin();
Side childSide = boneLine.getSide(target);
if (needsCalcChildDistance(childSide))
return Geometry.getDistance(target, boneLine);
return -1;
}
private double calcChildOffset(PrecisionLine boneLine,
PrecisionLine sourceRay, PrecisionLine childLine) {
PrecisionPoint joint = boneLine.intersect(childLine);
PrecisionDimension offset = joint.getDifference(sourceRay.getOrigin());
double off = offset.getDiagonal();
if (!sourceRay.contains(joint)) {
off = -off;
}
return off;
}
private boolean needsCalcChildDistance(Side childSide) {
if (childSide == Side.Right)
return direction == ISubDirection.NER
|| direction == ISubDirection.SE
|| direction == ISubDirection.NW
|| direction == ISubDirection.SWR;
if (childSide == Side.Left)
return direction == ISubDirection.NE
|| direction == ISubDirection.SER
|| direction == ISubDirection.NWR
|| direction == ISubDirection.SW;
return false;
}
// private PrecisionPoint calcChildOffset(IBranchPart branch,
// IBranchPart child, PrecisionPoint source) {
//
// PrecisionPoint target = calcChildTargetLocation(branch, child, source);
// PrecisionDimension d = target.getDifference(source);
// SubFishboneStructureData fd = getCastedData(branch);
// double w = d.height * fd.r1.cos() / fd.r1.sin();
// double jointOffset = d.width - w;
// if (direction.isRightHeaded())
// jointOffset = -jointOffset;
// double distance = direction.isDownwards() ? d.height : -d.height;
// PrecisionPoint offset = new PrecisionPoint(jointOffset, distance);
// return direction.isRotated() ? offset.transpose() : offset;
// }
private PrecisionLine getBoneLine(IBranchPart branch) {
IAnchor anchor = ((INodePart) branch.getTopicPart())
.getSourceAnchor(branch);
int orientation = getSourceOrientation(branch);
PrecisionPoint p1 = anchor.getLocation(Geometry
.getOppositePosition(orientation), 0);
PrecisionPoint p2 = anchor.getLocation(orientation, 0);
return new PrecisionLine(p1, p2, LineType.Line);
}
private PrecisionLine getChildBoneLine(IBranchPart branch, IBranchPart child) {
IAnchor anchor = ((INodePart) child.getTopicPart())
.getTargetAnchor(branch);
int orientation = getChildTargetOrientation(branch, child);
PrecisionPoint p1 = anchor.getLocation(orientation, 0);
double angle = direction.getSubDirection().getRotateAngle();
PrecisionPoint p2 = p1.getMoved(Math.toRadians(angle), 100);
return new PrecisionLine(p1, p2, LineType.Line);
}
private PrecisionPoint calcSourceLocation(IBranchPart branch,
PrecisionLine boneLine) {
if (!branch.getSubBranches().isEmpty() && !branch.isFolded()
&& direction.isRotated()) {
PrecisionPoint ref = new PrecisionPoint(((IReferencedFigure) branch
.getTopicPart().getFigure()).getReference());
PrecisionPoint p = ref.getMoved(Math.toRadians(-direction
.getRotateAngle()), 100);
return boneLine.intersect(new PrecisionLine(ref, p, LineType.Line));
}
return ((INodePart) branch.getTopicPart()).getSourceAnchor(branch)
.getLocation(getSourceOrientation(branch), 0);
}
private PrecisionLine getBoneRay(PrecisionPoint p) {
double angle = direction.getRotateAngle();
if (direction == ISubDirection.SER || direction == ISubDirection.NWR
|| direction == ISubDirection.NW
|| direction == ISubDirection.SW) {
angle = 180 + angle;
}
return new PrecisionLine(p, p.getMoved(Math.toRadians(angle), 100),
LineType.Ray);
}
public int calcChildIndex(IBranchPart branch, ParentSearchKey key) {
return calcInsIndex(branch, key.getFeedback(), false);
}
public IInsertion calcInsertion(IBranchPart branch, ParentSearchKey key) {
return new Insertion(branch, calcInsIndex(branch, key.getFeedback(),
true), key.getFigure().getSize());
}
private int calcInsIndex(IBranchPart branch, IBranchPart child,
boolean withDisabled) {
if (branch.getSubBranches().isEmpty() || branch.isFolded())
return withDisabled ? 0 : -1;
PrecisionLine boneLine = getBoneLine(branch);
PrecisionPoint source = calcSourceLocation(branch, boneLine);
PrecisionLine sourceRay = getBoneRay(source);
double offset = calcChildOffset(boneLine, sourceRay, getChildBoneLine(
branch, child));
boolean reversed = direction.isChildrenTraverseReversed();
List<IBranchPart> subBranches = branch.getSubBranches();
int num = subBranches.size();
int ret = 0;
for (IBranchPart subBranch : subBranches) {
double subOffset = calcChildOffset(boneLine, sourceRay,
getChildBoneLine(branch, subBranch));
if (reversed) {
if (offset > subOffset)
return ret;
} else {
if (offset < subOffset)
return ret;
}
if (withDisabled || subBranch.getFigure().isEnabled()) {
ret++;
}
}
return withDisabled ? num : -1;
}
// private PrecisionPoint calcSourceLocation(IBranchPart branch) {
//// return new PrecisionPoint(((IReferencedFigure) branch.getTopicPart()
//// .getFigure()).getReference());
// return ((INodePart) branch.getTopicPart()).getSourceAnchor(branch)
// .getLocation(calcSourceOrientation(branch), 0);
// }
//
// 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(calcChildTargetOrientation(branch,
// child), 0);
// }
// }
// return new PrecisionPoint(getReference(child));
// }
// private Point getReference(IBranchPart branch) {
// ITopicPart topic = branch.getTopicPart();
// if (topic != null)
// return ((IReferencedFigure) topic.getFigure()).getReference();
// return ((IReferencedFigure) branch.getFigure()).getReference();
// }
// public Object calcNavigation(IBranchPart branch, int direction) {
// int nav = this.direction.calcNavigation(direction);
// if (nav == GEF.NAVI_PREV && branch.getBranchIndex() == 0)
// nav = GEF.NAVI_PARENT;
// return nav;
// }
public IPart calcChildNavigation(IBranchPart branch,
IBranchPart sourceChild, String navReqType, boolean sequential) {
if (direction.isRotated()) {
if (GEF.REQ_NAV_UP.equals(navReqType)) {
return getSubTopicPart(branch, sourceChild.getBranchIndex() - 1);
} else if (GEF.REQ_NAV_DOWN.equals(navReqType)) {
return getSubTopicPart(branch, sourceChild.getBranchIndex() + 1);
} else if (!sequential) {
if (direction.isRightHeaded()) {
if (GEF.REQ_NAV_RIGHT.equals(navReqType)) {
return branch.getTopicPart();
}
} else {
if (GEF.REQ_NAV_LEFT.equals(navReqType)) {
return branch.getTopicPart();
}
}
}
} else {
String prevType = direction.isRightHeaded() ? GEF.REQ_NAV_RIGHT
: GEF.REQ_NAV_LEFT;
String nextType = direction.isRightHeaded() ? GEF.REQ_NAV_LEFT
: GEF.REQ_NAV_RIGHT;
if (prevType.equals(navReqType)) {
ITopicPart prev = getSubTopicPart(branch, sourceChild
.getBranchIndex() - 1);
if (prev == null && !sequential)
return branch.getTopicPart();
return prev;
} else if (nextType.equals(navReqType)) {
return getSubTopicPart(branch, sourceChild.getBranchIndex() + 1);
}
}
return super.calcChildNavigation(branch, sourceChild, navReqType,
sequential);
}
public IPart calcNavigation(IBranchPart branch, String navReqType) {
if (isNavChild(branch, navReqType)) {
if (direction.isChildrenTraverseReversed())
return getSubTopicPart(branch,
branch.getSubBranches().size() - 1);
return getSubTopicPart(branch, 0);
}
return super.calcNavigation(branch, navReqType);
}
private boolean isNavChild(IBranchPart branch, String navReqType) {
if (direction.isRotated()) {
if (direction.isDownwards()) {
if (GEF.REQ_NAV_DOWN.equals(navReqType))
return true;
} else {
if (GEF.REQ_NAV_UP.equals(navReqType))
return true;
}
}
if (direction.isRightHeaded())
return GEF.REQ_NAV_LEFT.equals(navReqType);
return GEF.REQ_NAV_RIGHT.equals(navReqType);
}
public int getChildTargetOrientation(IBranchPart branch,
IBranchPart subBranch) {
if (!branch.getSubBranches().contains(subBranch)) {
return direction.isRightHeaded() ? PositionConstants.EAST
: PositionConstants.WEST;
}
return direction.getChildTargetOrientation();
}
public int getSourceOrientation(IBranchPart branch) {
return direction.getSourceOrientation();
}
public int getRangeGrowthDirection(IBranchPart branch,
IBranchRangePart range) {
return getRangeGrowthDirection();
}
private int getRangeGrowthDirection() {
if (direction.isRotated())
return PositionConstants.SOUTH;
return direction.isRightHeaded() ? PositionConstants.EAST
: PositionConstants.WEST;
}
public int getSummaryDirection(IBranchPart branch, ISummaryPart summary) {
if (direction.isRotated())
return direction.isRightHeaded() ? PositionConstants.EAST
: PositionConstants.EAST;
return direction.isDownwards() ? PositionConstants.SOUTH
: PositionConstants.NORTH;
}
public int getQuickMoveOffset(IBranchPart branch, IBranchPart child,
int direction) {
int rangeGrowthDirection = getRangeGrowthDirection();
if (direction == rangeGrowthDirection)
return 1;
if (direction == Geometry.getOppositePosition(rangeGrowthDirection))
return -1;
return super.getQuickMoveOffset(branch, child, direction);
}
}