/FileExtraction/Java_unseen/X0Ken_FreeMind/accessories/plugins/ClonePasteAction.java
/*FreeMind - A Program for creating and viewing Mindmaps
 *Copyright (C) 2000-2011 Joerg Mueller, Daniel Polansky, Christian Foltin, Dimitri Polivaev and others.
 *
 *See COPYING for Details
 *
 *This program is free software; you can redistribute it and/or
 *modify it under the terms of the GNU General Public License
 *as published by the Free Software Foundation; either version 2
 *of the License, or (at your option) any later version.
 *
 *This program is distributed in the hope that it will be useful,
 *but WITHOUT ANY WARRANTY; without even the implied warranty of
 *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *GNU General Public License for more details.
 *
 *You should have received a copy of the GNU General Public License
 *along with this program; if not, write to the Free Software
 *Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package accessories.plugins;

import java.awt.datatransfer.Transferable;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Vector;

import javax.swing.Action;
import javax.swing.ImageIcon;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;

import freemind.common.OptionalDontShowMeAgainDialog;
import freemind.controller.MenuItemEnabledListener;
import freemind.controller.MindMapNodesSelection;
import freemind.controller.actions.generated.instance.CompoundAction;
import freemind.controller.actions.generated.instance.CutNodeAction;
import freemind.controller.actions.generated.instance.DeleteNodeAction;
import freemind.controller.actions.generated.instance.HookNodeAction;
import freemind.controller.actions.generated.instance.MoveNodeXmlAction;
import freemind.controller.actions.generated.instance.MoveNodesAction;
import freemind.controller.actions.generated.instance.NewNodeAction;
import freemind.controller.actions.generated.instance.NodeAction;
import freemind.controller.actions.generated.instance.NodeListMember;
import freemind.controller.actions.generated.instance.PasteNodeAction;
import freemind.controller.actions.generated.instance.UndoPasteNodeAction;
import freemind.controller.actions.generated.instance.XmlAction;
import freemind.extensions.HookRegistration;
import freemind.main.FreeMind;
import freemind.main.Resources;
import freemind.main.Tools;
import freemind.main.Tools.MindMapNodePair;
import freemind.modes.MindMap;
import freemind.modes.MindMapNode;
import freemind.modes.ModeController;
import freemind.modes.ModeController.NodeSelectionListener;
import freemind.modes.NodeAdapter;
import freemind.modes.mindmapmode.MindMapController;
import freemind.modes.mindmapmode.actions.NodeHookAction;
import freemind.modes.mindmapmode.actions.xml.ActionFilter;
import freemind.modes.mindmapmode.actions.xml.ActionPair;
import freemind.modes.mindmapmode.hooks.MindMapNodeHookAdapter;
import freemind.view.mindmapview.NodeView;

/**
 * This is the "paste node as clone" action from the menu.
 * 
 * @author foltin
 * @date 25.4.2011
 * 
 */
public class ClonePasteAction extends MindMapNodeHookAdapter {

	/**
	 * 
	 */
	public ClonePasteAction() {
	}

	public void invoke(MindMapNode pNode) {
		super.invoke(pNode);
		Vector mindMapNodes = getMindMapNodes();
		logger.fine("Clones for nodes: " + Tools.listToString(mindMapNodes));
		// now, construct the plugin for those nodes:
		for (Iterator itPastedNodes = mindMapNodes.iterator(); itPastedNodes
				.hasNext();) {
			MindMapNode copiedNode = (MindMapNode) itPastedNodes.next();
			ClonePlugin clonePlugin = ClonePlugin.getHook(copiedNode);
			// first the clone master
			if (clonePlugin == null) {
				int showResult = new OptionalDontShowMeAgainDialog(
						getMindMapController().getFrame().getJFrame(),
						getMindMapController().getSelectedView(),
						"choose_clone_type",
						"clone_type_question",
						getMindMapController(),
						new OptionalDontShowMeAgainDialog.StandardPropertyHandler(
								getMindMapController().getController(),
								FreeMind.RESOURCES_COMPLETE_CLONING),
						OptionalDontShowMeAgainDialog.BOTH_OK_AND_CANCEL_OPTIONS_ARE_STORED)
						.show().getResult();
				Properties properties = new Properties();
				properties
						.setProperty(
								ClonePlugin.XML_STORAGE_CLONE_ITSELF,
								showResult == JOptionPane.OK_OPTION ? ClonePlugin.CLONE_ITSELF_TRUE
										: ClonePlugin.CLONE_ITSELF_FALSE);
				Vector selecteds = Tools.getVectorWithSingleElement(copiedNode);
				getMindMapController().addHook(copiedNode, selecteds,
						ClonePlugin.PLUGIN_LABEL, properties);
			}
			// finally, we construct a new one:
			Transferable copy = getMindMapController().copy(copiedNode, true);
			addNewClone(copiedNode, pNode, copy);
		}
	}

	public void addNewClone(MindMapNode originalNode,
			MindMapNode pDestinationNode, Transferable copy) {
		String originalNodeId = getMindMapController().getNodeID(originalNode);
		logger.fine("Original node " + originalNode + ", id " + originalNodeId);
		if (originalNode.isRoot()) {
			getMindMapController().getController().errorMessage(
					getMindMapController().getText(
							"clone_plugin_no_root_cloning"));
			return;
		}
		// insert clone:
		List listOfChilds = pDestinationNode.getChildren();
		Vector listOfChildIds = new Vector();
		for (Iterator it = listOfChilds.iterator(); it.hasNext();) {
			String nodeID = getMindMapController().getNodeID(
					(MindMapNode) it.next());
			listOfChildIds.add(nodeID);
			logger.fine("Old child id:" + nodeID);
		}
		getMindMapController().paste(copy, pDestinationNode);
	}

	public Vector getMindMapNodes() {
		return getRegistration().getMindMapNodes();
	}

	protected Registration getRegistration() {
		return (Registration) getPluginBaseClass();
	}

	public interface ClonePropertiesObserver {
		void propertiesChanged(CloneProperties pCloneProperties);
	}

	public static class CloneProperties {
		boolean mCloneItself = false;
		private HashSet mObserverSet = new HashSet();
		protected static java.util.logging.Logger logger = null;

		/**
		 * 
		 */
		public CloneProperties() {
			if (logger == null) {
				logger = freemind.main.Resources.getInstance().getLogger(
						this.getClass().getName());
			}
		}
		
		public boolean isCloneItself() {
			return mCloneItself;
		}

		public void setCloneItself(boolean pCloneItself) {
			logger.finest("Setting mCloneItself to " + pCloneItself);
			boolean fire = false;
			if (pCloneItself != mCloneItself) {
				fire = true;
			}
			mCloneItself = pCloneItself;
			if (fire) {
				firePropertiesChanged();
			}
		}

		public void registerObserver(ClonePropertiesObserver pObserver) {
			mObserverSet.add(pObserver);
		}

		public void deregisterObserver(ClonePropertiesObserver pObserver) {
			mObserverSet.remove(pObserver);
		}

		private void firePropertiesChanged() {
			for (Iterator it = mObserverSet.iterator(); it.hasNext();) {
				ClonePropertiesObserver observer = (ClonePropertiesObserver) it
						.next();
				observer.propertiesChanged(this);
			}
		}

	}

	public static class Registration implements HookRegistration,
			MenuItemEnabledListener, ActionFilter, NodeSelectionListener {

		private static final String PLUGIN_NAME = "accessories/plugins/ClonePasteAction.properties";
		private static ImageIcon sCloneIcon;
		private static ImageIcon sOriginalIcon;
		private static boolean sShowIcon = true;

		/**
		 * Mapping of clone id (String) to a HashSet of {@link MindMapNode}s
		 */
		private HashMap mCloneIdsMap = new HashMap();
		/**
		 * This is the reverse of mCloneIdsMap: {@link MindMapNode} to cloneId.
		 */
		private HashMap mClonesMap = new HashMap();

		/**
		 * This is a storage cloneId to clone properties.
		 */
		private HashMap mClonePropertiesMap = new HashMap();

		private final MindMapController controller;

		private final MindMap mMap;

		private final java.util.logging.Logger logger;
		private Vector mLastMarkedNodeViews = new Vector();

		public Registration(ModeController controller, MindMap map) {
			this.controller = (MindMapController) controller;
			mMap = map;
			logger = controller.getFrame().getLogger(this.getClass().getName());
		}

		public void register() {
			if (sCloneIcon == null) {
				sCloneIcon = new ImageIcon(
						controller.getResource("images/clone.png"));
				sOriginalIcon = new ImageIcon(
						controller.getResource("images/clone_original.png"));
				sShowIcon = Resources.getInstance().getBoolProperty(
						FreeMind.RESOURCES_DON_T_SHOW_CLONE_ICONS);
			}
			controller.getActionFactory().registerFilter(this);
			controller.registerNodeSelectionListener(this, false);
		}

		public void deRegister() {
			controller.deregisterNodeSelectionListener(this);
			controller.getActionFactory().deregisterFilter(this);
		}

		public boolean isEnabled(JMenuItem pItem, Action pAction) {
			if (controller == null)
				return false;
			String hookName = ((NodeHookAction) pAction).getHookName();
			if (PLUGIN_NAME.equals(hookName)) {
				// only enabled, if nodes have been copied before.
				Vector mindMapNodes = getMindMapNodes();
				// logger.warning("Nodes " + Tools.listToString(mindMapNodes));
				return !mindMapNodes.isEmpty();
			}
			List selecteds = controller.getSelecteds();
			for (Iterator it = selecteds.iterator(); it.hasNext();) {
				MindMapNode node = (MindMapNode) it.next();
				if (ClonePlugin.getHook(node) != null) {
					return true;
				}
			}
			return false;
		}

		public Vector getMindMapNodes() {
			Vector mindMapNodes = new Vector();
			Transferable clipboardContents = controller.getClipboardContents();
			if (clipboardContents != null) {
				try {
					List transferData = (List) clipboardContents
							.getTransferData(MindMapNodesSelection.copyNodeIdsFlavor);
					for (Iterator it = transferData.iterator(); it.hasNext();) {
						String nodeId = (String) it.next();
						MindMapNode node = controller.getNodeFromID(nodeId);
						mindMapNodes.add(node);
					}
				} catch (Exception e) {
					// e.printStackTrace();
					// freemind.main.Resources.getInstance().logException(e);
				}
			}
			return mindMapNodes;
		}

		public String generateNewCloneId(String pProposedID) {
			return Tools.generateID(pProposedID, mCloneIdsMap, "CLONE_");
		}

		/**
		 * @param pCloneId
		 * @return true, if the pCloneId is new (not already registered)
		 */
		public boolean registerClone(String pCloneId, ClonePlugin pPlugin) {
			boolean vectorPresent = mCloneIdsMap.containsKey(pCloneId);
			HashSet v = getHashSetToCloneId(pCloneId);
			MindMapNode node = pPlugin.getNode();
			for (Iterator it = v.iterator(); it.hasNext();) {
				MindMapNode otherCloneNode = (MindMapNode) it.next();
				ClonePlugin otherClone = ClonePlugin.getHook(otherCloneNode);
				if (otherClone == null) {
					it.remove();
					logger.warning("Found clone node "
							+ controller.getNodeID(otherCloneNode)
							+ " which isn't a clone any more.");
					continue;
				}
				// inform all others
				otherClone.addClone(node);
				// inform this clone about its brothers
				pPlugin.addClone(otherCloneNode);
			}
			v.add(node);
			mClonesMap.put(node, pCloneId);
			selectShadowNode(node, true, node);
			if (!mClonePropertiesMap.containsKey(pCloneId)) {
				mClonePropertiesMap.put(pCloneId, new CloneProperties());
			}
			return !vectorPresent;
		}

		public void deregisterClone(String pCloneId, ClonePlugin pPlugin) {
			HashSet cloneSet = getHashSetToCloneId(pCloneId);
			MindMapNode node = pPlugin.getNode();
			cloneSet.remove(node);
			mClonesMap.remove(node);
			// inform all others
			for (Iterator it = cloneSet.iterator(); it.hasNext();) {
				MindMapNode otherCloneNode = (MindMapNode) it.next();
				ClonePlugin otherClone = ClonePlugin.getHook(otherCloneNode);
				if (otherClone == null) {
					it.remove();
					logger.warning("Found clone node "
							+ controller.getNodeID(otherCloneNode)
							+ " which isn't a clone any more.");
					continue;
				}
				otherClone.removeClone(node);
			}
			if (cloneSet.isEmpty()) {
				// remove entire clone
				mCloneIdsMap.remove(cloneSet);
				mClonePropertiesMap.remove(pCloneId);
			}
		}

		public CloneProperties getCloneProperties(String pCloneId) {
			if (mClonePropertiesMap.containsKey(pCloneId)) {
				return (CloneProperties) mClonePropertiesMap.get(pCloneId);
			}
			throw new IllegalArgumentException(
					"Clone properties not found for " + pCloneId);
		}

		protected HashSet getHashSetToCloneId(String pCloneId) {
			HashSet v = null;
			if (!mCloneIdsMap.containsKey(pCloneId)) {
				v = new HashSet();
				mCloneIdsMap.put(pCloneId, v);
			} else {
				v = (HashSet) mCloneIdsMap.get(pCloneId);
			}
			return v;
		}

		public ActionPair filterAction(ActionPair pair) {
			// shortcut for no clones for speed up.
			if (mCloneIdsMap.isEmpty()) {
				return pair;
			}
			XmlAction doAction = pair.getDoAction();
			doAction = cloneAction(doAction);
			pair.setDoAction(doAction);
			return pair;
		}

		private XmlAction cloneAction(XmlAction doAction) {
			logger.fine("Found do action: " + doAction.getClass().getName());
			if (doAction instanceof NodeAction) {
				NodeAction nodeAction = (NodeAction) doAction;
				MindMapNode node = controller.getNodeFromID(nodeAction
						.getNode());
				// check for clone or original?
				doAction = cloneAction(nodeAction, node);
			} else {
				if (doAction instanceof CompoundAction) {
					CompoundAction compoundAction = (CompoundAction) doAction;
					List choiceList = compoundAction.getListChoiceList();
					int index = 0;
					for (Iterator it = choiceList.iterator(); it.hasNext();) {
						XmlAction subAction = (XmlAction) it.next();
						subAction = cloneAction(subAction);
						compoundAction.setAtChoice(index, subAction);
						index++;
					}
				}
			}
			return doAction;
		}

		private XmlAction cloneAction(NodeAction nodeAction, MindMapNode node) {
			List correspondingNodes = getCorrespondingNodes(nodeAction, node);
			if (correspondingNodes.isEmpty()) {
				return nodeAction;
			}
			// create new action:
			CompoundAction compound = new CompoundAction();
			compound.addChoice(nodeAction);
			for (Iterator it = correspondingNodes.iterator(); it.hasNext();) {
				Tools.MindMapNodePair pair = (Tools.MindMapNodePair) it.next();
				getNewCompoundAction(nodeAction, pair, compound);
			}
			return compound;
		}

		private void getNewCompoundAction(NodeAction nodeAction,
				Tools.MindMapNodePair correspondingNodePair,
				CompoundAction compound) {
			// deep copy:
			NodeAction copiedNodeAction = (NodeAction) Tools
					.deepCopy(nodeAction);
			// special cases:
			if (copiedNodeAction instanceof MoveNodesAction) {
				MoveNodesAction moveAction = (MoveNodesAction) copiedNodeAction;
				for (int i = 0; i < moveAction.getListNodeListMemberList()
						.size(); i++) {
					NodeListMember member = moveAction.getNodeListMember(i);
					changeNodeListMember(correspondingNodePair, moveAction,
							member);
				}
			}
			if (copiedNodeAction instanceof HookNodeAction) {
				HookNodeAction hookAction = (HookNodeAction) copiedNodeAction;
				for (int i = 0; i < hookAction.getListNodeListMemberList()
						.size(); i++) {
					NodeListMember member = hookAction.getNodeListMember(i);
					changeNodeListMember(correspondingNodePair, hookAction,
							member);
				}
			}
			if (copiedNodeAction instanceof NewNodeAction) {
				NewNodeAction newNodeAction = (NewNodeAction) copiedNodeAction;
				String newId = mMap.getLinkRegistry().generateUniqueID(null);
				newNodeAction.setNewId(newId);
			}
			copiedNodeAction.setNode(controller.getNodeID(correspondingNodePair
					.getCorresponding()));
			if (copiedNodeAction instanceof PasteNodeAction) {
				/*
				 * difficult thing here: if something is pasted, the paste
				 * action itself contains the node ids of the paste. The first
				 * pasted action will get that node id. This should be the
				 * corresponding node itself. This presumably corrects a bug
				 * that the selection on move actions is changing.
				 */
				compound.addChoice(copiedNodeAction);
			} else {
				compound.addAtChoice(0, copiedNodeAction);
			}
		}

		public void changeNodeListMember(
				Tools.MindMapNodePair correspondingNodePair,
				NodeAction pAction, NodeListMember member) {
			NodeAdapter memberNode = controller.getNodeFromID(member.getNode());
			List correspondingMoveNodes = getCorrespondingNodes(pAction,
					memberNode);
			if (!correspondingMoveNodes.isEmpty()) {
				// search for this clone:
				for (Iterator it = correspondingMoveNodes.iterator(); it
						.hasNext();) {
					Tools.MindMapNodePair pair = (Tools.MindMapNodePair) it
							.next();
					if (pair.getCloneNode() == correspondingNodePair
							.getCloneNode()) {
						// found:
						member.setNode(controller.getNodeID(pair
								.getCorresponding()));
						break;
					}
				}
			}
		}

		/**
		 * Method takes into account, that some actions are different.
		 * 
		 * @param nodeAction
		 * @param node
		 * @return
		 */
		public List getCorrespondingNodes(NodeAction nodeAction,
				MindMapNode node) {
			boolean startWithParent = false;
			// Behavior for complete cloning.
			if (mClonesMap.containsKey(node)) {
				String cloneId = (String) mClonesMap.get(node);
				if (getCloneProperties(cloneId).isCloneItself()) {
					// Behavior for complete cloning
					if (nodeAction instanceof MoveNodesAction
							|| nodeAction instanceof MoveNodeXmlAction
							|| nodeAction instanceof DeleteNodeAction
							|| nodeAction instanceof CutNodeAction) {
						// ok, there is an action for a clone itself. be
						// careful:
						// clone only, if parents are clones:
						startWithParent = true;
					} else if (nodeAction instanceof PasteNodeAction) {
						PasteNodeAction pna = (PasteNodeAction) nodeAction;
						if (pna.getAsSibling()) {
							// sibling means, that the paste goes below the
							// clone.
							// skip.
							startWithParent = true;
						} else {
							// here, the action changes the children, thus, they
							// are
							// subject to cloning.
						}
					} else if (nodeAction instanceof UndoPasteNodeAction) {
						UndoPasteNodeAction pna = (UndoPasteNodeAction) nodeAction;
						if (pna.getAsSibling()) {
							// sibling means, that the paste goes below the
							// clone.
							// skip.
							startWithParent = true;
						} else {
							// here, the action changes the children, thus, they
							// are
							// subject to cloning.
						}
					}
				} else {
					// Behavior for children cloning only
					/*
					 * new node action belongs to the children, so clone it,
					 * even, when node is the clone itself.
					 */
					if (nodeAction instanceof NewNodeAction) {
						// here, the action changes the children, thus, they are
						// subject to cloning.
					} else if (nodeAction instanceof PasteNodeAction) {
						PasteNodeAction pna = (PasteNodeAction) nodeAction;
						if (pna.getAsSibling()) {
							// sibling means, that the paste goes below the
							// clone.
							// skip.
							startWithParent = true;
						} else {
							// here, the action changes the children, thus, they
							// are
							// subject to cloning.
						}
					} else if (nodeAction instanceof UndoPasteNodeAction) {
						UndoPasteNodeAction pna = (UndoPasteNodeAction) nodeAction;
						if (pna.getAsSibling()) {
							// sibling means, that the paste goes below the
							// clone.
							// skip.
							startWithParent = true;
						} else {
							// here, the action changes the children, thus, they
							// are
							// subject to cloning.
						}
					} else {
						// ok, there is an action for a clone itself. be
						// careful:
						// clone only, if parents are clones:
						startWithParent = true;
					}
				}
			}
			List/* MindMapNodePair */correspondingNodes = getCorrespondingNodes(
					node, startWithParent);
			return correspondingNodes;
		}

		/**
		 * This is the main method here. It returns to a given node its cloned
		 * nodes on the other side.
		 * 
		 * @param pNode
		 *            is checked to be son of one of the clones/original.
		 * @param pStartWithParent
		 *            Sometimes, it is relevant, if only one of the parents is a
		 *            clone, eg. for all actions, that affect the clone itself,
		 *            thus not need to be cloned, but perhaps the clone is
		 *            itself a node inside of another clone!
		 * @return a list of {@link MindMapNodePair}s where the first is the
		 *         corresponding node and the second is the clone. If the return
		 *         value is empty, the node isn't son of any.
		 */
		public List getCorrespondingNodes(MindMapNode pNode,
				boolean pStartWithParent) {
			// in case, no clones are present, this method returns very fast.
			if (mClonesMap.isEmpty()) {
				return Collections.EMPTY_LIST;
			}
			MindMapNode clone;
			{
				MindMapNode child;
				// code doubling to speed up. First check for a clone on the way
				// to root.
				if (pStartWithParent) {
					child = pNode.getParentNode();
				} else {
					child = pNode;
				}
				while (!mClonesMap.containsKey(child)) {
					if (child.isRoot()) {
						// nothing found!
						return Collections.EMPTY_LIST;
					}
					child = child.getParentNode();
				}
				clone = child;
			}
			MindMapNode child;
			// now, there is a clone on the way. Collect the indices.
			Vector indexVector = new Vector();
			if (pStartWithParent) {
				addNodePosition(indexVector, pNode);
				child = pNode.getParentNode();
			} else {
				child = pNode;
			}
			while (clone != child) {
				addNodePosition(indexVector, child);
				child = child.getParentNode();
			}
			Vector returnValue = new Vector();
			MindMapNode originalNode = child;
			HashSet targets = (HashSet) mCloneIdsMap.get(mClonesMap.get(child));
			CloneLoop: for (Iterator itClone = targets.iterator(); itClone
					.hasNext();) {
				MindMapNode cloneNode = (MindMapNode) itClone.next();
				MindMapNode target = cloneNode;
				if (cloneNode == originalNode)
					continue;
				for (int i = indexVector.size() - 1; i >= 0; --i) {
					int index = ((Integer) indexVector.get(i)).intValue();
					if (target.getChildCount() <= index) {
						logger.warning("Index " + index
								+ " in other tree not found from "
								+ printNodeIds(targets) + " originating from "
								+ printNodeId(cloneNode) + " start at parent "
								+ pStartWithParent);
						// with crossed fingers.
						continue CloneLoop;
					}
					target = (MindMapNode) target.getChildAt(index);
				}
				// logger.fine("Found corresponding node " + printNodeId(target)
				// + " on clone " + printNodeId(cloneNode));
				returnValue.add(new Tools.MindMapNodePair(target, cloneNode));
			}
			return returnValue;
		}

		private void addNodePosition(Vector indexVector, MindMapNode child) {
			indexVector.add(new Integer(child.getParentNode().getChildPosition(
					child)));
		}

		/**
		 * @param pCloneNode
		 * @return
		 */
		private String printNodeId(MindMapNode pCloneNode) {
			try {
				return controller.getNodeID(pCloneNode) + ": '"
						+ (pCloneNode.getShortText(controller)) + "'";
			} catch (Exception e) {
				return "NOT FOUND: '" + pCloneNode + "'";
			}
		}

		/**
		 * @param pClones
		 * @return
		 */
		private String printNodeIds(HashSet pClones) {
			Vector strings = new Vector();
			for (Iterator it = pClones.iterator(); it.hasNext();) {
				MindMapNode pluginNode = (MindMapNode) it.next();
				strings.add(printNodeId(pluginNode));
			}
			return Tools.listToString(strings);
		}

		/**
		 * Is sent when a node is selected.
		 */
		public void onFocusNode(NodeView node) {
			markShadowNode(node, true);
		}

		/**
		 * Is sent when a node is deselected.
		 */
		public void onLostFocusNode(NodeView node) {
			markShadowNode(node, false);
		}

		public void markShadowNode(NodeView pNode, boolean pEnableShadow) {
			// at startup, the node is null.
			if (pNode == null || pNode.getModel() == null) {
				return;
			}
			if (!pEnableShadow) {
				if (!mLastMarkedNodeViews.isEmpty()) {
					for (Iterator it = mLastMarkedNodeViews.iterator(); it
							.hasNext();) {
						MindMapNode node = (MindMapNode) it.next();
						if (mClonesMap.containsKey(node)) {
							setIcon(node, sOriginalIcon);
						} else {
							setIcon(node, null);
						}
					}
					mLastMarkedNodeViews.clear();
				}
			} else {
				markShadowNode(pNode.getModel(), pEnableShadow);
			}
		}

		public void markShadowNode(MindMapNode model, boolean pEnableShadow) {
			mLastMarkedNodeViews.clear();
			try {
				List/* pair of MindMapNodePair */shadowNodes = getCorrespondingNodes(
						model, false);
				for (Iterator it = shadowNodes.iterator(); it.hasNext();) {
					Tools.MindMapNodePair shadowNode = (Tools.MindMapNodePair) it
							.next();
					MindMapNode correspondingNode = shadowNode
							.getCorresponding();
					mLastMarkedNodeViews.add(correspondingNode);
					selectShadowNode(correspondingNode, pEnableShadow,
							shadowNode.getCloneNode());
				}
			} catch (IllegalArgumentException e) {
				freemind.main.Resources.getInstance().logException(e);
			}
		}

		private void selectShadowNode(MindMapNode node, boolean pEnableShadow,
				MindMapNode pCloneNode) {
			if (!sShowIcon) {
				return;
			}
			while (node != null) {
				ImageIcon i = pEnableShadow ? sCloneIcon : null;
				if (node == pCloneNode) {
					i = sOriginalIcon;
				}
				setIcon(node, i);
				if (node == pCloneNode)
					break;
				node = node.getParentNode();
				// comment this out to get a complete marked path to the root of
				// the
				// clones.
				break;
			}
		}

		public void setIcon(MindMapNode node, ImageIcon i) {
			node.setStateIcon(ClonePlugin.PLUGIN_LABEL, i);
			controller.nodeRefresh(node);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * freemind.modes.ModeController.NodeSelectionListener#onSelectionChange
		 * (freemind.modes.MindMapNode, boolean)
		 */
		public void onSelectionChange(NodeView pNode, boolean pIsSelected) {
		}

		public void onUpdateNodeHook(MindMapNode pNode) {

		}

		public void onSaveNode(MindMapNode pNode) {

		}

	}

}
