/* ****************************************************************************** * 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.branch; import static org.xmind.ui.style.StyleUtils.getInteger; import java.util.ArrayList; 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.IDecoratedFigure; import org.xmind.gef.draw2d.IReferencedFigure; import org.xmind.gef.draw2d.IRotatableFigure; import org.xmind.gef.draw2d.ReferencedLayoutData; import org.xmind.gef.draw2d.decoration.IDecoration; import org.xmind.gef.draw2d.geometry.Geometry; import org.xmind.gef.draw2d.geometry.PrecisionDimension; import org.xmind.gef.draw2d.geometry.PrecisionRectangle; import org.xmind.gef.draw2d.geometry.PrecisionRotator; import org.xmind.gef.graphicalpolicy.IStyleSelector; import org.xmind.gef.part.IGraphicalPart; import org.xmind.gef.part.IPart; import org.xmind.ui.branch.BoundaryLayoutHelper.BoundaryData; import org.xmind.ui.decorations.ISummaryDecoration; import org.xmind.ui.internal.figures.BranchFigure; import org.xmind.ui.mindmap.IBoundaryPart; import org.xmind.ui.mindmap.IBranchPart; import org.xmind.ui.mindmap.IBranchRangePart; import org.xmind.ui.mindmap.ILabelPart; import org.xmind.ui.mindmap.IPlusMinusPart; import org.xmind.ui.mindmap.ISummaryPart; import org.xmind.ui.mindmap.ITopicPart; import org.xmind.ui.style.StyleUtils; import org.xmind.ui.style.Styles; import org.xmind.ui.tools.ParentSearchKey; import org.xmind.ui.util.MindMapUtils; public abstract class AbstractBranchStructure implements IBranchStructure, IBranchStructureExtension, INavigableBranchStructureExtension, IInsertableBranchStructureExtension { protected static final String CACHE_STRUCTURE_DATA = "org.xmind.ui.branchCache.structureData"; //$NON-NLS-1$ protected static final String CACHE_BOUNDARY_LAYOUT_HELPER = "org.xmind.ui.branchCache.boundaryLayoutHelper"; //$NON-NLS-1$ protected static class LayoutInfo extends ReferencedLayoutData { private ReferencedLayoutData delegate; private boolean folded; private boolean minimized; private Rectangle minArea; public LayoutInfo(ReferencedLayoutData delegate, boolean folded, boolean minimized) { this.delegate = delegate; this.folded = folded; this.minimized = minimized; this.minArea = null; } public boolean isFolded() { return folded; } public boolean isMinimized() { return minimized; } public Rectangle getMinArea() { return minArea; } public void setMinArea(Rectangle minArea) { this.minArea = minArea; } public void putMinArea(IFigure figure) { if (minArea == null) { delegate.put(figure, delegate.createInitBounds()); } else { delegate.put(figure, minArea.getCopy()); } } public void add(Rectangle blankArea) { delegate.add(blankArea); } public void addMargins(Insets margin) { delegate.addMargins(margin); } public void addMargins(int top, int left, int bottom, int right) { delegate.addMargins(top, left, bottom, right); } public Rectangle createInitBounds() { return delegate.createInitBounds(); } public Rectangle createInitBounds(Point ref) { return delegate.createInitBounds(ref); } public Rectangle get(Object figure) { return delegate.get(figure); } public Rectangle getCheckedClientArea() { return delegate.getCheckedClientArea(); } public Rectangle getClientArea() { return delegate.getClientArea(); } public Point getReference() { return delegate.getReference(); } public void put(IFigure figure, Rectangle preferredBounds) { delegate.put(figure, preferredBounds); } public void translate(int dx, int dy) { delegate.translate(dx, dy); } } public void fillLayoutData(IBranchPart branch, ReferencedLayoutData data) { BranchFigure figure = (BranchFigure) branch.getFigure(); boolean folded = figure.isFolded(); boolean minimized = figure.isMinimized(); LayoutInfo info = new LayoutInfo(data, folded, minimized); fillLayoutInfo(branch, info); } protected void fillLayoutInfo(IBranchPart branch, LayoutInfo info) { fillTopic(branch, info); fillPlusMinus(branch, info); fillLabel(branch, info); List<IBranchPart> subBranches = branch.getSubBranches(); fillSubBranches(branch, subBranches, info); List<IBoundaryPart> boundaries = branch.getBoundaries(); fillBoundaries(branch, boundaries, subBranches, info); List<ISummaryPart> summaries = branch.getSummaries(); List<IBranchPart> summaryBranches = new ArrayList<IBranchPart>( branch.getSummaryBranches()); fillSummaries(branch, summaries, summaryBranches, subBranches, info); fillUnhandledSummaryBranches(branch, summaryBranches, info); addExtraSpaces(branch, info); fillOverallBoundary(branch, boundaries, info); } protected void fillTopic(IBranchPart branch, LayoutInfo info) { ITopicPart topic = branch.getTopicPart(); if (topic != null) { if (info.isMinimized()) { info.putMinArea(topic.getFigure()); } else { doFillTopic(branch, topic, info); } } } protected void doFillTopic(IBranchPart branch, ITopicPart topicPart, LayoutInfo info) { IFigure fig = topicPart.getFigure(); if (fig instanceof IReferencedFigure) { IReferencedFigure refFig = (IReferencedFigure) fig; info.put(refFig, refFig.getPreferredBounds(info.getReference())); } else { Dimension size = fig.getPreferredSize(); Point ref = info.getReference(); Rectangle r = new Rectangle(ref.x - size.width / 2, ref.y - size.height / 2, size.width, size.height); info.put(fig, r); } } protected void fillPlusMinus(IBranchPart branch, LayoutInfo info) { IPlusMinusPart plusMinus = branch.getPlusMinus(); if (plusMinus != null) { IFigure pmFigure = plusMinus.getFigure(); if (info.isMinimized()) { info.putMinArea(pmFigure); } else { doFillPlusMinus(branch, plusMinus, info); if (info.get(pmFigure) == null) { info.putMinArea(pmFigure); } } } } protected abstract void doFillPlusMinus(IBranchPart branch, IPlusMinusPart plusMinus, LayoutInfo info); protected void fillLabel(IBranchPart branch, LayoutInfo info) { ILabelPart label = branch.getLabel(); if (label != null) { if (info.isMinimized() || !label.getFigure().isVisible()) { info.putMinArea(label.getFigure()); } else { doFillLabel(branch, label, info); } } } protected void doFillLabel(IBranchPart branch, ILabelPart label, LayoutInfo info) { IFigure figure = label.getFigure(); ITopicPart topicPart = branch.getTopicPart(); Rectangle area; if (topicPart != null) { area = info.get(topicPart.getFigure()); } else { area = info.createInitBounds(); } if (figure instanceof IRotatableFigure) { IRotatableFigure f = (IRotatableFigure) figure; double angle = f.getRotationDegrees(); if (!Geometry.isSameAngleDegree(angle, 0, 0.00001)) { Point ref = info.getReference(); PrecisionRotator r = new PrecisionRotator(); r.setOrigin(ref.x, ref.y); r.setAngle(angle); PrecisionRectangle rect = r.r(new PrecisionRectangle(area)); PrecisionDimension size = f.getNormalPreferredSize(-1, -1); rect.x += (rect.width - size.width) / 2; rect.y = rect.bottom() - 2; rect.width = size.width; rect.height = size.height; r.t(rect); info.put(figure, rect.toDraw2DRectangle()); return; } } Dimension size = figure.getPreferredSize(); Rectangle r = new Rectangle(area.x + (area.width - size.width) / 2, area.bottom() - 2, size.width, size.height); info.put(figure, r); } protected void fillSubBranches(IBranchPart branch, List<IBranchPart> subBranches, LayoutInfo info) { if (subBranches.isEmpty()) return; if ((info.isFolded() || info.isMinimized()) && minimizesSubBranchesToOnePoint()) { if (info.isFolded() && !info.isMinimized()) { info.setMinArea(info.createInitBounds(calcSubBranchesMinPoint( branch, subBranches, info))); } for (IBranchPart subBranch : subBranches) { info.putMinArea(subBranch.getFigure()); } } else { doFillSubBranches(branch, subBranches, info); for (IBranchPart subBranch : subBranches) { IFigure subBranchFigure = subBranch.getFigure(); if (info.get(subBranchFigure) == null) { info.putMinArea(subBranchFigure); } } } } protected abstract void doFillSubBranches(IBranchPart branch, List<IBranchPart> subBranches, LayoutInfo info); protected boolean minimizesSubBranchesToOnePoint() { return true; } protected Point calcSubBranchesMinPoint(IBranchPart branch, List<IBranchPart> subBranches, LayoutInfo info) { IPlusMinusPart plusMinus = branch.getPlusMinus(); if (plusMinus != null) { Rectangle pmBounds = info.get(plusMinus.getFigure()); if (pmBounds != null) { return pmBounds.getCenter(); } } return info.getReference(); } protected void fillBoundaries(IBranchPart branch, List<IBoundaryPart> boundaries, List<IBranchPart> subBranches, LayoutInfo info) { if (boundaries.isEmpty()) return; if (subBranches.isEmpty() || ((info.isFolded() || info.isMinimized()) && minimizesSubBranchesToOnePoint())) { for (IBoundaryPart b : boundaries) { info.putMinArea(b.getFigure()); } } else { doFillBoundaries(branch, boundaries, info); } } protected void doFillBoundaries(IBranchPart branch, List<IBoundaryPart> boundaries, LayoutInfo info) { for (IBoundaryPart boundary : boundaries) { doFillBoundary(branch, boundary, info); } } protected void doFillBoundary(IBranchPart branch, IBoundaryPart boundary, LayoutInfo info) { BoundaryLayoutHelper helper = getBoundaryLayoutHelper(branch); BoundaryData d = helper.getBoundaryData(boundary); if (d.isOverall()) { if (d != helper.getOverallBoundary()) { info.putMinArea(boundary.getFigure()); } return; } Rectangle area = null; for (IBranchPart subBranch : d.getSubBranches()) { Insets ins = helper.getInnerInsets( helper.getSubBranchData(subBranch), d); Rectangle r2 = info.get(subBranch.getFigure()); area = Geometry.union(area, r2.getExpanded(ins)); } if (area != null) { area.expand(boundary.getFigure().getInsets()); } if (area == null) { info.putMinArea(boundary.getFigure()); } else { info.put(boundary.getFigure(), area); } } protected void fillSummaries(IBranchPart branch, List<ISummaryPart> summaries, List<IBranchPart> summaryBranches, List<IBranchPart> subBranches, LayoutInfo info) { if (!summaries.isEmpty()) { if (subBranches.isEmpty() || ((info.isFolded() || info.isMinimized()) && minimizesSubBranchesToOnePoint())) { for (ISummaryPart s : summaries) { info.putMinArea(s.getFigure()); } } else { doFillSummaries(branch, summaries, summaryBranches, info); } } } private void doFillSummaries(IBranchPart branch, List<ISummaryPart> summaries, List<IBranchPart> summaryBranches, LayoutInfo info) { for (ISummaryPart summary : summaries) { doFillSummary(branch, summary, summaryBranches, info); } } private void doFillSummary(IBranchPart branch, ISummaryPart summary, List<IBranchPart> summaryBranches, LayoutInfo info) { int direction = getSummaryDirection(branch, summary); Rectangle area = getSummaryArea(branch, summary, direction, info); if (area != null) { info.put(summary.getFigure(), area); } else { info.putMinArea(summary.getFigure()); } IBranchPart conclusionBranch = getConclusionBranch(branch, summary, summaryBranches); if (conclusionBranch != null) { if (area == null) { info.putMinArea(conclusionBranch.getFigure()); } else { Insets ins = getConclusionReferenceDescription(branch, summary, conclusionBranch); int x, y; switch (direction) { case PositionConstants.NORTH: x = area.x + area.width / 2; y = area.y - ins.bottom; break; case PositionConstants.SOUTH: x = area.x + area.width / 2; y = area.bottom() + ins.top; break; case PositionConstants.WEST: x = area.x - ins.right; y = area.y + area.height / 2; break; default: x = area.right() + ins.left; y = area.y + area.height / 2; } info.put(conclusionBranch.getFigure(), Geometry.getExpanded(x, y, ins)); } } } private IBranchPart getConclusionBranch(IBranchPart branch, ISummaryPart summary, List<IBranchPart> summaryBranches) { IGraphicalPart part = summary.getNode(); if (part instanceof ITopicPart) { IBranchPart conclusionBranch = ((ITopicPart) part).getOwnerBranch(); if (conclusionBranch != null && summaryBranches.contains(conclusionBranch)) { summaryBranches.remove(conclusionBranch); return conclusionBranch; } } return null; } private Insets getConclusionReferenceDescription(IBranchPart branch, ISummaryPart summary, IGraphicalPart conclusion) { IFigure fig = conclusion.getFigure(); if (fig instanceof IReferencedFigure) return ((IReferencedFigure) fig).getReferenceDescription(); Dimension size = fig.getPreferredSize(); int w = size.width / 2; int h = size.height / 2; return new Insets(h, w, size.height - h, size.width - w); } protected Rectangle getSummaryArea(IBranchPart branch, ISummaryPart summary, int direction, ReferencedLayoutData data) { Rectangle r = null; for (IBranchPart subBranch : summary.getEnclosingBranches()) { r = Geometry.union(r, data.get(subBranch.getFigure())); } if (r == null) return null; Rectangle area = data.createInitBounds(); int width = getPreferredSummaryWidth(summary); switch (direction) { case PositionConstants.NORTH: area.x = r.x; area.width = r.width; area.y = r.y - width; area.height = width; break; case PositionConstants.SOUTH: area.x = r.x; area.width = r.width; area.y = r.bottom(); area.height = width; break; case PositionConstants.WEST: area.x = r.x - width; area.width = width; area.y = r.y; area.height = r.height; break; default: area.x = r.right(); area.width = width; area.y = r.y; area.height = r.height; } IStyleSelector ss = StyleUtils.getStyleSelector(summary); String shape = StyleUtils.getString(summary, ss, Styles.ShapeClass, null); int lineWidth = StyleUtils.getInteger(summary, ss, Styles.LineWidth, shape, 1); return area.expand(lineWidth, lineWidth); } private int getPreferredSummaryWidth(ISummaryPart summary) { IFigure figure = summary.getFigure(); if (figure instanceof IDecoratedFigure) { IDecoration decoration = ((IDecoratedFigure) figure) .getDecoration(); if (decoration instanceof ISummaryDecoration) { return ((ISummaryDecoration) decoration) .getPreferredWidth(figure); } } return Styles.DEFAULT_SUMMARY_WIDTH + Styles.DEFAULT_SUMMARY_SPACING * 2; } protected void fillUnhandledSummaryBranches(IBranchPart branch, List<IBranchPart> summaryBranches, LayoutInfo info) { if (!summaryBranches.isEmpty()) { for (IBranchPart summaryBranch : summaryBranches) { info.putMinArea(summaryBranch.getFigure()); } } } protected void addExtraSpaces(IBranchPart branch, ReferencedLayoutData data) { // may be subclassed } public int getSummaryDirection(IBranchPart branch, ISummaryPart summary) { return PositionConstants.EAST; } protected void fillOverallBoundary(IBranchPart branch, List<IBoundaryPart> boundaries, LayoutInfo info) { if (boundaries.isEmpty()) return; BoundaryLayoutHelper helper = getBoundaryLayoutHelper(branch); BoundaryData overallBoundary = helper.getOverallBoundary(); if (overallBoundary == null) return; if (info.isMinimized()) { info.putMinArea(overallBoundary.boundaryFigure); } else { Rectangle area = info.getCheckedClientArea(); area = overallBoundary.expanded(area.getCopy()); info.put(overallBoundary.boundaryFigure, area); } } public int getRangeGrowthDirection(IBranchPart branch, IBranchRangePart range) { return PositionConstants.SOUTH; } public void invalidate(IGraphicalPart part) { if (part instanceof IBranchPart) { invalidateBranch((IBranchPart) part); } } protected void invalidateBranch(IBranchPart branch) { MindMapUtils.flushCache(branch, CACHE_STRUCTURE_DATA); MindMapUtils.flushCache(branch, CACHE_BOUNDARY_LAYOUT_HELPER); ITopicPart topic = branch.getTopicPart(); if (topic != null) { IFigure topicFigure = topic.getFigure(); if (topicFigure != null) { topicFigure.invalidate(); } } } protected BoundaryLayoutHelper getBoundaryLayoutHelper(IBranchPart branch) { BoundaryLayoutHelper helper = (BoundaryLayoutHelper) MindMapUtils .getCache(branch, CACHE_BOUNDARY_LAYOUT_HELPER); if (helper == null) { helper = new BoundaryLayoutHelper(branch, this); MindMapUtils.setCache(branch, CACHE_BOUNDARY_LAYOUT_HELPER, helper); } return helper; } protected Dimension getBorderedSize(IBranchPart branch, IBranchPart subBranch) { return getBoundaryLayoutHelper(branch).getBorderedSize(subBranch); } protected int getMinorSpacing(IBranchPart branch) { return getInteger(branch, branch.getBranchPolicy().getStyleSelector(branch), Styles.MinorSpacing, 5); } protected int getMajorSpacing(IBranchPart branch) { return StyleUtils.getMajorSpacing(branch, 5); } protected Object getStructureData(IBranchPart branch) { Object data = MindMapUtils.getCache(branch, CACHE_STRUCTURE_DATA); if (!isValidStructureData(branch, data)) { data = createStructureData(branch); if (data != null) { MindMapUtils.setCache(branch, CACHE_STRUCTURE_DATA, data); } } return data; } protected Object createStructureData(IBranchPart branch) { return null; } protected boolean isValidStructureData(IBranchPart branch, Object data) { return data != null; } // public void calculateSubBranchInsets(BoundaryData boundary, // SubBranchData subBranch, Insets insets) { // } /* * Subclass may extend this method. * * @seeorg.xmind.ui.mindmap.graphicalpolicies.IBranchStructure# * calcSourceOrientation(org.xmind.ui.parts.IBranchPart) */ public int getSourceOrientation(IBranchPart branch) { return PositionConstants.NONE; } /* * Subclass may extend this method. * * @seeorg.xmind.ui.mindmap.graphicalpolicies.IBranchStructure# * calcChildTargetOrientation(org.xmind.ui.parts.IBranchPart, * org.xmind.ui.parts.IBranchPart) */ public int getChildTargetOrientation(IBranchPart branch, IBranchPart subBranch) { return PositionConstants.NONE; } // /* // * Subclass may extend this method. // * // * @see org.xmind.ui.graphicalpolicies.IBranchStructure#calcChildTargetOrientation(org.xmind.ui.parts.IBranchPart, // * org.xmind.gef.draw2d.IReferencedFigure) // */ // public int calcChildTargetOrientation(IBranchPart branch, // IReferencedFigure childFigure) { // return PositionConstants.NONE; // } /* * Subclass may extend this method. * * @seeorg.xmind.ui.mindmap.graphicalpolicies.IBranchStructure# * calcChildIndex(org.xmind.ui.tools.ParentSearchKey) */ public int calcChildIndex(IBranchPart branch, ParentSearchKey key) { return -1; } /* * Subclass may extend this method. * * @seeorg.xmind.ui.mindmap.graphicalpolicies.IBranchStructure# * calcChildDistance(org.xmind.ui.parts.IBranchPart, * org.xmind.ui.tools.ParentSearchKey) */ public int calcChildDistance(IBranchPart branch, ParentSearchKey key) { return -1; } public IInsertion calcInsertion(IBranchPart branch, ParentSearchKey key) { return null; } public IPart calcNavigation(IBranchPart branch, String navReqType) { return null; } public IPart calcChildNavigation(IBranchPart branch, IBranchPart sourceChild, String navReqType, boolean sequential) { if (GEF.REQ_NAV_BEGINNING.equals(navReqType)) { return getSubTopicPart(branch, 0); } else if (GEF.REQ_NAV_END.equals(navReqType)) { return getSubTopicPart(branch, branch.getSubBranches().size() - 1); } return null; } public void calcSequentialNavigation(IBranchPart branch, IBranchPart startChild, IBranchPart endChild, List<IBranchPart> results) { addSubBranches(branch, startChild.getBranchIndex(), endChild.getBranchIndex(), results); } public void calcTraversableBranches(IBranchPart branch, IBranchPart sourceChild, List<IBranchPart> results) { addSubBranch(branch, sourceChild.getBranchIndex() + 1, results); results.add(branch); addSubBranch(branch, sourceChild.getBranchIndex() - 1, results); } public void calcTraversableChildren(IBranchPart branch, List<IBranchPart> results) { addSubBranches(branch, 0, branch.getSubBranches().size() - 1, results); } protected void addSubBranches(IBranchPart branch, IBranchPart fromChild, IBranchPart toChild, List<IBranchPart> results) { addSubBranches(branch, branch.getSubBranches().indexOf(fromChild), branch.getSubBranches().indexOf(toChild), results); } protected void addSubBranches(IBranchPart branch, int fromIndex, int toIndex, List<IBranchPart> results) { boolean decreasing = toIndex < fromIndex; for (int i = fromIndex; decreasing ? i >= toIndex : i <= toIndex;) { addSubBranch(branch, i, results); if (decreasing) { i--; } else { i++; } } } protected void addSubBranch(IBranchPart branch, int index, List<IBranchPart> results) { if (index < 0 || index >= branch.getSubBranches().size()) return; results.add(branch.getSubBranches().get(index)); } protected IBranchPart getSubBranch(IBranchPart branch, int index) { if (index >= 0 && index < branch.getSubBranches().size()) { return branch.getSubBranches().get(index); } return null; } protected ITopicPart getSubTopicPart(IBranchPart branch, int index) { IBranchPart subBranch = getSubBranch(branch, index); if (subBranch != null) return subBranch.getTopicPart(); return null; } protected IInsertion getCurrentInsertion(IBranchPart branch) { return (IInsertion) MindMapUtils.getCache(branch, IInsertion.CACHE_INSERTION); } public int getQuickMoveOffset(IBranchPart branch, IBranchPart child, int direction) { return 0; } }