/* ****************************************************************************** * 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.Comparator; import java.util.List; import java.util.Set; import org.eclipse.draw2d.PositionConstants; import org.eclipse.draw2d.geometry.Point; import org.eclipse.draw2d.geometry.Rectangle; import org.xmind.core.Core; import org.xmind.core.ISummary; import org.xmind.core.ITopic; import org.xmind.core.ITopicExtension; import org.xmind.core.ITopicExtensionElement; import org.xmind.core.ITopicRange; import org.xmind.gef.GEF; import org.xmind.gef.ISourceProvider; import org.xmind.gef.IViewer; import org.xmind.gef.Request; import org.xmind.gef.command.Command; import org.xmind.gef.command.CompoundCommand; import org.xmind.gef.draw2d.IReferencedFigure; import org.xmind.gef.draw2d.geometry.IIntersectionSolver; import org.xmind.gef.graphicalpolicy.IStructure; import org.xmind.gef.part.IPart; import org.xmind.ui.branch.IBranchStructureExtension; import org.xmind.ui.commands.AddTopicCommand; import org.xmind.ui.commands.CommandMessages; import org.xmind.ui.commands.DeleteTopicCommand; import org.xmind.ui.commands.ModifyPositionCommand; import org.xmind.ui.commands.ModifyRightNumberOfUnbalancedStructureCommand; import org.xmind.ui.internal.branch.UnbalancedData; import org.xmind.ui.mindmap.IBranchPart; import org.xmind.ui.mindmap.ITopicPart; import org.xmind.ui.mindmap.MindMapUI; import org.xmind.ui.util.MindMapUtils; public class TopicMovablePolicy extends MindMapPolicyBase { public boolean understands(String requestType) { return super.understands(requestType) || GEF.REQ_MOVETO.equals(requestType) || GEF.REQ_COPYTO.equals(requestType) || GEF.REQ_ALIGN.equals(requestType) || GEF.REQ_MOVE_UP.equals(requestType) || GEF.REQ_MOVE_DOWN.equals(requestType) || GEF.REQ_MOVE_LEFT.equals(requestType) || GEF.REQ_MOVE_RIGHT.equals(requestType) || GEF.REQ_SORT.equals(requestType); } public void handle(Request request) { String requestType = request.getType(); if (GEF.REQ_MOVETO.equals(requestType) || GEF.REQ_COPYTO.equals(requestType)) { moveOrCopyTopics(request); } else if (GEF.REQ_ALIGN.equals(requestType)) { alignTopics(request); } else if (GEF.REQ_SORT.equals(requestType)) { sortTopics(request); } else if (GEF.REQ_MOVE_UP.equals(requestType) || GEF.REQ_MOVE_DOWN.equals(requestType) || GEF.REQ_MOVE_LEFT.equals(requestType) || GEF.REQ_MOVE_RIGHT.equals(requestType)) { quickMove(request, requestType); } } private void quickMove(Request request, String direction) { int dir = getDirection(direction); if (dir < 0) return; IBranchPart parent = null; List<IBranchPart> branches = new ArrayList<IBranchPart>(); for (IPart p : request.getTargets()) { IBranchPart branch = MindMapUtils.findBranch(p); if (branch != null) { if (!branch.getTopic().isAttached()) return; if (parent != null && parent != branch.getParentBranch()) return; if (parent == null) parent = branch.getParentBranch(); if (parent == null) return; branches.add(branch); } } if (parent == null) return; IStructure structure = parent.getBranchPolicy().getStructure(parent); if (!(structure instanceof IBranchStructureExtension)) return; Collections.sort(branches, new Comparator<IBranchPart>() { public int compare(IBranchPart o1, IBranchPart o2) { return o1.getBranchIndex() - o2.getBranchIndex(); } }); IBranchStructureExtension bse = (IBranchStructureExtension) structure; List<Command> commands = new ArrayList<Command>(); int total = parent.getTopic().getChildren(ITopic.ATTACHED).size(); for (int i = 0; i < branches.size(); i++) { IBranchPart child = branches.get(i); int offset = bse.getQuickMoveOffset(parent, child, dir); if (offset > 0) { for (int j = child.getBranchIndex() + 1; j < parent .getSubBranches().size(); j++) { IBranchPart b = parent.getSubBranches().get(j); if (!branches.contains(b)) break; offset++; } } ITopic topic = child.getTopic(); int index = topic.getIndex(); int newIndex = index + offset; if (newIndex < 0 || newIndex >= total) return; DeleteTopicCommand c1 = new DeleteTopicCommand(topic); AddTopicCommand c2 = new AddTopicCommand(topic, parent.getTopic(), newIndex, ITopic.ATTACHED); commands.add(c1); commands.add(c2); } if (commands.isEmpty()) return; CompoundCommand command = new CompoundCommand(commands); command.setLabel(CommandMessages.Command_MoveTopic); saveAndRun(command, request.getTargetDomain()); } private int getDirection(String direction) { if (GEF.REQ_MOVE_UP.equals(direction)) return PositionConstants.NORTH; if (GEF.REQ_MOVE_DOWN.equals(direction)) return PositionConstants.SOUTH; if (GEF.REQ_MOVE_LEFT.equals(direction)) return PositionConstants.WEST; if (GEF.REQ_MOVE_RIGHT.equals(direction)) return PositionConstants.EAST; return -1; } private void sortTopics(Request request) { } private void alignTopics(Request request) { List<ITopic> topics = MindMapUtils.filterOutDescendents( MindMapUtils.getTopics(request.getTargets()), null); if (topics.isEmpty()) return; Object param = request.getParameter(GEF.PARAM_ALIGNMENT); if (param == null || !(param instanceof Integer)) return; int alignment = ((Integer) param).intValue(); IViewer viewer = request.getTargetViewer(); List<ITopicPart> topicParts = new ArrayList<ITopicPart>(topics.size()); for (ITopic topic : topics) { IPart p = viewer.findPart(topic); if (p instanceof ITopicPart) { topicParts.add((ITopicPart) p); } } if (topicParts.isEmpty()) return; TopicAlignmentSolver solver = new TopicAlignmentSolver(alignment); solver.recordInitPositions(topicParts); solver.solve(); List<Command> commands = new ArrayList<Command>(topicParts.size()); for (Object key : solver.getKeys(IIntersectionSolver.CATEGORY_FREE)) { if (key instanceof ITopicPart) { ITopicPart topicPart = (ITopicPart) key; Point pos = solver.getSolvedPosition(key); pos = getNewPosition(topicPart, pos); ITopic topic = topicPart.getTopic(); commands.add(new ModifyPositionCommand(topic, MindMapUtils.toModelPosition(pos))); } } if (commands.isEmpty()) return; String commandLabel = getAlignCommandLabel(alignment); CompoundCommand cmd = new CompoundCommand(commands); cmd.setLabel(commandLabel); saveAndRun(cmd, request.getTargetDomain()); } private String getAlignCommandLabel(int alignment) { switch (alignment) { case PositionConstants.LEFT: return CommandMessages.Command_AlignLeft; case PositionConstants.CENTER: return CommandMessages.Command_AlignCenter; case PositionConstants.RIGHT: return CommandMessages.Command_AlignRight; case PositionConstants.TOP: return CommandMessages.Command_AlignTop; case PositionConstants.MIDDLE: return CommandMessages.Command_AlignMiddle; case PositionConstants.BOTTOM: return CommandMessages.Command_AlignBottom; } return CommandMessages.Command_Align; } private Point getNewPosition(ITopicPart topicPart, Point pos) { IBranchPart branch = topicPart.getOwnerBranch(); if (branch != null) { IBranchPart parent = branch.getParentBranch(); if (parent != null) { Point parentPos = ((IReferencedFigure) parent.getFigure()) .getReference(); pos = new Point(pos.x - parentPos.x, pos.y - parentPos.y); } } return pos; } private boolean hasCallout(List<ITopic> topics) { if (topics == null || topics.isEmpty()) return false; for (ITopic callout : topics) { if (ITopic.CALLOUT.equals(callout.getType())) return true; } return false; } private void moveOrCopyTopics(Request request) { List<IPart> sources = request.getTargets(); List<ITopic> topics = MindMapUtils .filterOutDescendents(MindMapUtils.getTopics(sources), null); if (topics.isEmpty()) return; Collections.sort(topics, Core.getTopicComparator()); Point targetPosition = (Point) request.getParameter(GEF.PARAM_POSITION); ITopicPart parentPart = getTargetParent(request); boolean copy = Boolean.TRUE .equals(request.getParameter(MindMapUI.PARAM_COPY)); if (targetPosition == null && parentPart == null && !copy) return; boolean relative = Boolean.TRUE .equals(request.getParameter(GEF.PARAM_POSITION_RELATIVE)); int targetIndex = request.getIntParameter(GEF.PARAM_INDEX, -1); IViewer viewer = request.getTargetViewer(); ITopic targetParent; String targetType; if (parentPart != null) { targetParent = parentPart.getTopic(); //can't add itself as child if (topics.contains(targetParent)) return; if (hasCallout(topics)) { targetType = ITopic.CALLOUT; } else { targetType = ITopic.ATTACHED; } } else { targetParent = (ITopic) viewer.getAdapter(ITopic.class); targetType = ITopic.DETACHED; parentPart = (ITopicPart) viewer.getAdapter(ITopicPart.class); } if (targetParent == null || (!copy && !isValidMoveToNewParent(targetParent, topics))) // Sorry, NO valid parent topic has been found to add these topics return; TopicMoveCommandBuilder builder = new TopicMoveCommandBuilder(viewer, request.getTargetCommandStack(), targetParent, targetIndex, targetType, targetPosition, relative); IBranchPart parentBranch = MindMapUtils.findBranch(parentPart); if (parentBranch == null) return; ITopic centralTopic = (ITopic) viewer.getAdapter(ITopic.class); if (centralTopic == null) return; String centralTopicStructure = centralTopic.getStructureClass(); boolean isUnbalancedStructure = centralTopicStructure == null || UnbalancedData.STRUCTUREID_UNBALANCED .equalsIgnoreCase(centralTopicStructure); if (isUnbalancedStructure) { ITopicExtension extension = centralTopic.createExtension( UnbalancedData.EXTENTION_UNBALANCEDSTRUCTURE); ITopicExtensionElement element = extension.getContent() .getCreatedChild( UnbalancedData.EXTENTIONELEMENT_RIGHTNUMBER); String preMoveRightNum = element.getTextContent(); if (preMoveRightNum == null) preMoveRightNum = String.valueOf(0); int postMoveRightNum = Integer.valueOf(preMoveRightNum); for (IPart selected : sources) { IBranchPart mainBranch = MindMapUtils.findBranch(selected); if (mainBranch != null && !mainBranch.isCentral()) { IBranchPart centralBranch = mainBranch.getParentBranch(); if (centralBranch != null && centralBranch.isCentral()) { IStructure structure = centralBranch.getBranchPolicy() .getStructure(centralBranch); if ((((IBranchStructureExtension) structure) .getChildTargetOrientation(centralBranch, mainBranch) == PositionConstants.WEST)) { postMoveRightNum--; } } if (parentBranch.isCentral() && targetType != ITopic.DETACHED) { Rectangle bounds = parentPart.getFigure().getBounds(); boolean positionRight = bounds.getCenter() .getDifference((Point) request.getParameter( GEF.PARAM_POSITION_ABSOLUTE)).width < 0; org.xmind.core.util.Point position = mainBranch .getTopic().getPosition(); // boolean topicPosForceRight = position != null && position.x > 0; // boolean posRight = position == null && positionRight; boolean right = position != null ? position.x > 0 : positionRight; if (right) { postMoveRightNum++; } } } } if (!preMoveRightNum.equals(postMoveRightNum)) { ModifyRightNumberOfUnbalancedStructureCommand modifyRightNumberCommand = new ModifyRightNumberOfUnbalancedStructureCommand( centralTopic, preMoveRightNum, postMoveRightNum); modifyRightNumberCommand .setLabel(copy ? CommandMessages.Command_CopyTopic : CommandMessages.Command_MoveTopic); builder.addPendingCommand(modifyRightNumberCommand, true); } } PropertyCommandBuilder builder2 = new PropertyCommandBuilder(viewer, builder, request); if (!builder.canStart()) return; builder.setLabel(copy ? CommandMessages.Command_CopyTopic : CommandMessages.Command_MoveTopic); builder.start(); builder2.start(); if (copy) builder.copyTopics(topics); else builder.moveTopics(topics); builder2.addSources(topics.toArray(), true); builder2.end(); builder.end(); // saveAndRun(cmd, request.getTargetDomain()); Command cmd = builder.getCommand(); if (cmd instanceof ISourceProvider) { select(((ISourceProvider) cmd).getSources(), viewer); } } private ITopicPart getTargetParent(Request request) { Object param = request.getParameter(GEF.PARAM_PARENT); if (param instanceof ITopicPart) return (ITopicPart) param; return null; } private boolean isValidMoveToNewParent(ITopic newParent, List<ITopic> topics) { if (MindMapUtils.isAncestorInList(newParent, topics)) return false; Set<ITopicRange> ranges = MindMapUtils.findContainedRanges(topics, true, false); if (!ranges.isEmpty()) { for (ITopicRange range : ranges) { ITopic summaryTopic = ((ISummary) range).getTopic(); if (summaryTopic != null && (summaryTopic.equals(newParent) || MindMapUtils .isDescendentOf(newParent, summaryTopic))) return false; } } return true; } // private Command createReorganizeCommand(Request request, // List<ITopic> topics, IViewer viewer, Point position, // boolean relative, ITopic parent, int index, String type, // boolean copy) { // // TopicMoveCommandBuilder builder = new TopicMoveCommandBuilder(viewer, // parent, index, type, position, relative, copy); // for (ITopic t : topics) { // builder.moveTopic(t); // } // PropertyCommandBuilder propCmdBuilder = new PropertyCommandBuilder( // viewer); // propCmdBuilder.addFromRequest(request, false); // if (!propCmdBuilder.isEmpty()) { // for (Command c : propCmdBuilder.getCommands()) { // builder.addCommand(c, false); // } // } // return builder.createCommand(); // } }