/* ****************************************************************************** * 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.editpolicies; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.geometry.Point; import org.xmind.core.IBoundary; import org.xmind.core.IRelationship; import org.xmind.core.ISummary; import org.xmind.core.ITopic; import org.xmind.core.ITopicRange; import org.xmind.gef.IViewer; import org.xmind.gef.command.ICommandStack; import org.xmind.gef.draw2d.IReferencedFigure; import org.xmind.gef.part.IGraphicalPart; import org.xmind.gef.part.IPart; import org.xmind.ui.commands.AddBoundaryCommand; import org.xmind.ui.commands.AddSummaryCommand; import org.xmind.ui.commands.AddTopicCommand; import org.xmind.ui.commands.CloneTopicCommand; import org.xmind.ui.commands.CommandBuilder; import org.xmind.ui.commands.DeleteBoundaryCommand; import org.xmind.ui.commands.DeleteSummaryCommand; import org.xmind.ui.commands.DeleteTopicCommand; import org.xmind.ui.commands.ModifyBoundaryMasterCommand; import org.xmind.ui.commands.ModifyPositionCommand; import org.xmind.ui.commands.ModifyRangeCommand; import org.xmind.ui.util.MindMapUtils; public class TopicMoveCommandBuilder extends DeleteCommandBuilder { private static class TopicInfo { public ITopic oldParent; public int oldIndex; public String oldType; public Point newPosition; public boolean needsReorganize; } private static final List<ITopic> EMPTY_TOPICS = Collections.emptyList(); private ITopic targetParent; private int targetIndex; private String targetType; private Point targetPosition; private boolean relative; private int insertIndex; private Set<ITopic> cachedParent = null; private Map<ITopicRange, List<ITopic>> oldRangedTopics = null; private Set<ITopicRange> rangesToMove = null; public TopicMoveCommandBuilder(IViewer viewer, CommandBuilder delegate, ITopic targetParent, int targetIndex, String targetType, Point targetPosition, boolean relative) { super(viewer, delegate); init(targetParent, targetIndex, targetType, targetPosition, relative); } public TopicMoveCommandBuilder(IViewer viewer, ICommandStack commandStack, ITopic targetParent, int targetIndex, String targetType, Point targetPosition, boolean relative) { super(viewer, commandStack); init(targetParent, targetIndex, targetType, targetPosition, relative); } private void init(ITopic targetParent, int targetIndex, String targetType, Point targetPosition, boolean relative) { this.targetParent = targetParent; this.targetIndex = targetIndex; this.targetType = targetType; this.targetPosition = targetPosition; this.relative = relative; this.insertIndex = targetIndex; if (this.insertIndex < 0) { this.insertIndex = targetParent.getChildren(targetType).size(); } } public int getTargetIndex() { return targetIndex; } public ITopic getTargetParent() { return targetParent; } public Point getTargetPosition() { return targetPosition; } public String getTargetType() { return targetType; } public boolean isRelative() { return relative; } public int getInsertIndex() { return insertIndex; } // public void moveTopic(ITopic topic) { // moveTopic(topic, insertIndex); // insertIndex++; // } // // public void copyTopic(ITopic topic) { // copyTopic(topic, insertIndex); // insertIndex++; // } public void copyTopics(List<ITopic> topics) { for (ITopic topic : topics) { copyTopic(topic, insertIndex); insertIndex++; } } public void moveTopics(List<ITopic> topics) { Map<ITopic, TopicInfo> oldInfo = new HashMap<ITopic, TopicInfo>( topics.size()); for (ITopic topic : topics) { TopicInfo info = deleteTopic(topic, insertIndex); if (!oldInfo.containsKey(topic)) { oldInfo.put(topic, info); } } for (ITopic topic : topics) { moveTopic(topic, insertIndex, oldInfo.get(topic)); insertIndex++; } } private TopicInfo deleteTopic(ITopic topic, int toIndex) { TopicInfo info = new TopicInfo(); info.oldParent = topic.getParent(); info.oldIndex = topic.getIndex(); info.oldType = topic.getType(); info.newPosition = calculateTargetPosition(topic); info.needsReorganize = needsReorganize(topic, toIndex, info.oldParent, info.oldIndex, info.oldType); if (info.needsReorganize) { if (!isCached(info.oldParent)) { for (ITopicRange range : getSubRanges(info.oldParent)) { cacheOldRangedTopics(range, info.oldParent); } } deleteTopic(topic, true); } return info; } private void moveTopic(ITopic topic, int toIndex, TopicInfo info) { // ITopic oldParent = topic.getParent(); // int oldIndex = topic.getIndex(); // String oldType = topic.getType(); // Point toPosition = calculateTargetPosition(topic); // boolean needsReorganize = needsReorganize(topic, toIndex, oldParent, // oldIndex, oldType); // if (needsReorganize) { // if (!isCached(oldParent)) { // for (ITopicRange range : getSubRanges(oldParent)) { // cacheOldRangedTopics(range, oldParent); // } // } // deleteTopic(topic, true); // } add(new ModifyPositionCommand(topic, MindMapUtils.toModelPosition(info.newPosition)), !info.needsReorganize); if (info.needsReorganize) { addTopic(topic, getTargetParent(), toIndex, getTargetType(), true); if (rangesToMove != null && !rangesToMove.isEmpty()) { while (!rangesToMove.isEmpty()) { ITopicRange range = rangesToMove.iterator().next(); moveRange(range, range.getParent()); rangesToMove.remove(range); } } } } private void addTopic(ITopic topic, ITopic toParent, int toIndex, String toType, boolean topicCollectable) { add(new AddTopicCommand(topic, toParent, toIndex, toType), topicCollectable); if (ITopic.ATTACHED.equals(toType)) { int topicIndex = topic.getIndex(); enlargeOldBoundaries(topic, toParent, topicIndex); moveMasterBoundaries(topic, topicIndex); } } private void enlargeOldBoundaries(ITopic topic, ITopic parent, int index) { Set<ITopicRange> ranges = getSubRanges(parent); for (ITopicRange range : ranges) { int startIndex = range.getStartIndex(); int endIndex = range.getEndIndex(); if (index <= endIndex) { add(new ModifyRangeCommand(range, endIndex + 1, false), false); } if (index <= startIndex) { add(new ModifyRangeCommand(range, startIndex + 1, true), false); } } } private void moveMasterBoundaries(ITopic topic, int topicIndex) { IBoundary masterBoundary = null; for (Object o : getSubRanges(topic).toArray()) { ITopicRange range = (ITopicRange) o; if (range instanceof IBoundary) { IBoundary boundary = (IBoundary) range; if (boundary.isMasterBoundary() && masterBoundary == null) { masterBoundary = boundary; addDeleteRangeCommand(boundary, topic); } } } if (masterBoundary != null) { IBoundary existingSingleBoundary = findSingleBoundary( getTargetParent(), topicIndex); if (existingSingleBoundary == null) { add(new ModifyBoundaryMasterCommand(masterBoundary, false), false); add(new ModifyRangeCommand(masterBoundary, topicIndex, true), false); add(new ModifyRangeCommand(masterBoundary, topicIndex, false), false); addAddRangeCommand(masterBoundary, getTargetParent()); } } } private IBoundary findSingleBoundary(ITopic parent, int childIndex) { for (ITopicRange range : getSubRanges(parent)) { if (range instanceof IBoundary && range.getStartIndex() == childIndex && range.getEndIndex() == childIndex) return (IBoundary) range; } return null; } private IBoundary findMasterBoundary(ITopic parent) { for (ITopicRange range : getSubRanges(parent)) { if (range instanceof IBoundary && ((IBoundary) range).isMasterBoundary()) return (IBoundary) range; } return null; } private void moveRange(ITopicRange range, ITopic oldParent) { ITopic summaryTopic = null; if (range instanceof ISummary) { summaryTopic = ((ISummary) range).getTopic(); if (summaryTopic != null) { add(new DeleteTopicCommand(summaryTopic), false); } } addDeleteRangeCommand(range, oldParent); List<ITopic> rangedTopics = getOldRangedTopics(range); if (!rangedTopics.isEmpty()) { if (ITopic.ATTACHED.equals(getTargetType())) { int startIndex = rangedTopics.get(0).getIndex(); int endIndex = rangedTopics.get(rangedTopics.size() - 1) .getIndex(); add(new ModifyRangeCommand(range, startIndex, true), false); add(new ModifyRangeCommand(range, endIndex, false), false); addAddRangeCommand(range, getTargetParent()); if (summaryTopic != null) { addTopic(summaryTopic, getTargetParent(), -1, ITopic.SUMMARY, false); } } else if (range instanceof IBoundary && rangedTopics.size() == 1) { ITopic topic = rangedTopics.get(0); IBoundary overallBoundary = findMasterBoundary(topic); if (overallBoundary == null) { add(new ModifyRangeCommand(range, -1, true), false); add(new ModifyRangeCommand(range, -1, false), false); add(new ModifyBoundaryMasterCommand((IBoundary) range, true), false); addAddRangeCommand(range, topic); } } } } private void addDeleteRangeCommand(ITopicRange range, ITopic oldParent) { if (range instanceof IBoundary) { add(new DeleteBoundaryCommand((IBoundary) range), false); } else if (range instanceof ISummary) { add(new DeleteSummaryCommand((ISummary) range), false); } removeSubRange(range, oldParent); } private void addAddRangeCommand(ITopicRange range, ITopic newParent) { if (range instanceof IBoundary) { add(new AddBoundaryCommand((IBoundary) range, newParent), false); } else if (range instanceof ISummary) { add(new AddSummaryCommand((ISummary) range, newParent), false); } addSubRange(range, newParent); } protected void deleteBoundary(IBoundary boundary, boolean sourceCollectable) { if (rangesToMove == null) rangesToMove = new HashSet<ITopicRange>(); rangesToMove.add(boundary); } protected void deleteSummary(ISummary summary, boolean sourceCollectable) { if (rangesToMove == null) rangesToMove = new HashSet<ITopicRange>(); rangesToMove.add(summary); } protected void deleteRelationship(IRelationship relationship, boolean sourceCollectable) { } private Point calculateTargetPosition(ITopic topic) { Point position = getTargetPosition(); if (position == null) return null; IPart parentPart = getViewer().findPart(getTargetParent()); if (parentPart == null) return null; Point parentPosition = getPosition((IGraphicalPart) parentPart); if (parentPosition == null) return null; if (isRelative()) { IPart topicPart = getViewer().findPart(topic); if (topicPart == null) return null; Point oldPosition = getPosition((IGraphicalPart) topicPart); return toNewPosition(parentPosition, oldPosition, position); } return toNewPosition(parentPosition, position); } protected static Point getPosition(IGraphicalPart part) { IFigure figure = part.getFigure(); if (figure instanceof IReferencedFigure) { return ((IReferencedFigure) figure).getReference(); } return figure.getBounds().getLocation(); } private static Point toNewPosition(Point parentPosition, Point oldPosition, Point position) { int x = oldPosition.x + position.x - parentPosition.x; int y = oldPosition.y + position.y - parentPosition.y; return new Point(x, y); } private static Point toNewPosition(Point parentPosition, Point position) { int x = position.x - parentPosition.x; int y = position.y - parentPosition.y; return new Point(x, y); } private boolean needsReorganize(ITopic topic, int toIndex, ITopic oldParent, int oldIndex, String oldType) { if (!getTargetParent().equals(oldParent)) return true; if (oldType != getTargetType() && (oldType == null || !oldType.equals(getTargetType()))) return true; if (ITopic.ATTACHED.equals(oldType) && oldIndex != getValidAttachedIndex(oldParent, toIndex)) return true; return false; } private int getValidAttachedIndex(ITopic parent, int index) { int size = parent.getChildren(ITopic.ATTACHED).size(); return index >= size ? index - 1 : index; } protected boolean isCached(ITopic parent) { return cachedParent != null && cachedParent.contains(parent); } protected void cacheOldRangedTopics(ITopicRange range, ITopic parent) { if (cachedParent == null) { cachedParent = new HashSet<ITopic>(); } cachedParent.add(parent); if (oldRangedTopics == null) oldRangedTopics = new HashMap<ITopicRange, List<ITopic>>(); List<ITopic> list = oldRangedTopics.get(range); if (list == null) { list = new ArrayList<ITopic>(range.getEnclosingTopics()); oldRangedTopics.put(range, list); } } protected List<ITopic> getOldRangedTopics(ITopicRange range) { if (oldRangedTopics != null) { List<ITopic> list = oldRangedTopics.get(range); if (list != null) return list; } return EMPTY_TOPICS; } private void copyTopic(ITopic topic, int toIndex) { Point position = calculateTargetPosition(topic); CloneTopicCommand clone = new CloneTopicCommand( getTargetParent().getOwnedWorkbook(), topic); add(clone, true); ITopic clonedTopic = (ITopic) clone.getSource(); add(new ModifyPositionCommand(clonedTopic, MindMapUtils.toModelPosition(position)), false); addTopic(clonedTopic, getTargetParent(), toIndex, getTargetType(), true); } }