/*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 plugins.map; import java.io.File; import java.io.FileFilter; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.Timer; import java.util.TimerTask; import java.util.Vector; import javax.swing.Action; import javax.swing.JMenuItem; import org.openstreetmap.gui.jmapviewer.Coordinate; import org.openstreetmap.gui.jmapviewer.MemoryTileCache; import org.openstreetmap.gui.jmapviewer.OsmFileCacheTileLoader; import org.openstreetmap.gui.jmapviewer.OsmMercator; import org.openstreetmap.gui.jmapviewer.OsmTileLoader; import org.openstreetmap.gui.jmapviewer.Tile; import org.openstreetmap.gui.jmapviewer.TileController; import org.openstreetmap.gui.jmapviewer.interfaces.TileCache; import org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener; import org.openstreetmap.gui.jmapviewer.interfaces.TileSource; import org.openstreetmap.gui.jmapviewer.tilesources.OsmTileSource; import plugins.map.MapNodePositionHolder.MapNodePositionListener; import freemind.common.BooleanProperty; import freemind.common.DontShowNotificationProperty; import freemind.common.SeparatorProperty; import freemind.common.TextTranslator; import freemind.controller.MenuItemEnabledListener; import freemind.controller.actions.generated.instance.PlaceNodeXmlAction; import freemind.controller.actions.generated.instance.XmlAction; import freemind.extensions.HookRegistration; import freemind.main.FreeMind; import freemind.main.FreeMindMain.VersionInformation; import freemind.main.Resources; import freemind.main.Tools; import freemind.main.Tools.IntHolder; import freemind.modes.MindMap; import freemind.modes.MindMapNode; import freemind.modes.ModeController; import freemind.modes.mindmapmode.MindMapController; import freemind.modes.mindmapmode.actions.NodeHookAction; import freemind.modes.mindmapmode.actions.xml.ActionFactory; import freemind.modes.mindmapmode.actions.xml.ActionPair; import freemind.modes.mindmapmode.actions.xml.ActorXml; import freemind.preferences.FreemindPropertyContributor; import freemind.preferences.layout.OptionPanel; public class Registration implements HookRegistration, ActorXml, TileLoaderListener, MenuItemEnabledListener { /** * * Clean the file cache periodically. * * @author foltin * @date 27.04.2012 */ public class CachePurger extends TimerTask { /** * @author foltin * @date 27.04.2012 */ private final class AgeFilter implements FileFilter { private final long mYoungestFileToAccept; public AgeFilter(long pYoungestFileToAccept) { mYoungestFileToAccept = pYoungestFileToAccept; } public boolean accept(File pPathname) { return pPathname.getName().endsWith(".tags") && pPathname.lastModified() <= mYoungestFileToAccept; } } private final File mCacheDirectory; private final long mCacheMaxAge; /** * @param pCacheDirectory * @param pCacheMaxAge */ public CachePurger(File pCacheDirectory, long pCacheMaxAge) { mCacheDirectory = pCacheDirectory; mCacheMaxAge = pCacheMaxAge; } /* * (non-Javadoc) * * @see java.util.TimerTask#run() */ public void run() { // the jobs must not overtake themselves. synchronized (mCachePurgerSemaphore) { if (mCachePurgerSemaphore.getValue() > 0) { return; } mCachePurgerSemaphore.setValue(1); } try { logger.info("Start purging for " + mCacheDirectory); if (mCacheDirectory.exists()) { File[] cacheDirectories = mCacheDirectory.listFiles(); for (int i = 0; i < cacheDirectories.length; i++) { File cacheDirectory = cacheDirectories[i]; purgeDirectory(cacheDirectory); } } logger.info("Finished purging"); } finally { mCachePurgerSemaphore.setValue(0); } } /** * @param pCacheDirectory */ private void purgeDirectory(File pCacheDirectory) { logger.fine("Start purging for subdir " + pCacheDirectory); File[] listTagFiles = pCacheDirectory.listFiles(new AgeFilter( System.currentTimeMillis() - mCacheMaxAge)); if (listTagFiles == null) { return; } for (int i = 0; i < listTagFiles.length; i++) { File tagFile = listTagFiles[i]; File imageFile = new File(tagFile.getPath().replace(".tags", ".png")); try { logger.finest("Deleting " + tagFile); logger.finest("Deleting " + imageFile); tagFile.delete(); imageFile.delete(); } catch (Exception e) { freemind.main.Resources.getInstance().logException(e); } } } } private static final String PLUGINS_MAP_NODE_POSITION = MapNodePositionHolder.class .getName(); /* * Collects MapNodePositionHolder. This is necessary to be able to display * them all efficiently. */ private HashSet/* MapNodePositionHolder s */mMapNodePositionHolders = new HashSet(); private HashSet mMapNodePositionListeners = new HashSet(); private final MindMapController controller; private final MindMap mMap; private final java.util.logging.Logger logger; private TileSource mTileSource; private TileController mTileController; private MemoryTileCache mTileCache; private MapDialog mMapDialog = null; private MapDialogPropertyContributor mOptionContributor; private static Timer sTimer; private static Boolean sTimerSemaphore = new Boolean(false); private IntHolder mCachePurgerSemaphore = new IntHolder(0); private static final class MapDialogPropertyContributor implements FreemindPropertyContributor { private final MindMapController modeController; public MapDialogPropertyContributor(MindMapController modeController) { this.modeController = modeController; } public List getControls(TextTranslator pTextTranslator) { Vector controls = new Vector(); controls.add(new OptionPanel.NewTabProperty( "plugins/map/MapDialog.properties_MapDialogTabName")); controls.add(new SeparatorProperty( "plugins/map/MapDialog.properties_PatternSeparatorName")); controls.add(new BooleanProperty("node_map_show_tooltip.tooltip", "node_map_show_tooltip")); controls.add(new DontShowNotificationProperty( "resources_search_for_node_text_without_question.tooltip", FreeMind.RESOURCES_SEARCH_FOR_NODE_TEXT_WITHOUT_QUESTION)); return controls; } } public Registration(ModeController controller, MindMap map) { this.controller = (MindMapController) controller; mMap = map; logger = controller.getFrame().getLogger(this.getClass().getName()); mTileSource = new OsmTileSource.Mapnik(); mTileCache = new MemoryTileCache(); mTileController = new TileController(mTileSource, mTileCache, this); mTileController.setTileLoader(createTileLoader(this)); mOptionContributor = new MapDialogPropertyContributor(this.controller); synchronized (sTimerSemaphore) { if (sTimer == null) { // only once in the system sTimer = new Timer(); long purgeTime = Resources.getInstance().getLongProperty( MapDialog.TILE_CACHE_PURGE_TIME, MapDialog.TILE_CACHE_PURGE_TIME_DEFAULT); sTimer.schedule(new CachePurger(getCacheDirectory(), getCacheMaxAge()), purgeTime, purgeTime); } } } /** * @param pPosition * @param pTileSource */ public TileImage getImageForTooltip(Coordinate pPosition, int pZoom, String pTileSource) { TileSource tileSource = FreeMindMapController.changeTileSource( pTileSource, null); if (tileSource != null) { mTileSource = tileSource; mTileController.setTileSource(tileSource); } int tileSize = mTileSource.getTileSize(); int exactx = OsmMercator.LonToX(pPosition.getLon(), pZoom); int exacty = OsmMercator.LatToY(pPosition.getLat(), pZoom); int x = exactx / tileSize; int y = exacty / tileSize; // determine other surrounding tiles that are close to the exact // point. int dx = exactx % tileSize; int dy = exacty % tileSize; // determine quadrant of cursor in tile: if (dx < tileSize / 2) { x -= 1; dx += tileSize; } if (dy < tileSize / 2) { y -= 1; dy += tileSize; } TileImage tileImage = new TileImage(); tileImage.setTiles(2, x, y, pZoom, mTileController, logger, dx, dy); // wait for tiles: int timeout = 60; while (timeout-- > 0) { try { if (tileImage.isLoaded() || tileImage.hasErrors()) { break; } Thread.sleep(100); } catch (Exception e) { freemind.main.Resources.getInstance().logException(e); } } return tileImage; } public void deRegister() { OptionPanel.removeContributor(mOptionContributor); controller.getActionFactory().deregisterActor(getDoActionClass()); } public void register() { OptionPanel.addContributor(mOptionContributor); controller.getActionFactory().registerActor(this, getDoActionClass()); } public void registerMapNode(MapNodePositionHolder pMapNodePositionHolder) { mMapNodePositionHolders.add(pMapNodePositionHolder); for (Iterator it = mMapNodePositionListeners.iterator(); it.hasNext();) { MapNodePositionListener listener = (MapNodePositionListener) it .next(); try { listener.registerMapNode(pMapNodePositionHolder); } catch (Exception e) { freemind.main.Resources.getInstance().logException(e); } } } public Set getMapNodePositionHolders() { return Collections.unmodifiableSet(mMapNodePositionHolders); } public void deregisterMapNode(MapNodePositionHolder pMapNodePositionHolder) { mMapNodePositionHolders.remove(pMapNodePositionHolder); for (Iterator it = mMapNodePositionListeners.iterator(); it.hasNext();) { MapNodePositionListener listener = (MapNodePositionListener) it .next(); try { listener.deregisterMapNode(pMapNodePositionHolder); } catch (Exception e) { freemind.main.Resources.getInstance().logException(e); } } } public void registerMapNodePositionListener( MapNodePositionListener pMapNodePositionListener) { mMapNodePositionListeners.add(pMapNodePositionListener); } public void deregisterMapNodePositionListener( MapNodePositionListener pMapNodePositionListener) { mMapNodePositionListeners.remove(pMapNodePositionListener); } public OsmTileLoader createTileLoader(TileLoaderListener mMap) { OsmTileLoader loader = null; String tileCacheClass = Resources.getInstance().getProperty( MapDialog.TILE_CACHE_CLASS); if (Tools.safeEquals(tileCacheClass, "file")) { File cacheDir = getCacheDirectory(); try { OsmFileCacheTileLoader osmFileCacheTileLoader = new OsmFileCacheTileLoader( mMap, cacheDir); loader = osmFileCacheTileLoader; long maxFileAge = getCacheMaxAge(); logger.info("Setting cache max age to " + maxFileAge / OsmFileCacheTileLoader.FILE_AGE_ONE_DAY + " days."); osmFileCacheTileLoader.setCacheMaxFileAge(maxFileAge); } catch (Exception e1) { freemind.main.Resources.getInstance().logException(e1); } } if (loader == null) { logger.info("Using osm tile loader"); loader = new OsmTileLoader(mMap); } VersionInformation freemindVersion = controller.getFrame() .getFreemindVersion(); loader.headers.put("User-agent", "FreeMind " + freemindVersion); return loader; } protected long getCacheMaxAge() { long maxFileAge = Resources.getInstance().getLongProperty( MapDialog.TILE_CACHE_MAX_AGE, OsmFileCacheTileLoader.FILE_AGE_ONE_WEEK); return maxFileAge; } protected File getCacheDirectory() { String directory = Resources.getInstance().getProperty( MapDialog.FILE_TILE_CACHE_DIRECTORY); if (directory.startsWith("%/")) { directory = Resources.getInstance().getFreemindDirectory() + File.separator + directory.substring(2); } File cacheDir = new File(directory); logger.info("Trying to use file cache tile loader with dir " + directory); return cacheDir; } /** * Set map position. Is undoable. * * @param pTileSource * */ public void changePosition(MapNodePositionHolder pHolder, Coordinate pPosition, Coordinate pMapCenter, int pZoom, String pTileSource) { MindMapNode node = pHolder.getNode(); PlaceNodeXmlAction doAction = createPlaceNodeXmlActionAction(node, pPosition, pMapCenter, pZoom, pTileSource); PlaceNodeXmlAction undoAction = createPlaceNodeXmlActionAction(node, pHolder.getPosition(), pHolder.getMapCenter(), pHolder.getZoom(), pHolder.getTileSource()); ActionFactory actionFactory = controller.getActionFactory(); actionFactory.doTransaction(PLUGINS_MAP_NODE_POSITION, new ActionPair( doAction, undoAction)); } /** * @param pNode * @param pPosition * @param pMapCenter * @param pZoom * @param pTileSource * @return */ private PlaceNodeXmlAction createPlaceNodeXmlActionAction( MindMapNode pNode, Coordinate pPosition, Coordinate pMapCenter, int pZoom, String pTileSource) { logger.info("Setting position of node " + pNode); PlaceNodeXmlAction action = new PlaceNodeXmlAction(); action.setNode(controller.getNodeID(pNode)); action.setCursorLatitude(pPosition.getLat()); action.setCursorLongitude(pPosition.getLon()); action.setMapCenterLatitude(pMapCenter.getLat()); action.setMapCenterLongitude(pMapCenter.getLon()); action.setZoom(pZoom); action.setTileSource(pTileSource); return action; } /* * (non-Javadoc) * * @see * freemind.modes.mindmapmode.actions.xml.ActorXml#act(freemind.controller * .actions.generated.instance.XmlAction) */ public void act(XmlAction pAction) { if (pAction instanceof PlaceNodeXmlAction) { PlaceNodeXmlAction placeAction = (PlaceNodeXmlAction) pAction; MindMapNode node = controller.getNodeFromID(placeAction.getNode()); MapNodePositionHolder hook = MapNodePositionHolder.getHook(node); if (hook != null) { hook.setMapCenter(new Coordinate(placeAction .getMapCenterLatitude(), placeAction .getMapCenterLongitude())); hook.setPosition(new Coordinate( placeAction.getCursorLatitude(), placeAction .getCursorLongitude())); hook.setZoom(placeAction.getZoom()); hook.setTileSource(placeAction.getTileSource()); hook.recreateTooltip(); // TODO: Only, if values really changed. controller.nodeChanged(node); } else { throw new IllegalArgumentException( "MapNodePositionHolder to node id " + placeAction.getNode() + " not found."); } } } /* * (non-Javadoc) * * @see freemind.modes.mindmapmode.actions.xml.ActorXml#getDoActionClass() */ public Class getDoActionClass() { return PlaceNodeXmlAction.class; } /* * (non-Javadoc) * * @see org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener# * getTileCache() */ public TileCache getTileCache() { return mTileCache; } /* * (non-Javadoc) * * @see org.openstreetmap.gui.jmapviewer.interfaces.TileLoaderListener# * tileLoadingFinished(org.openstreetmap.gui.jmapviewer.Tile, boolean) */ public void tileLoadingFinished(Tile pTile, boolean pSuccess) { // TODO Auto-generated method stub } /* * (non-Javadoc) * * @see * freemind.controller.MenuItemEnabledListener#isEnabled(javax.swing.JMenuItem * , javax.swing.Action) */ public boolean isEnabled(JMenuItem pItem, Action pAction) { String hookName = ((NodeHookAction) pAction).getHookName(); logger.fine("Enabled for " + hookName); if (SearchInMapForNodeTextAction.NODE_CONTEXT_PLUGIN_NAME .equals(hookName)) { return true; } if (ShowMapToNodeAction.NODE_CONTEXT_PLUGIN_NAME.equals(hookName) || AddLinkToMapAction.NODE_CONTEXT_PLUGIN_NAME.equals(hookName) || RemoveMapToNodeAction.NODE_CONTEXT_PLUGIN_NAME .equals(hookName) || AddMapImageToNodeAction.NODE_CONTEXT_PLUGIN_NAME .equals(hookName)) { for (Iterator it = controller.getSelecteds().iterator(); it .hasNext();) { MindMapNode node = (MindMapNode) it.next(); MapNodePositionHolder hook = MapNodePositionHolder .getHook(node); if (hook != null) { return true; } } } return false; } public MapDialog getMapDialog() { return mMapDialog; } public void setMapDialog(MapDialog pMapDialog) { mMapDialog = pMapDialog; } public interface NodeVisibilityListener { void nodeVisibilityChanged( MapNodePositionHolder pMapNodePositionHolder, boolean pVisible); } private HashSet mNodeVisibilityListeners = new HashSet(); public void registerNodeVisibilityListener( NodeVisibilityListener pNodeVisibilityListener) { mNodeVisibilityListeners.add(pNodeVisibilityListener); } public void deregisterNodeVisibilityListener( NodeVisibilityListener pNodeVisibilityListener) { mNodeVisibilityListeners.remove(pNodeVisibilityListener); } /** * @param pVisible * is true, when a node is visible now. * @param pMapNodePositionHolder */ public void fireNodeVisibilityChanged(boolean pVisible, MapNodePositionHolder pMapNodePositionHolder) { for (Iterator it = mNodeVisibilityListeners.iterator(); it.hasNext();) { NodeVisibilityListener listener = (NodeVisibilityListener) it .next(); try { listener.nodeVisibilityChanged(pMapNodePositionHolder, pVisible); } catch (Exception e) { freemind.main.Resources.getInstance().logException(e); } } } }