/*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.EventQueue;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;
import accessories.plugins.ClonePasteAction.CloneProperties;
import accessories.plugins.ClonePasteAction.ClonePropertiesObserver;
import accessories.plugins.ClonePasteAction.Registration;
import freemind.extensions.PermanentNodeHook;
import freemind.main.Tools;
import freemind.main.XMLElement;
import freemind.modes.MindMapNode;
import freemind.modes.ModeController.NodeLifetimeListener;
import freemind.modes.mindmapmode.hooks.PermanentMindMapNodeHookAdapter;
public class ClonePlugin extends PermanentMindMapNodeHookAdapter implements
NodeLifetimeListener, ClonePropertiesObserver {
public static final String CLONE_ITSELF_FALSE = "false";
public static final String CLONE_ITSELF_TRUE = "true";
public static final String PLUGIN_LABEL = "accessories/plugins/ClonePlugin.properties";
public static final String XML_STORAGE_CLONES = "CLONE_IDS";
public static final String XML_STORAGE_CLONE_ID = "CLONE_ID";
public static final String XML_STORAGE_CLONE_ITSELF = "CLONE_ITSELF";
/**
* This is the master list. {@link ClonePlugin#mCloneNodes mCloneNodes} is
* derived from it. It contains id strings.
*/
private HashSet mCloneNodeIds;
/**
* Includes the original node. This is a cached list with the MindMapNodes
* belonging to the {@link ClonePlugin#mCloneNodeIds mCloneNodeIds}.
*/
private HashSet mCloneNodes;
private String mCloneId;
private boolean mDisabled = false;
private Boolean mCloneItself = null;
private CloneProperties mClonePropertiesHolder;
public ClonePlugin() {
}
public void invoke(MindMapNode node) {
super.invoke(node);
registerPlugin();
}
public void addClone(MindMapNode cloneNode) {
mCloneNodeIds.add(getMindMapController().getNodeID(cloneNode));
clearCloneCache();
}
public void clearCloneCache() {
mCloneNodes = new HashSet();
}
private void disablePlugin() {
mDisabled = true;
getMindMapController().getController().errorMessage(
getMindMapController().getText("clone_plugin_impossible"));
EventQueue.invokeLater(new Runnable() {
public void run() {
if (getHook(getNode()) != null) {
removeHook();
}
}
});
}
/**
* double add = remove.
*
*/
protected void removeHook() {
// first deactivate cloning for this node (otherwise, the deactivation will be cloned, too!)
deregisterCloning();
Vector selecteds = Tools.getVectorWithSingleElement(getNode());
getMindMapController()
.addHook(getNode(), selecteds, PLUGIN_LABEL, null);
}
public void save(XMLElement xml) {
super.save(xml);
HashMap values = new HashMap();
values.put(XML_STORAGE_CLONES, getCloneIdsAsString());
values.put(XML_STORAGE_CLONE_ID, mCloneId);
String cloneItselfValue = getCloneItselfValue();
values.put(XML_STORAGE_CLONE_ITSELF, cloneItselfValue);
logger.finest("Saved mCloneItself to " + cloneItselfValue);
saveNameValuePairs(values, xml);
logger.fine("Saved clone plugin");
}
protected String getCloneItselfValue() {
return mClonePropertiesHolder
.isCloneItself() ? CLONE_ITSELF_TRUE
: CLONE_ITSELF_FALSE;
}
public String getCloneIdsAsString() {
StringBuffer cloneIds = new StringBuffer();
for (Iterator it = mCloneNodeIds.iterator(); it.hasNext();) {
String cloneId = (String) it.next();
cloneIds.append(cloneId);
cloneIds.append(",");
}
return cloneIds.toString();
}
public void loadFrom(XMLElement child) {
super.loadFrom(child);
mCloneNodes = null;
mCloneNodeIds = new HashSet();
HashMap values = loadNameValuePairs(child);
String cloneIds = (String) values.get(XML_STORAGE_CLONES);
if (cloneIds != null) {
StringTokenizer st = new StringTokenizer(cloneIds, ",");
while (st.hasMoreTokens()) {
String cloneId = st.nextToken();
mCloneNodeIds.add(cloneId);
}
}
mCloneId = (String) values.get(XML_STORAGE_CLONE_ID);
if (values.containsKey(XML_STORAGE_CLONE_ITSELF)) {
mCloneItself = Boolean.valueOf(Tools.safeEquals(CLONE_ITSELF_TRUE,
values.get(XML_STORAGE_CLONE_ITSELF)));
} else {
mCloneItself = Boolean.FALSE;
}
logger.finest("Loaded mCloneItself to " + mCloneItself);
}
public void shutdownMapHook() {
logger.fine("Shutdown of clones");
deregisterPlugin();
super.shutdownMapHook();
}
private void registerPlugin() {
if (mDisabled)
return;
/*
* test for error cases: - orig is child of clone now - if clone is a
* child of clone, this is here not reachable, as the plugin remains
* active and is not newly invoked. Hmm, what to do?
*/
MindMapNode originalNode = getNode();
HashSet cloneNodes = getCloneNodes();
logger.fine("Invoke shadow class with orig: "
+ printNodeId(originalNode) + " and clones "
+ printNodeIds(cloneNodes));
// check for error case that clones are descendant of one another.
for (Iterator it = cloneNodes.iterator(); it.hasNext();) {
MindMapNode cloneNode = (MindMapNode) it.next();
if (originalNode != null && originalNode.isDescendantOf(cloneNode)) {
disablePlugin();
return;
}
}
getMindMapController().registerNodeLifetimeListener(this, false);
Registration registration = getRegistration();
if (mCloneId == null) {
// hmm, it seems, that I am the first. Let's generate an id:
mCloneId = registration.generateNewCloneId(null);
}
registration.registerClone(mCloneId, this);
// the clone list contains itself, too.
addClone(getNode());
mClonePropertiesHolder = registration
.getCloneProperties(mCloneId);
if (mCloneItself != null) {
mClonePropertiesHolder.setCloneItself(mCloneItself.booleanValue());
}
mClonePropertiesHolder.registerObserver(this);
}
protected Registration getRegistration() {
return (Registration) getPluginBaseClass();
}
private void deregisterPlugin() {
mClonePropertiesHolder.deregisterObserver(this);
deregisterCloning();
getMindMapController().deregisterNodeLifetimeListener(this);
// remove icon
getNode().setStateIcon(getName(), null);
getMindMapController().nodeRefresh(getNode());
}
protected void deregisterCloning() {
getRegistration().deregisterClone(mCloneId, this);
}
public void onCreateNodeHook(MindMapNode node) {
HashSet cloneNodes = getCloneNodes();
for (Iterator it = cloneNodes.iterator(); it.hasNext();) {
MindMapNode clone = (MindMapNode) it.next();
for (Iterator it2 = cloneNodes.iterator(); it2.hasNext();) {
MindMapNode clone2 = (MindMapNode) it2.next();
if (clone != clone2) {
checkForChainError(clone, node, clone2);
}
}
}
}
public void onPreDeleteNode(MindMapNode node) {
}
public void onPostDeleteNode(MindMapNode node, MindMapNode parent) {
}
/**
* @return a list of {@link MindMapNode}s including the original node!
*/
HashSet getCloneNodes() {
// is list up to date?
if (mCloneNodes != null) {
for (Iterator it = mCloneNodes.iterator(); it.hasNext();) {
MindMapNode cloneNode = (MindMapNode) it.next();
if (cloneNode.getParentNode() == null) {
clearCloneCache();
}
}
} else {
clearCloneCache();
}
if (mCloneNodes.isEmpty()) {
mCloneNodes.add(getNode());
for (Iterator it = mCloneNodeIds.iterator(); it.hasNext();) {
String cloneId = (String) it.next();
try {
mCloneNodes.add(getMindMapController().getNodeFromID(
cloneId));
} catch (IllegalArgumentException e) {
// freemind.main.Resources.getInstance().logException(e);
it.remove();
}
}
}
return mCloneNodes;
}
/**
* @param pCloneNode
* @return
*/
private String printNodeId(MindMapNode pCloneNode) {
try {
return getMindMapController().getNodeID(pCloneNode) + ": '"
+ (pCloneNode.getShortText(getMindMapController())) + "'";
} catch (Exception e) {
return "NOT FOUND: '" + pCloneNode + "'";
}
}
/**
* @param pTargets
* @return
*/
private String printNodeIds(Collection pTargets) {
Vector strings = new Vector();
for (Iterator it = pTargets.iterator(); it.hasNext();) {
MindMapNode node = (MindMapNode) it.next();
strings.add(printNodeId(node));
}
return "" + strings;
}
private void checkForChainError(MindMapNode originalNode, MindMapNode node,
MindMapNode cloneNode) {
if (cloneNode.isDescendantOfOrEqual(node)
&& node.isDescendantOfOrEqual(originalNode)) {
// orig -> .... -> node -> .. -> clone
disablePlugin();
}
}
public void removeClone(MindMapNode pCloneNode) {
String nodeID = getMindMapController().getNodeID(pCloneNode);
mCloneNodeIds.remove(nodeID);
clearCloneCache();
if (mCloneNodeIds.isEmpty()
|| (mCloneNodeIds.size() == 1 && mCloneNodeIds
.contains(getNodeId()))) {
// remove myself
logger.info("I'm the last clone " + nodeID);
removeHook();
}
}
public static ClonePlugin getHook(MindMapNode originalNode) {
if (originalNode == null) {
return null;
}
for (Iterator it2 = originalNode.getActivatedHooks().iterator(); it2
.hasNext();) {
PermanentNodeHook hook = (PermanentNodeHook) it2.next();
if (hook instanceof ClonePlugin) {
ClonePlugin cloneHook = (ClonePlugin) hook;
return cloneHook;
}
}
return null;
}
/*
* (non-Javadoc)
*
* @see
* freemind.extensions.PermanentNodeHookAdapter#processUnfinishedLinks()
*/
public void processUnfinishedLinks() {
super.processUnfinishedLinks();
if (mDisabled)
return;
HashSet cloneNodes = getCloneNodes();
// activate other clones, if not already activated.
for (Iterator it = cloneNodes.iterator(); it.hasNext();) {
MindMapNode cloneNode = (MindMapNode) it.next();
ClonePlugin hook = getHook(cloneNode);
if (hook == null && cloneNode != null) {
// add hook to clone partner:
Vector selecteds = Tools.getVectorWithSingleElement(cloneNode);
// Transport the data to the plugin, as this method calls
// invoke.
Properties hookProperties = new Properties();
hookProperties.setProperty(XML_STORAGE_CLONE_ID, mCloneId);
hookProperties.setProperty(XML_STORAGE_CLONES,
getCloneIdsAsString());
hookProperties.setProperty(XML_STORAGE_CLONE_ITSELF, getCloneItselfValue());
getMindMapController().addHook(cloneNode, selecteds,
PLUGIN_LABEL, hookProperties);
}
}
}
/*
* (non-Javadoc)
*
* @see accessories.plugins.ClonePasteAction.ClonePropertiesObserver#
* propertiesChanged(accessories.plugins.ClonePasteAction.CloneProperties)
*/
public void propertiesChanged(CloneProperties pCloneProperties) {
mCloneItself = Boolean.valueOf(pCloneProperties.isCloneItself());
}
public String getCloneId() {
return mCloneId;
}
}