/* * Freeplane - mind map editor * Copyright (C) 2008 Dimitry Polivaev * * This file author is Dimitry Polivaev * * 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, see <http://www.gnu.org/licenses/>. */ package org.freeplane.features.mode; import java.awt.event.ActionEvent; import java.io.IOException; import java.lang.reflect.Method; import java.util.Collection; import java.util.EnumSet; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import org.freeplane.core.extension.IExtension; import org.freeplane.core.io.IElementDOMHandler; import org.freeplane.core.io.IElementHandler; import org.freeplane.core.io.IExtensionElementWriter; import org.freeplane.core.io.ITreeWriter; import org.freeplane.core.ui.AFreeplaneAction; import org.freeplane.core.ui.SelectableAction; import org.freeplane.core.undo.IActor; import org.freeplane.features.map.IMapSelection; import org.freeplane.features.map.MapController; import org.freeplane.features.map.NodeModel; import org.freeplane.n3.nanoxml.XMLElement; public abstract class PersistentNodeHook { static private Set<Class<? extends IExtension>> mapExtensionClasses = new HashSet<Class<? extends IExtension>>(); public static boolean isMapExtension(final Class<? extends IExtension> clazz) { return mapExtensionClasses.contains(clazz); } public abstract class HookAction extends AFreeplaneAction { private static final long serialVersionUID = 1L; public HookAction(final String key) { super(key); } public void actionPerformed(final ActionEvent e) { undoableSetHook(!isActiveForSelection()); } } @SelectableAction(checkOnNodeChange = true) protected class SelectableHookAction extends HookAction { private static final long serialVersionUID = 1L; public SelectableHookAction(final String key) { super(key); // System.out.println("SelectableHookAction " + key); } @Override public void actionPerformed(final ActionEvent e) { setSelected(!isActiveForSelection()); super.actionPerformed(e); setSelected(isActiveForSelection()); } @Override public void setSelected() { setSelected(isActiveForSelection()); } } @SelectableAction(checkOnNodeChange = true) protected class SelectableEnumAction extends HookAction { private static final long serialVersionUID = 1L; final Enum<?> value; public SelectableEnumAction(String key, final Enum<?> value) { super(key + "." + String.valueOf(value)); this.value = value; } @Override public void actionPerformed(final ActionEvent e) { undoableSetHook(false); if(value != null) undoableSetHook((IExtension)value); setSelected(true); } @Override public void setSelected() { setSelected(isActiveForSelection(value)); } } private final class ToggleHookActor implements IActor { IExtension extension; private final NodeModel node; private ToggleHookActor(final NodeModel node, final IExtension extension) { this.node = node; this.extension = extension != null ? extension : node.getExtension(getExtensionClass()); } public void act() { extension = toggle(node, extension); } public String getDescription() { return getHookName(); } public void undo() { act(); } } protected class XmlReader implements IElementDOMHandler { public Object createElement(final Object parent, final String tag, final XMLElement attributes) { if (attributes == null) { return null; } if (!getHookName().equals(attributes.getAttribute("NAME", null))) { return null; } return parent; } public void endElement(final Object parent, final String tag, final Object userObject, final XMLElement lastBuiltElement) { if (getHookAnnotation().onceForMap()) { final XMLElement parentNodeElement = lastBuiltElement.getParent().getParent(); if (parentNodeElement == null || !parentNodeElement.getName().equals("map")) { return; } } final NodeModel node = (NodeModel) userObject; if (node.getExtension(getExtensionClass()) != null) { return; } final IExtension extension = createExtension(node, lastBuiltElement); if (extension == null) { return; } add(node, extension); } } protected class XmlWriter implements IExtensionElementWriter { public void writeContent(final ITreeWriter writer, final Object object, final IExtension extension) throws IOException { final XMLElement element = new XMLElement("hook"); try { saveExtension(extension, element); writer.addElement(null, element); } catch (final Exception e) { e.printStackTrace(); } } } @SuppressWarnings({ "unchecked", "rawtypes" }) public PersistentNodeHook() { super(); final Class<? extends IExtension> extensionClass = getExtensionClass(); if (getHookAnnotation().onceForMap()) { mapExtensionClasses.add(extensionClass); } // this.modeController = modeController; // controller = modeController.getController(); final ModeController modeController = Controller.getCurrentModeController(); if (modeController.getModeName().equals("MindMap")) { if(extensionClass.isEnum()){ Class<Enum> enumClass = (Class<Enum>) (Class<?>)extensionClass; EnumSet<? extends Enum<?>> all= EnumSet.allOf(enumClass); for(Enum e : all){ registerAction(new SelectableEnumAction(getClass().getSimpleName() + "Action", e)); } registerAction(new SelectableEnumAction(getClass().getSimpleName() + "Action", null)); } else{ final HookAction selectableHookAction = createHookAction(); if (selectableHookAction != null) { registerAction(selectableHookAction); } } } final MapController mapController = modeController.getMapController(); mapController.getReadManager().addElementHandler("hook", createXmlReader()); final IExtensionElementWriter xmlWriter = createXmlWriter(); if (xmlWriter != null) { mapController.getWriteManager().addExtensionElementWriter(extensionClass, xmlWriter); } if (this instanceof IExtension) { // do not use getExtensionClass() here since in several subclasses getExtensionClass() returns a // different class than getClass() modeController.addExtension((Class<? extends IExtension>) getClass(), (IExtension) this); } } protected void add(final NodeModel node, final IExtension extension) { assert (getExtensionClass().equals(extension.getClass())); node.addExtension(extension); } protected IExtension createExtension(final NodeModel node) { return createExtension(node, null); } protected IExtension createExtension(final NodeModel node, final XMLElement element){ try { final Class<? extends IExtension> extensionClass = getExtensionClass(); if(extensionClass.isEnum()){ final String value = element.getAttribute("VALUE"); final Method factory = extensionClass.getMethod("valueOf", String.class); return (IExtension)factory.invoke(null, value); } return extensionClass.newInstance(); } catch (SecurityException e) { e.printStackTrace(); } catch (Exception e) { } return null; } protected HookAction createHookAction() { return new SelectableHookAction(getClass().getSimpleName() + "Action"); } protected IElementHandler createXmlReader() { return new XmlReader(); } protected IExtensionElementWriter createXmlWriter() { return new XmlWriter(); } @SuppressWarnings("unchecked") protected Class<? extends IExtension> getExtensionClass() { return (Class<? extends IExtension>) getClass(); } private NodeHookDescriptor getHookAnnotation() { final NodeHookDescriptor annotation = getClass().getAnnotation(NodeHookDescriptor.class); return annotation; } protected String getHookName() { return getHookAnnotation().hookName(); } public IExtension getMapHook() { final NodeModel rootNode = Controller.getCurrentController().getMap().getRootNode(); return rootNode.getExtension(getExtensionClass()); } protected NodeModel[] getNodes() { if (getHookAnnotation().onceForMap()) { return getRootNode(); } return getSelectedNodes(); } protected NodeModel[] getRootNode() { final NodeModel[] nodes = new NodeModel[1]; nodes[0] = Controller.getCurrentController().getMap().getRootNode(); return nodes; } protected NodeModel[] getSelectedNodes() { //DOCEAR: if nothing is selected (map is opened in background for updating) IMapSelection iMapSelection = Controller.getCurrentController().getSelection(); if (iMapSelection == null) { return null; } final Collection<NodeModel> selection = Controller.getCurrentController().getSelection().getSelection(); final int size = selection.size(); final NodeModel[] nodes = new NodeModel[size]; final Iterator<NodeModel> iterator = selection.iterator(); int i = 0; while (iterator.hasNext()) { nodes[i++] = iterator.next(); } return nodes; } public boolean isActive(final NodeModel nodeModel) { if (!nodeModel.isRoot() && getHookAnnotation().onceForMap()) { return isActive(nodeModel.getMap().getRootNode()); } return nodeModel.containsExtension(getExtensionClass()); } public IExtension getExtension(final NodeModel nodeModel) { if (!nodeModel.isRoot() && getHookAnnotation().onceForMap()) { return getExtension(nodeModel.getMap().getRootNode()); } return nodeModel.getExtension(getExtensionClass()); } protected boolean isActiveForSelection() { final NodeModel[] nodes = getNodes(); //Docear: if nothing is selected (map is opened in background for updating) if (nodes == null) { return false; } for (int i = 0; i < nodes.length; i++) { final NodeModel nodeModel = nodes[i]; if (nodeModel.containsExtension(getExtensionClass())) { return true; } } return false; } private boolean isActiveForSelection(Enum<?> value) { final NodeModel[] nodes = getNodes(); for (int i = 0; i < nodes.length; i++) { final NodeModel nodeModel = nodes[i]; final IExtension nodeValue = nodeModel.getExtension(getExtensionClass()); if (value == null && nodeValue != null || value != null && !value.equals(nodeValue)) { return false; } } return true; } protected void registerAction(final AFreeplaneAction action) { Controller.getCurrentModeController().addAction(action); } protected void remove(final NodeModel node, final IExtension extension) { node.removeExtension(extension); } protected void saveExtension(final IExtension extension, final XMLElement element) { element.setAttribute("NAME", getHookName()); if(extension instanceof Enum){ element.setAttribute("VALUE", extension.toString()); } } public void undoableActivateHook(final NodeModel node, final IExtension extension) { undoableToggleHook(node, extension); } public void undoableDeactivateHook(final NodeModel node) { final IExtension extension = node.getExtension(getExtensionClass()); if (extension != null) { undoableToggleHook(node, extension); } } public void undoableSetHook(final boolean enable) { final NodeModel[] nodes = getNodes(); for (int i = 0; i < nodes.length; i++) { final NodeModel node = nodes[i]; if (node.containsExtension(getExtensionClass()) != enable) { undoableToggleHook(node); } } } public void undoableSetHook(final IExtension extension) { final NodeModel[] nodes = getNodes(); for (int i = 0; i < nodes.length; i++) { final NodeModel node = nodes[i]; if (extension != null || node.containsExtension(getExtensionClass())) { undoableToggleHook(node, extension); } } } public void undoableToggleHook(final NodeModel node) { undoableToggleHook(node, node.getExtension(getExtensionClass())); } public void undoableToggleHook(final NodeModel node, final IExtension extension) { final IActor actor = new ToggleHookActor(node, extension); Controller.getCurrentModeController().execute(actor, node.getMap()); } protected IExtension toggle(final NodeModel node, IExtension extension) { final IExtension before; final IExtension after; if (extension != null && node.containsExtension(extension.getClass())) { before = extension; after = null; remove(node, extension); } else { if (extension == null) { extension = createExtension(node); } if (extension != null) { add(node, extension); } before = null; after = extension; } Controller.getCurrentModeController().getMapController() .nodeChanged(node, getExtensionClass(), before, after); return extension; } public static void removeMapExtensions(NodeModel node) { final IExtension[] extensionArray = node.getExtensions().values().toArray(new IExtension[]{}); for(IExtension extension : extensionArray){ if(PersistentNodeHook.isMapExtension(extension.getClass())){ node.removeExtension(extension); } } } }