/* ****************************************************************************** * 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.tools; import java.util.ArrayList; import java.util.List; import java.util.Set; import org.eclipse.core.runtime.Assert; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.Layer; import org.eclipse.draw2d.UpdateManager; 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.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.util.Util; import org.eclipse.swt.graphics.Cursor; import org.xmind.core.ISummary; import org.xmind.core.ITopic; import org.xmind.core.ITopicRange; import org.xmind.gef.GEF; import org.xmind.gef.Request; import org.xmind.gef.draw2d.DecoratedShapeFigure; import org.xmind.gef.draw2d.IReferencedFigure; import org.xmind.gef.draw2d.decoration.PathShapeDecoration; import org.xmind.gef.draw2d.graphics.Path; import org.xmind.gef.event.MouseDragEvent; import org.xmind.gef.graphicalpolicy.IStructure; import org.xmind.gef.part.IGraphicalEditPart; import org.xmind.gef.part.IPart; import org.xmind.gef.status.IStatusListener; import org.xmind.gef.status.StatusEvent; import org.xmind.gef.tool.ITool; import org.xmind.ui.branch.IInsertableBranchStructureExtension; import org.xmind.ui.branch.ILockableBranchStructureExtension; import org.xmind.ui.branch.IMovableBranchStructureExtension; import org.xmind.ui.internal.MindMapUIPlugin; import org.xmind.ui.mindmap.IBranchPart; import org.xmind.ui.mindmap.IMindMapImages; import org.xmind.ui.mindmap.ITopicPart; import org.xmind.ui.mindmap.MindMapUI; import org.xmind.ui.prefs.PrefConstants; import org.xmind.ui.tools.DummyMoveTool; import org.xmind.ui.tools.ITopicMoveToolHelper; import org.xmind.ui.tools.ParentSearchKey; import org.xmind.ui.tools.ParentSearcher; import org.xmind.ui.util.MindMapUtils; public class TopicMoveTool extends DummyMoveTool implements IStatusListener { private class RoundedRectDecoration extends PathShapeDecoration { public Insets getPreferredInsets(IFigure figure, int width, int height) { int lineWidth = getLineWidth(); return new Insets(lineWidth, lineWidth, lineWidth, lineWidth); } @Override protected void sketch(IFigure figure, Path shape, Rectangle box, int purpose) { shape.addRoundedRectangle(box, 5); } } private static final int INVENT_WIDTH = 60; private static final int INVENT_HEIGHT = 16; private static ITopicMoveToolHelper defaultHelper = null; private IFigure invent = null; private Point inventStartLoc = null; private ParentSearcher parentSearcher = null; private boolean slightMove = false; private boolean specialMove = false; private ITopicMoveToolHelper helper = null; private ParentSearchKey key = null; private BranchDummy branchDummy = null; private IBranchPart targetParent = null; private List<IFigure> disabledFigures = null; private IPart specialTargetPart = null; private int specialIndex = -1; public TopicMoveTool() { initMoveTopicMoveTool(); getStatus().addStatusListener(this); } private void initMoveTopicMoveTool() { IPreferenceStore prefStore = MindMapUIPlugin.getDefault() .getPreferenceStore(); boolean status = prefStore .getBoolean(PrefConstants.MANUAL_LAYOUT_ALLOWED); getStatus().setStatus(GEF.ST_FREE_MOVE_MODE, status); } public void setSource(IGraphicalEditPart source) { Assert.isTrue(source instanceof ITopicPart); super.setSource(source); } protected ITopicPart getSourceTopic() { return (ITopicPart) super.getSource(); } protected IBranchPart getSourceBranch() { return (IBranchPart) getSourceTopic().getParent(); } public IFigure getInvent() { return invent; } private void doCreateInvent() { if (invent != null) return; if (!getStatus().isStatus(GEF.ST_ACTIVE)) return; invent = createInvent(); } private Point getInventStartLoc() { if (inventStartLoc == null) { IFigure fig = getInvent(); if (fig != null) { if (fig instanceof IReferencedFigure) { inventStartLoc = ((IReferencedFigure) fig).getReference(); } else { inventStartLoc = fig.getBounds().getLocation(); } } } return inventStartLoc; } protected void onActivated(ITool prevTool) { super.onActivated(prevTool); lockBranchStructures(getTargetViewer().getRootPart()); collectDisabledBranches(); if (!isCopyMove()) { disableFigures(); } } private void lockBranchStructures(IPart part) { if (part instanceof IBranchPart) { IBranchPart branch = (IBranchPart) part; IStructure sa = branch.getBranchPolicy().getStructure(branch); if (sa instanceof ILockableBranchStructureExtension) { ((ILockableBranchStructureExtension) sa).lock(branch); } } for (IPart child : part.getChildren()) { lockBranchStructures(child); } } private void unlockBranchStructures(IPart part) { if (part instanceof IBranchPart) { IBranchPart branch = (IBranchPart) part; IStructure sa = branch.getBranchPolicy().getStructure(branch); if (sa instanceof ILockableBranchStructureExtension) { ((ILockableBranchStructureExtension) sa).unlock(branch); } } for (IPart child : part.getChildren()) { unlockBranchStructures(child); } } protected IFigure createDummy() { slightMove = true; if (branchDummy == null) { branchDummy = new BranchDummy(getTargetViewer(), getSourceBranch()); } return branchDummy.getBranch().getFigure(); } protected void destroyDummy(IFigure dummy) { if (branchDummy != null) { branchDummy.dispose(); branchDummy = null; } super.destroyDummy(dummy); } private void destroyInvent() { if (invent != null) { destroyInvent(invent); invent = null; } } private IFigure createInvent() { DecoratedShapeFigure figure = new DecoratedShapeFigure(); Point loc = getDummyStartLoc(); if (loc != null) figure.setLocation( loc.getTranslated(-INVENT_WIDTH / 2, -INVENT_HEIGHT / 2)); figure.setSize(INVENT_WIDTH, INVENT_HEIGHT); figure.setDecoration(new RoundedRectDecoration()); Layer layer = getTargetViewer().getLayer(GEF.LAYER_PRESENTATION); if (layer != null) layer.add(figure); return figure; } private void collectDisabledBranches() { List<IPart> selectedParts = getSelectedParts(getTargetViewer()); for (IPart part : selectedParts) { addDisabledPart(part); } List<ITopic> topics = MindMapUtils.getTopics(selectedParts); Set<ITopicRange> ranges = MindMapUtils.findContainedRanges(topics, true, false); if (!ranges.isEmpty()) { for (ITopicRange r : ranges) { ITopic st = ((ISummary) r).getTopic(); if (st != null) { addDisabledPart(getTargetViewer().findPart(st)); } } } } private void addDisabledPart(IPart part) { if (part instanceof ITopicPart) { ITopicPart topic = (ITopicPart) part; addDisabledFigure(topic.getFigure()); IBranchPart branch = topic.getOwnerBranch(); if (branch != null) { addDisabledFigure(branch.getFigure()); } } } private void addDisabledFigure(IFigure figure) { if (disabledFigures == null) disabledFigures = new ArrayList<IFigure>(); disabledFigures.add(figure); } private void clearDisabledFigures() { disabledFigures = null; } private void disableFigures() { if (disabledFigures != null) { for (IFigure figure : disabledFigures) { figure.setEnabled(false); } } } private void enableFigures() { if (disabledFigures != null) { for (IFigure figure : disabledFigures) { figure.setEnabled(true); } } } protected void onMoving(Point currentPos, MouseDragEvent me) { if (slightMove) { if (!((IGraphicalEditPart) getSourceTopic()) .containsPoint(currentPos)) { slightMove = false; } } super.onMoving(currentPos, me); if (branchDummy != null) { key = new ParentSearchKey( getSourceTopic(), (IReferencedFigure) branchDummy .getBranch().getTopicPart().getFigure(), currentPos); key.setFeedback(branchDummy.getBranch()); targetParent = updateTargetParent(); if (specialIndex < 0) { if (invent == null) invent = createInvent(); key.setInvent(invent); updateInventPosition(currentPos); } updateWithParent(targetParent); } } private void updateInventPosition(Point pos) { IFigure fig = getInvent(); int x = 0; int y = 0; if (fig != null) { Point cursorStart = getStartingPosition(); Point inventStart = getInventStartLoc(); if (usesRelativeLocation() && cursorStart != null && inventStart != null) { if (targetParent != null && key != null) { Point insertionPosition = calcInsertionPosition( targetParent, getSourceBranch(), key); x = insertionPosition.x; y = insertionPosition.y; } else { x = pos.x - cursorStart.x + inventStart.x; y = pos.y - cursorStart.y + inventStart.y; } if (fig instanceof IReferencedFigure) ((IReferencedFigure) fig).setReference(x, y); else fig.setLocation(new Point(x, y)); } else { if (fig instanceof IReferencedFigure) ((IReferencedFigure) fig).setReference(pos.x, pos.y); else fig.setLocation(pos); } } } private Point calcInsertionPosition(IBranchPart parent, IBranchPart child, ParentSearchKey key) { UpdateManager um = key.getFigure().getUpdateManager(); if (um != null) um.performValidation(); if (parent != null) { IStructure structure = parent.getBranchPolicy() .getStructure(parent); if (structure instanceof IInsertableBranchStructureExtension) { return ((IInsertableBranchStructureExtension) structure) .calcInsertionPosition(parent, child, key); } } return new Point(0, 0); } private boolean isBranchMoved(IBranchPart parent, IBranchPart child, ParentSearchKey key) { UpdateManager um = key.getFigure().getUpdateManager(); if (um != null) um.performValidation(); if (parent != null) { IStructure structure = parent.getBranchPolicy() .getStructure(parent); if (structure instanceof IInsertableBranchStructureExtension) { return ((IInsertableBranchStructureExtension) structure) .isBranchMoved(parent, child, key); } } return true; } private IBranchPart updateTargetParent() { if (isFloatMove()) return null; if (isSpecialFreeMove() && specialTargetPart instanceof IBranchPart) { return (IBranchPart) specialTargetPart; } if (isFreeMove() || isSlightMove()) { IPart parent = getSourceBranch().getParent(); return parent instanceof IBranchPart ? (IBranchPart) parent : null; } return getParentSearcher() .searchTargetParent(getTargetViewer().getRootPart(), key); } private void updateWithParent(IBranchPart parent) { updateDummyWithParent(parent); updateHelperWithParent(parent); if (specialIndex < 0) updateInventVisible(parent); } private void updateDummyWithParent(IBranchPart parent) { } private void updateInventVisible(IBranchPart parent) { if (invent != null) invent.setVisible(parent != null); } protected void updateDummyPosition(Point pos) { super.updateDummyPosition(pos); } private void updateHelperWithParent(IBranchPart parent) { ITopicMoveToolHelper oldHelper = this.helper; ITopicMoveToolHelper newHelper = getHelper(parent); if (newHelper != oldHelper) { if (oldHelper != null) oldHelper.deactivate(getDomain(), getTargetViewer()); if (newHelper != null) newHelper.activate(getDomain(), getTargetViewer()); this.helper = newHelper; } if (helper != null) { helper.update(parent, isBranchMoved(parent, getSourceBranch(), key), key, specialIndex); } } private ITopicMoveToolHelper getHelper(IBranchPart parent) { // if (parent != null) { // ITopicMoveToolHelper helper = (ITopicMoveToolHelper) parent // .getBranchPolicy().getToolHelper(parent, // ITopicMoveToolHelper.class); // if (helper != null) // return helper; // } return getDefaultHelper(); } protected static ITopicMoveToolHelper getDefaultHelper() { if (defaultHelper == null) { defaultHelper = new TopicMoveToolHelper(); } return defaultHelper; } private boolean isSlightMove() { if (isFloatMove() || isAlreadyFloat()) return false; if (isSpecialFreeMove()) return false; if (isFreeable()) { if (isFreeMove() || isAlreadyFree()) return false; } return slightMove; } private boolean isFloatMove() { return getStatus().isStatus(GEF.ST_SHIFT_PRESSED); } private boolean isFreeMove() { if (isFreeMovePattern()) return true; if (isSpecialFreeMove()) return true; if (Util.isMac()) return getStatus().isStatus(GEF.ST_CONTROL_PRESSED); return getStatus().isStatus(GEF.ST_ALT_PRESSED); } private boolean isFreeMovePattern() { ITopicPart topicPart = getSourceParentTopic(); ITopic topic = null; if (topicPart != null) topic = topicPart.getTopic(); return topic == null ? getStatus().isStatus(GEF.ST_FREE_MOVE_MODE) : getStatus().isStatus(GEF.ST_FREE_MOVE_MODE) && topic.isRoot(); } private boolean isCopyMove() { if (Util.isMac()) return getStatus().isStatus(GEF.ST_ALT_PRESSED); return getStatus().isStatus(GEF.ST_CONTROL_PRESSED); } private boolean isSpecialFreeMove() { return specialMove; } private boolean hasSpecicalPart() { List<IPart> selectedParts = getSelectedParts(getTargetViewer()); for (IPart movedPart : selectedParts) { if (movedPart instanceof ITopicPart) { String type = ((ITopicPart) movedPart).getTopic().getType(); if (ITopic.CALLOUT.equals(type)) { IPart movedBranch = ((ITopicPart) movedPart).getParent(); specialTargetPart = movedBranch.getParent(); if (movedBranch != null && specialTargetPart instanceof IBranchPart) { specialIndex = ((IBranchPart) specialTargetPart) .getCalloutBranches().indexOf(movedBranch); } return true; } } } return false; } protected ParentSearcher getParentSearcher() { if (parentSearcher == null) { parentSearcher = new ParentSearcher(); } return parentSearcher; } @Override protected void start() { super.start(); if (createsDummyOnActivated()) doCreateInvent(); specialMove = hasSpecicalPart(); } protected void end() { if (helper != null) { helper.deactivate(getDomain(), getTargetViewer()); helper = null; } super.end(); destroyInvent(); inventStartLoc = null; targetParent = null; parentSearcher = null; key = null; specialMove = false; specialTargetPart = null; specialIndex = -1; enableFigures(); clearDisabledFigures(); unlockBranchStructures(getTargetViewer().getRootPart()); } protected void suspend() { if (helper != null) { helper.deactivate(getDomain(), getTargetViewer()); helper = null; } super.end(); destroyInvent(); } private void destroyInvent(IFigure invent) { if (invent.getParent() != null) invent.getParent().remove(invent); } protected Request createRequest() { if (isSlightMove()) return null; IBranchPart targetParentBranch = this.targetParent; ITopicPart targetParent = targetParentBranch == null ? null : targetParentBranch.getTopicPart(); boolean relative = true;//isRelative(); Point position = relative ? getRelativePosition() : getAbsolutePosition(); boolean copy = isCopyMove(); int index = -1; boolean free = isFreeMove(); if (!isFloatMove()) { index = getParentSearcher().getIndex(targetParentBranch, key); if (free) { if (!isFreeable() && !isAlreadyFloat() && !isSpecialFreeMove()) { free = false; } } else { if (targetParent != null) { if (targetParent == getSourceParentTopic()) { if (isFreeable()) { free = isAlreadyFree(); } } else {//if ((!isAlreadyFloat() && !isAlreadyFree())) { position = null; } } } if (!free && targetParent != null) { position = null; } } String reqType = copy ? GEF.REQ_COPYTO : GEF.REQ_MOVETO; Request request = new Request(reqType); request.setDomain(getDomain()); request.setViewer(getTargetViewer()); List<IPart> parts = new ArrayList<IPart>(); for (IPart p : getSelectedParts(getTargetViewer())) { if (p.hasRole(GEF.ROLE_MOVABLE)) { parts.add(p); } } request.setTargets(parts); // fillTargets(request, getTargetViewer(), false); request.setPrimaryTarget(getSourceTopic()); request.setParameter(GEF.PARAM_POSITION, position); request.setParameter(GEF.PARAM_POSITION_ABSOLUTE, getAbsolutePosition()); request.setParameter(GEF.PARAM_POSITION_RELATIVE, Boolean.valueOf(relative)); request.setParameter(GEF.PARAM_PARENT, targetParent); request.setParameter(GEF.PARAM_INDEX, Integer.valueOf(index)); request.setParameter(MindMapUI.PARAM_COPY, Boolean.valueOf(copy)); request.setParameter(MindMapUI.PARAM_FREE, Boolean.valueOf(free)); IBranchPart sourceParent; IBranchPart sourceBranch = getSourceBranch(); if (sourceBranch != null) { sourceParent = sourceBranch.getParentBranch(); if (sourceParent != null) { IStructure structure = sourceParent.getBranchPolicy() .getStructure(sourceParent); if (structure instanceof IMovableBranchStructureExtension) { ((IMovableBranchStructureExtension) structure) .decorateMoveOutRequest(sourceParent, key, targetParentBranch, request); } } } else { sourceParent = null; } if (targetParentBranch != null) { IStructure structure = targetParentBranch.getBranchPolicy() .getStructure(targetParentBranch); if (structure instanceof IMovableBranchStructureExtension) { ((IMovableBranchStructureExtension) structure) .decorateMoveInRequest(targetParentBranch, key, sourceParent, request); } } return request; } private boolean isAlreadyFree() { return getSourceTopic().getTopic().getPosition() != null; } private boolean isAlreadyFloat() { return !getSourceTopic().getTopic().isAttached(); } private boolean isFreeable() { if (!MindMapUI.isFreePositionMoveAllowed()) return false; IBranchPart branch = getSourceBranch(); return branch != null && MindMapUtils.isSubBranchesFreeable(branch.getParentBranch()); } private ITopicPart getSourceParentTopic() { IBranchPart sourceBranch = getSourceBranch(); if (sourceBranch != null) { IPart p = sourceBranch.getParent(); if (p instanceof IBranchPart) { return ((IBranchPart) p).getTopicPart(); } } return null; } // private boolean isRelative() { // return targetParent != null; // } private Point getRelativePosition() { Dimension off = getCursorPosition() .getDifference(getStartingPosition()); return new Point(off.width, off.height); } private Point getAbsolutePosition() { return getCursorPosition(); } public void statusChanged(StatusEvent event) { int k = event.key; if (getStatus().isStatus(GEF.ST_ACTIVE)) { if (k == GEF.ST_SHIFT_PRESSED || k == GEF.ST_CONTROL_PRESSED || k == GEF.ST_ALT_PRESSED) { updateDisabilities(); updateDummyPosition(getCursorPosition()); targetParent = updateTargetParent(); updateWithParent(targetParent); } } } private void updateDisabilities() { if (isCopyMove()) { enableFigures(); } else { disableFigures(); } } public Cursor getCurrentCursor(Point pos, IPart host) { if (isCopyMove()) return MindMapUI.getImages().getCursor(IMindMapImages.CURSOR_ADD); return super.getCurrentCursor(pos, host); } }