/* * Freeplane - mind map editor * Copyright (C) 2008 Joerg Mueller, Daniel Polansky, Christian Foltin, Dimitry Polivaev * * This file is modified by Dimitry Polivaev in 2008. * * 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.Component; import java.awt.Container; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import org.freeplane.core.extension.ExtensionContainer; import org.freeplane.core.extension.IExtension; import org.freeplane.core.ui.AFreeplaneAction; import org.freeplane.core.ui.IMenuContributor; import org.freeplane.core.ui.IUserInputListenerFactory; import org.freeplane.core.ui.MenuBuilder; import org.freeplane.core.undo.IActor; import org.freeplane.core.undo.IUndoHandler; import org.freeplane.features.map.IExtensionCopier; import org.freeplane.features.map.ITooltipProvider; import org.freeplane.features.map.MapController; import org.freeplane.features.map.MapModel; import org.freeplane.features.map.NodeModel; import org.freeplane.features.map.mindmapmode.DocuMapAttribute; import org.freeplane.features.ui.INodeViewLifeCycleListener; /** * Derive from this class to implement the Controller for your mode. Overload * the methods you need for your data model, or use the defaults. There are some * default Actions you may want to use for easy editing of your model. Take * MindMapController as a sample. */ public class ModeController extends AController { // // final private Controller controller; private final ExtensionContainer extensionContainer; private final Collection<IExtensionCopier> copiers; private boolean isBlocked = false; private MapController mapController; final private Map<Integer, ITooltipProvider> toolTip = new TreeMap<Integer, ITooltipProvider>(); final private List<IMenuContributor> menuContributors = new LinkedList<IMenuContributor>(); /** * The model, this controller belongs to. It may be null, if it is the * default controller that does not show a map. */ final private List<INodeViewLifeCycleListener> nodeViewListeners = new LinkedList<INodeViewLifeCycleListener>(); /** * Take care! This listener is also used for modelpopups (as for graphical * links). */ private IUserInputListenerFactory userInputListenerFactory; final private Controller controller; /** * Instantiation order: first me and then the model. */ public ModeController(final Controller controller) { this.controller = controller; extensionContainer = new ExtensionContainer(new HashMap<Class<? extends IExtension>, IExtension>()); copiers = new LinkedList<IExtensionCopier>(); } @Override public void addAction(final AFreeplaneAction action) { super.addAction(action); if (mapController != null) { mapController.addListenerForAction(action); } } public void addExtension(final Class<? extends IExtension> clazz, final IExtension extension) { extensionContainer.addExtension(clazz, extension); } public void registerExtensionCopier(final IExtensionCopier copier) { copiers.add(copier); } public void unregisterExtensionCopier(final IExtensionCopier copier) { copiers.remove(copier); } public void copyExtensions(final Object key, final NodeModel from, final NodeModel to) { for (final IExtensionCopier copier : copiers) { copier.copy(key, from, to); } } public void undoableCopyExtensions(final Object key, final NodeModel from, final NodeModel to) { final MapModel map = to.getMap(); if (map == null) { copyExtensions(key, from, to); return; } final IUndoHandler undoHandler = (IUndoHandler) map.getExtension(IUndoHandler.class); if (undoHandler == null) { copyExtensions(key, from, to); return; } final NodeModel backup = new NodeModel(null); copyExtensions(key, to, backup); final IActor actor = new IActor() { public void undo() { removeExtensions(key, to); copyExtensions(key, backup, to); getMapController().nodeChanged(to); } public String getDescription() { return "undoableCopyExtensions"; } public void act() { copyExtensions(key, from, to); getMapController().nodeChanged(to); } }; execute(actor, map); } void removeExtensions(final Object key, final NodeModel from, final NodeModel which) { if (from.equals(which)) { for (final IExtensionCopier copier : copiers) { copier.remove(key, from); } return; } for (final IExtensionCopier copier : copiers) { copier.remove(key, from, which); } } public void undoableRemoveExtensions(final Object key, final NodeModel from, final NodeModel which) { final MapModel map = from.getMap(); if (map == null) { removeExtensions(key, from, which); return; } final IUndoHandler undoHandler = (IUndoHandler) map.getExtension(IUndoHandler.class); if (undoHandler == null) { removeExtensions(key, from, which); return; } final NodeModel backup = new NodeModel(null); copyExtensions(key, from, backup); final IActor actor = new IActor() { public void undo() { copyExtensions(key, backup, from); getMapController().nodeChanged(from); } public String getDescription() { return "undoableCopyExtensions"; } public void act() { removeExtensions(key, from, which); getMapController().nodeChanged(from); } }; execute(actor, map); } public void resolveParentExtensions(final Object key, final NodeModel to) { for (final IExtensionCopier copier : copiers) { copier.resolveParentExtensions(key, to); } } public void undoableResolveParentExtensions(final Object key, final NodeModel to) { final MapModel map = to.getMap(); if (map == null) { resolveParentExtensions(key, to); return; } final IUndoHandler undoHandler = (IUndoHandler) map.getExtension(IUndoHandler.class); if (undoHandler == null) { resolveParentExtensions(key, to); return; } final NodeModel backup = new NodeModel(null); copyExtensions(key, to, backup); final IActor actor = new IActor() { public void undo() { copyExtensions(key, backup, to); getMapController().nodeChanged(to); } public String getDescription() { return "undoableCopyExtensions"; } public void act() { resolveParentExtensions(key, to); getMapController().nodeChanged(to); } }; execute(actor, map); } void removeExtensions(final Object key, final NodeModel from) { for (final IExtensionCopier copier : copiers) { copier.remove(key, from, from); } } public void addINodeViewLifeCycleListener(final INodeViewLifeCycleListener listener) { nodeViewListeners.add(listener); } public void addMenuContributor(final IMenuContributor contributor) { menuContributors.add(contributor); } public void commit() { } public boolean isUndoAction() { return false; } public void execute(final IActor actor, final MapModel map) { actor.act(); } @Override public AFreeplaneAction getAction(final String key) { final AFreeplaneAction action = super.getAction(key); if (action != null) { return action; } return getController().getAction(key); } public Controller getController() { return controller; } public <T extends IExtension> T getExtension(final Class<T> clazz) { return extensionContainer.getExtension(clazz); } public boolean containsExtension(final Class<? extends IExtension> clazz) { return extensionContainer.containsExtension(clazz); } public void removeExtension(Class<DocuMapAttribute> clazz) { extensionContainer.removeExtension(clazz); } public MapController getMapController() { return mapController; } public String getModeName() { return null; } public IUserInputListenerFactory getUserInputListenerFactory() { return userInputListenerFactory; } public boolean hasOneVisibleChild(final NodeModel parent) { int count = 0; for (final NodeModel child : getMapController().childrenUnfolded(parent)) { if (child.isVisible()) { count++; } if (count == 2) { return false; } } return count == 1; } public boolean isBlocked() { return isBlocked; } public void onViewCreated(final Container node) { for (final INodeViewLifeCycleListener hook : nodeViewListeners) { hook.onViewCreated(node); } } public void onViewRemoved(final Container node) { for (final INodeViewLifeCycleListener hook : nodeViewListeners) { hook.onViewRemoved(node); } } @Override public AFreeplaneAction removeAction(final String key) { final AFreeplaneAction action = super.removeAction(key); if (mapController != null) { mapController.removeListenerForAction(action); } return action; } public void removeINodeViewLifeCycleListener(final INodeViewLifeCycleListener listener) { nodeViewListeners.remove(listener); } public void rollback() { } public void setBlocked(final boolean isBlocked) { this.isBlocked = isBlocked; } public void setMapController(final MapController mapController) { this.mapController = mapController; addExtension(MapController.class, mapController); } public void setUserInputListenerFactory(final IUserInputListenerFactory userInputListenerFactory) { this.userInputListenerFactory = userInputListenerFactory; } /* * (non-Javadoc) * @see freeplane.modes.ModeController#setVisible(boolean) */ public void setVisible(final boolean visible) { final NodeModel node = getController().getSelection().getSelected(); if (visible) { mapController.onSelect(node); } else { if (node != null) { mapController.onDeselect(node); } } } public void shutdown() { } public void startTransaction() { } public void forceNewTransaction() { } /** * This method is called after and before a change of the map mapView. Use * it to perform the actions that cannot be performed at creation time. */ public void startup() { } public void updateMenus(String menuStructure, final Set<String> plugins) { final IUserInputListenerFactory userInputListenerFactory = getUserInputListenerFactory(); userInputListenerFactory.updateMenus(menuStructure, plugins); final MenuBuilder menuBuilder = userInputListenerFactory.getMenuBuilder(); final Iterator<IMenuContributor> iterator = menuContributors.iterator(); while (iterator.hasNext()) { iterator.next().updateMenus(this, menuBuilder); } } public boolean canEdit() { return false; } public String createToolTip(final NodeModel node, Component view) { // perhaps we should use the solution presented in the 3rd answer at // http://stackoverflow.com/questions/3355469/1-pixel-table-border-in-jtextpane-using-html // html/css example: http://www.wer-weiss-was.de/theme35/article3555660.html final String style = "<style type='text/css'>" // + " body { font-size: 10pt; }" // FIXME: copy from NoteController.setNoteTooltip() ? + "</style>"; final StringBuilder text = new StringBuilder("<html><head>"+style+"</head><body>"); boolean tooltipSet = false; for (final ITooltipProvider provider : toolTip.values()) { String value = provider.getTooltip(this, node, view); if (value == null) { continue; } value = value.replace("<html>", "<div>"); value = value.replaceAll("\\s*</?(body|head)>", ""); value = value.replace("<td>", "<td style='background-color: white'>"); value = value.replace("</html>", "</div>"); if (tooltipSet) { text.append("<br>"); } text.append(value); tooltipSet = true; } if (tooltipSet) { text.append("</body></html>"); // System.err.println("tooltip=" + text); return text.toString(); } return null; } /** */ public void addToolTipProvider(final Integer key, final ITooltipProvider tooltip) { if (tooltip == null) { if (toolTip.containsKey(key)) { toolTip.remove(key); } } else { toolTip.put(key, tooltip); } } }