/*FreeMind - A Program for creating and viewing Mindmaps *Copyright (C) 2000-2001 Joerg Mueller <joergmueller@bigfoot.com> *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 freemind.modes; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Timer; import java.util.TimerTask; import java.util.Vector; import java.util.logging.Logger; import javax.swing.event.EventListenerList; import javax.swing.event.TreeModelEvent; import javax.swing.event.TreeModelListener; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeNode; import freemind.controller.filter.DefaultFilter; import freemind.controller.filter.Filter; import freemind.controller.filter.condition.NoFilteringCondition; import freemind.main.FreeMindMain; import freemind.main.Tools; import freemind.main.XMLParseException; public abstract class MapAdapter extends DefaultTreeModel implements MindMap { private static final int INTERVAL_BETWEEN_FILE_MODIFICATION_TIME_CHECKS = 5000; /** * denotes the amount of changes since the last save. The initial value is * zero, such that new models are not to be saved. */ protected int changesPerformedSinceLastSave = 0; protected boolean readOnly = true; private File file; private long mFileTime = 0; private FreeMindMain frame; static protected Logger logger; private MapRegistry registry; private Filter filter = null; protected final ModeController mModeController; private HashSet mMapSourceChangedObserverSet = new HashSet(); private Timer mTimerForFileChangeObservation; public MapAdapter(FreeMindMain frame, ModeController modeController) { super(null); this.frame = frame; this.mModeController = modeController; mModeController.setModel(this); if (logger == null) { logger = frame.getLogger(this.getClass().getName()); } registry = new MapRegistry(this, modeController); filter = new DefaultFilter(NoFilteringCondition.createCondition(), true, false); mTimerForFileChangeObservation = new Timer(); mTimerForFileChangeObservation.schedule( new FileChangeInspectorTimerTask(), INTERVAL_BETWEEN_FILE_MODIFICATION_TIME_CHECKS, INTERVAL_BETWEEN_FILE_MODIFICATION_TIME_CHECKS); } public ModeController getModeController() { return mModeController; } protected class FileChangeInspectorTimerTask extends TimerTask { public void run() { boolean shouldFire = false; long lastModified = 0; // minimal synchronized block: synchronized (MapAdapter.this) { lastModified = getFileTime(); if (lastModified > mFileTime) { shouldFire = true; // don't change the file time here. Only, after the user has been asked. } } if (shouldFire) { for (Iterator it = mMapSourceChangedObserverSet.iterator(); it .hasNext();) { logger.info("File " + getFile() + " changed on disk as it was last modified at " + new Date(lastModified)); MapSourceChangedObserver observer = (MapSourceChangedObserver) it .next(); try { boolean changeAccepted = observer.mapSourceChanged(MapAdapter.this); if(!changeAccepted) { // this is a trick: at the next save/load the correct value is set again. mFileTime = Long.MAX_VALUE; } else { mFileTime = lastModified; } } catch (Exception e) { freemind.main.Resources.getInstance().logException(e); } } } } } /** * Instantiations of this class must call this, when a map was loaded or * saved. */ protected void setFileTime() { mFileTime = getFileTime(); } private long getFileTime() { long lastModified; File fileName; fileName = getFile(); if (fileName != null) { lastModified = fileName.lastModified(); } else { lastModified = 0; } return lastModified; } public void load(File file) throws FileNotFoundException, IOException { try { load(Tools.fileToUrl(file)); } catch (XMLParseException e) { freemind.main.Resources.getInstance().logException(e); } catch (URISyntaxException e) { freemind.main.Resources.getInstance().logException(e); } } /** * Attempts to lock the map using semaphore file. * * @return If the map is locked, return the name of the locking user, return * null otherwise. * @throws Exception */ public String tryToLock(File file) throws Exception { return null; } public void destroy() { cancelFileChangeObservationTimer(); // Do all the necessary destructions in your model, // e.g. remove file locks. // and remove all hooks: removeNodes(getRootNode()); } protected void cancelFileChangeObservationTimer() { mTimerForFileChangeObservation.cancel(); } // (PN) // public void close() { // } /** */ private void removeNodes(MindMapNode node) { node.removeAllHooks(); mModeController.fireNodePreDeleteEvent(node); // and all children: for (Iterator i = node.childrenUnfolded(); i.hasNext();) { MindMapNode child = (MindMapNode) i.next(); removeNodes(child); } } public FreeMindMain getFrame() { return frame; } // // Attributes // public boolean isSaved() { return (changesPerformedSinceLastSave == 0); } public boolean isReadOnly() { return readOnly; } /** * Counts the amount of actions performed. * * @param saved * true if the file was saved recently. False otherwise. */ public void setSaved(boolean saved) { boolean setTitle = false; if (saved) { changesPerformedSinceLastSave = 0; setTitle = true; } else { if (changesPerformedSinceLastSave == 0) { setTitle = true; } ++changesPerformedSinceLastSave; } if (setTitle) { getModeController().getController().setTitle(); } } protected int getNumberOfChangesSinceLastSave() { return changesPerformedSinceLastSave; } public MindMapNode getRootNode() { return (MindMapNode) getRoot(); } public void setRoot(MindMapNode root) { super.setRoot(root); } /** * @param newRoot * one of the nodes, that is now root. The others are grouped * around. */ public void changeRoot(MindMapNode newRoot) { if (newRoot == getRootNode()) { return; } boolean left = newRoot.isLeft(); MindMapNode node = newRoot; // collect parents (as we remove them from their parents...) Vector parents = new Vector(); while (node.getParentNode() != null) { MindMapNode parent = node.getParentNode(); parents.add(0, node); node = parent; } // bind all parents to a new chain: for (Iterator it = parents.iterator(); it.hasNext();) { node = (MindMapNode) it.next(); MindMapNode parent = node.getParentNode(); // remove parent node.removeFromParent(); // special treatment for left/right if (node == newRoot) { for (Iterator it2 = node.getChildren().iterator(); it2 .hasNext();) { MindMapNode child = (MindMapNode) it2.next(); child.setLeft(left); } parent.setLeft(!left); } // and put it as a child node.insert(parent, node.getChildCount()); } // and set root setRoot(newRoot); } /** * Change this to always return null if your model doesn't support files. */ public File getFile() { return file; } /** * Return URL of the map (whether as local file or a web location) */ public URL getURL() throws MalformedURLException { return getFile() != null ? Tools.fileToUrl(getFile()) : null; } protected void setFile(File file) { this.file = file; } protected String getText(String textId) { return getFrame().getResourceString(textId); } // // Node editing // public String getAsPlainText(List mindMapNodes) { return ""; } public String getAsRTF(List mindMapNodes) { return ""; } public String getAsHTML(List mindMapNodes) { return null; } public String getRestorable() { return null; } public MindMapLinkRegistry getLinkRegistry() { return null; } /** * This method should not be called directly! */ public void nodeChanged(TreeNode node) { getModeController().nodeChanged((MindMapNode) node); } public void nodeRefresh(TreeNode node) { getModeController().nodeRefresh((MindMapNode) node); } /** * Invoke this method if you've totally changed the children of node and its * childrens children... This will post a treeStructureChanged event. */ void nodeChangedInternal(TreeNode node) { if (node != null) { fireTreeNodesChanged(this, getPathToRoot(node), null, null); } } /** * Notifies all listeners that have registered interest for notification on * this event type. The event instance is lazily created using the * parameters passed into the fire method. * * @param source * the node being changed * @param path * the path to the root node * @param childIndices * the indices of the changed elements * @param children * the changed elements * @see EventListenerList */ protected void fireTreeNodesInserted(Object source, Object[] path, int[] childIndices, Object[] children) { // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); TreeModelEvent e = null; // Process the listeners last to first, notifying // those that are interested in this event e = fireTreeNodesInserted(source, path, childIndices, children, listeners, e); MindMapNode node = (MindMapNode) path[path.length - 1]; fireTreeNodesInserted(source, path, childIndices, children, node .getListeners().getListenerList(), e); } private TreeModelEvent fireTreeNodesInserted(Object source, Object[] path, int[] childIndices, Object[] children, Object[] listeners, TreeModelEvent e) { for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == TreeModelListener.class) { // Lazily create the event: if (e == null) e = new TreeModelEvent(source, path, childIndices, children); ((TreeModelListener) listeners[i + 1]).treeNodesInserted(e); } } return e; } protected void fireTreeNodesRemoved(Object source, Object[] path, int[] childIndices, Object[] children) { // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); TreeModelEvent e = null; // Process the listeners last to first, notifying // those that are interested in this event e = fireTreeNodesRemoved(source, path, childIndices, children, listeners, e); MindMapNode node = (MindMapNode) path[path.length - 1]; fireTreeNodesRemoved(source, path, childIndices, children, node .getListeners().getListenerList(), e); } private TreeModelEvent fireTreeNodesRemoved(Object source, Object[] path, int[] childIndices, Object[] children, Object[] listeners, TreeModelEvent e) { for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == TreeModelListener.class) { // Lazily create the event: if (e == null) e = new TreeModelEvent(source, path, childIndices, children); ((TreeModelListener) listeners[i + 1]).treeNodesRemoved(e); } } return e; } protected void fireTreeStructureChanged(Object source, Object[] path, int[] childIndices, Object[] children) { // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); TreeModelEvent e = null; // Process the listeners last to first, notifying // those that are interested in this event e = fireTreeStructureChanged(source, path, childIndices, children, listeners, e); MindMapNode node = (MindMapNode) path[path.length - 1]; fireTreeStructureChanged(source, path, childIndices, children, node .getListeners().getListenerList(), e); } private TreeModelEvent fireTreeStructureChanged(Object source, Object[] path, int[] childIndices, Object[] children, Object[] listeners, TreeModelEvent e) { for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == TreeModelListener.class) { // Lazily create the event: if (e == null) e = new TreeModelEvent(source, path, childIndices, children); ((TreeModelListener) listeners[i + 1]).treeStructureChanged(e); } } return e; } protected void fireTreeNodesChanged(Object source, Object[] path, int[] childIndices, Object[] children) { // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); TreeModelEvent e = null; // Process the listeners last to first, notifying // those that are interested in this event e = fireTreeNodesChanged(source, path, childIndices, children, listeners, e); MindMapNode node = (MindMapNode) path[path.length - 1]; fireTreeNodesChanged(source, path, childIndices, children, node .getListeners().getListenerList(), e); } private TreeModelEvent fireTreeNodesChanged(Object source, Object[] path, int[] childIndices, Object[] children, Object[] listeners, TreeModelEvent e) { for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == TreeModelListener.class) { // Lazily create the event: if (e == null) e = new TreeModelEvent(source, path, childIndices, children); ((TreeModelListener) listeners[i + 1]).treeNodesChanged(e); } } return e; } public MapRegistry getRegistry() { return registry; } public Filter getFilter() { return filter; } public void setFilter(Filter filter) { this.filter = filter; } public void registerMapSourceChangedObserver( MapSourceChangedObserver pMapSourceChangedObserver, long pGetEventIfChangedAfterThisTimeInMillies) { if (pGetEventIfChangedAfterThisTimeInMillies != 0 && mFileTime > pGetEventIfChangedAfterThisTimeInMillies) { try { // Issue event here. pMapSourceChangedObserver.mapSourceChanged(this); } catch (Exception e) { freemind.main.Resources.getInstance().logException(e); } } mMapSourceChangedObserverSet.add(pMapSourceChangedObserver); } public long deregisterMapSourceChangedObserver( MapSourceChangedObserver pMapSourceChangedObserver) { mMapSourceChangedObserverSet.remove(pMapSourceChangedObserver); return mFileTime; } }