// License: GPL. Copyright 2008 by Immanuel Scholz and others package org.openstreetmap.josm.gui.layer.markerlayer; import java.awt.Graphics; import java.awt.Point; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.util.Collection; import java.util.LinkedList; import javax.swing.Icon; import org.openstreetmap.josm.data.coor.CachedLatLon; import org.openstreetmap.josm.data.coor.EastNorth; import org.openstreetmap.josm.data.coor.LatLon; import org.openstreetmap.josm.data.gpx.GpxData; import org.openstreetmap.josm.data.gpx.GpxLink; import org.openstreetmap.josm.data.gpx.WayPoint; import org.openstreetmap.josm.gui.MapView; import org.openstreetmap.josm.tools.ImageProvider; /** * Basic marker class. Requires a position, and supports * a custom icon and a name. * * This class is also used to create appropriate Marker-type objects * when waypoints are imported. * * It hosts a public list object, named makers, containing implementations of * the MarkerMaker interface. Whenever a Marker needs to be created, each * object in makers is called with the waypoint parameters (Lat/Lon and tag * data), and the first one to return a Marker object wins. * * By default, one the list contains one default "Maker" implementation that * will create AudioMarkers for .wav files, ImageMarkers for .png/.jpg/.jpeg * files, and WebMarkers for everything else. (The creation of a WebMarker will * fail if there's no vaild URL in the <link> tag, so it might still make sense * to add Makers for such waypoints at the end of the list.) * * The default implementation only looks at the value of the <link> tag inside * the <wpt> tag of the GPX file. * * <h2>HowTo implement a new Marker</h2> * <ul> * <li> Subclass Marker or ButtonMarker and override <code>containsPoint</code> * if you like to respond to user clicks</li> * <li> Override paint, if you want a custom marker look (not "a label and a symbol")</li> * <li> Implement MarkerCreator to return a new instance of your marker class</li> * <li> In you plugin constructor, add an instance of your MarkerCreator * implementation either on top or bottom of Marker.markerProducers. * Add at top, if your marker should overwrite an current marker or at bottom * if you only add a new marker style.</li> * </ul> * * @author Frederik Ramm <frederik@remote.org> */ public class Marker implements ActionListener { public final String text; public final Icon symbol; public final MarkerLayer parentLayer; public double time; /* absolute time of marker since epoch */ public double offset; /* time offset in seconds from the gpx point from which it was derived, may be adjusted later to sync with other data, so not final */ private CachedLatLon coor; public final void setCoor(LatLon coor) { if(this.coor == null) { this.coor = new CachedLatLon(coor); } else { this.coor.setCoor(coor); } } public final LatLon getCoor() { return coor; } public final void setEastNorth(EastNorth eastNorth) { coor.setEastNorth(eastNorth); } public final EastNorth getEastNorth() { return coor.getEastNorth(); } /** * Plugins can add their Marker creation stuff at the bottom or top of this list * (depending on whether they want to override default behaviour or just add new * stuff). */ public static LinkedList<MarkerProducers> markerProducers = new LinkedList<MarkerProducers>(); // Add one Maker specifying the default behaviour. static { Marker.markerProducers.add(new MarkerProducers() { @SuppressWarnings("unchecked") public Marker createMarker(WayPoint wpt, File relativePath, MarkerLayer parentLayer, double time, double offset) { String uri = null; // cheapest way to check whether "link" object exists and is a non-empty // collection of GpxLink objects... try { for (GpxLink oneLink : (Collection<GpxLink>) wpt.attr.get(GpxData.META_LINKS)) { uri = oneLink.uri; break; } } catch (Exception ex) {} // Try a relative file:// url, if the link is not in an URL-compatible form if (relativePath != null && uri != null && !isWellFormedAddress(uri)) { uri = new File(relativePath.getParentFile(), uri).toURI().toString(); } String name_desc = ""; if (wpt.attr.containsKey("name")) { name_desc = wpt.getString("name"); } else if (wpt.attr.containsKey("desc")) { name_desc = wpt.getString("desc"); } if (uri == null) return new Marker(wpt.getCoor(), name_desc, wpt.getString("symbol"), parentLayer, time, offset); else if (uri.endsWith(".wav")) return AudioMarker.create(wpt.getCoor(), name_desc, uri, parentLayer, time, offset); else if (uri.endsWith(".png") || uri.endsWith(".jpg") || uri.endsWith(".jpeg") || uri.endsWith(".gif")) return ImageMarker.create(wpt.getCoor(), uri, parentLayer, time, offset); else return WebMarker.create(wpt.getCoor(), uri, parentLayer, time, offset); } private boolean isWellFormedAddress(String link) { try { new URL(link); return true; } catch (MalformedURLException x) { return false; } } }); } public Marker(LatLon ll, String text, String iconName, MarkerLayer parentLayer, double time, double offset) { setCoor(ll); this.text = text; this.offset = offset; this.time = time; this.symbol = ImageProvider.getIfAvailable("markers",iconName); this.parentLayer = parentLayer; } /** * Checks whether the marker display area contains the given point. * Markers not interested in mouse clicks may always return false. * * @param p The point to check * @return <code>true</code> if the marker "hotspot" contains the point. */ public boolean containsPoint(Point p) { return false; } /** * Called when the mouse is clicked in the marker's hotspot. Never * called for markers which always return false from containsPoint. * * @param ev A dummy ActionEvent */ public void actionPerformed(ActionEvent ev) { } /** * Paints the marker. * @param g graphics context * @param mv map view * @param mousePressed true if the left mouse button is pressed */ public void paint(Graphics g, MapView mv, boolean mousePressed, String show) { Point screen = mv.getPoint(getEastNorth()); if (symbol != null && show.equalsIgnoreCase("show")) { symbol.paintIcon(mv, g, screen.x-symbol.getIconWidth()/2, screen.y-symbol.getIconHeight()/2); } else { g.drawLine(screen.x-2, screen.y-2, screen.x+2, screen.y+2); g.drawLine(screen.x+2, screen.y-2, screen.x-2, screen.y+2); } if ((text != null) && (show.equalsIgnoreCase("show"))) { g.drawString(text, screen.x+4, screen.y+2); } } /** * Returns an object of class Marker or one of its subclasses * created from the parameters given. * * @param wpt waypoint data for marker * @param relativePath An path to use for constructing relative URLs or * <code>null</code> for no relative URLs * @param offset double in seconds as the time offset of this marker from * the GPX file from which it was derived (if any). * @return a new Marker object */ public static Marker createMarker(WayPoint wpt, File relativePath, MarkerLayer parentLayer, double time, double offset) { for (MarkerProducers maker : Marker.markerProducers) { Marker marker = maker.createMarker(wpt, relativePath, parentLayer, time, offset); if (marker != null) return marker; } return null; } /** * Returns an AudioMarker derived from this Marker and the provided uri * Subclasses of specific marker types override this to return null as they can't * be turned into AudioMarkers. This includes AudioMarkers themselves, as they * already have audio. * * @param uri uri of wave file * @return AudioMarker */ public AudioMarker audioMarkerFromMarker(String uri) { AudioMarker audioMarker = AudioMarker.create(getCoor(), this.text, uri, this.parentLayer, this.time, this.offset); return audioMarker; } }