source: josm/trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayer.java@ 944

Last change on this file since 944 was 944, checked in by stoecker, 16 years ago

fixing NPE. Closes #1532, #1531, #1529. Patch by avar

  • Property svn:eol-style set to native
File size: 12.6 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.gui.layer.markerlayer;
3
4import static org.openstreetmap.josm.tools.I18n.marktr;
5import static org.openstreetmap.josm.tools.I18n.tr;
6import static org.openstreetmap.josm.tools.I18n.trn;
7
8import java.awt.Color;
9import java.awt.Component;
10import java.awt.Graphics;
11import java.awt.Point;
12import java.awt.event.ActionEvent;
13import java.awt.event.ActionListener;
14import java.awt.event.MouseAdapter;
15import java.awt.event.MouseEvent;
16import java.io.File;
17import java.net.URL;
18import java.util.ArrayList;
19import java.util.Collection;
20
21import javax.swing.Icon;
22import javax.swing.JColorChooser;
23import javax.swing.JMenuItem;
24import javax.swing.JOptionPane;
25import javax.swing.JSeparator;
26import javax.swing.SwingUtilities;
27
28import org.openstreetmap.josm.Main;
29import org.openstreetmap.josm.actions.RenameLayerAction;
30import org.openstreetmap.josm.data.coor.EastNorth;
31import org.openstreetmap.josm.data.gpx.GpxData;
32import org.openstreetmap.josm.data.gpx.GpxLink;
33import org.openstreetmap.josm.data.gpx.WayPoint;
34import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
35import org.openstreetmap.josm.gui.MapView;
36import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
37import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
38import org.openstreetmap.josm.gui.layer.GpxLayer;
39import org.openstreetmap.josm.gui.layer.Layer;
40import org.openstreetmap.josm.tools.AudioPlayer;
41import org.openstreetmap.josm.tools.ColorHelper;
42import org.openstreetmap.josm.tools.ImageProvider;
43
44/**
45 * A layer holding markers.
46 *
47 * Markers are GPS points with a name and, optionally, a symbol code attached;
48 * marker layers can be created from waypoints when importing raw GPS data,
49 * but they may also come from other sources.
50 *
51 * The symbol code is for future use.
52 *
53 * The data is read only.
54 */
55public class MarkerLayer extends Layer {
56
57 /**
58 * A list of markers.
59 */
60 public final Collection<Marker> data;
61 private boolean mousePressed = false;
62 public GpxLayer fromLayer = null;
63
64 /*
65 private Icon audioTracerIcon = null;
66 private EastNorth playheadPosition = null;
67 private static Timer timer = null;
68 private static double audioAnimationInterval = 0.0; // seconds
69 private static double playheadTime = -1.0;
70 */
71 public MarkerLayer(GpxData indata, String name, File associatedFile, GpxLayer fromLayer) {
72
73 super(name);
74 this.associatedFile = associatedFile;
75 this.data = new ArrayList<Marker>();
76 this.fromLayer = fromLayer;
77 double firstTime = -1.0;
78 String lastLinkedFile = "";
79
80 for (WayPoint wpt : indata.waypoints) {
81 /* calculate time differences in waypoints */
82 double time = wpt.time;
83 boolean wpt_has_link = wpt.attr.containsKey("link");
84 if (firstTime < 0 && wpt_has_link) {
85 firstTime = time;
86 for (GpxLink oneLink : (Collection<GpxLink>) wpt.attr.get("link")) {
87 lastLinkedFile = oneLink.uri;
88 break;
89 }
90 }
91 if (wpt_has_link) {
92 for (GpxLink oneLink : (Collection<GpxLink>) wpt.attr.get("link")) {
93 if (!oneLink.uri.equals(lastLinkedFile))firstTime = time;
94 lastLinkedFile = oneLink.uri;
95 break;
96 }
97 }
98 Marker m = Marker.createMarker(wpt, indata.storageFile, this, time, time - firstTime);
99 if (m != null)
100 data.add(m);
101 }
102
103 SwingUtilities.invokeLater(new Runnable(){
104 public void run() {
105 Main.map.mapView.addMouseListener(new MouseAdapter() {
106 @Override public void mousePressed(MouseEvent e) {
107 if (e.getButton() != MouseEvent.BUTTON1)
108 return;
109 boolean mousePressedInButton = false;
110 if (e.getPoint() != null) {
111 for (Marker mkr : data) {
112 if (mkr.containsPoint(e.getPoint())) {
113 mousePressedInButton = true;
114 break;
115 }
116 }
117 }
118 if (! mousePressedInButton)
119 return;
120 mousePressed = true;
121 if (visible)
122 Main.map.mapView.repaint();
123 }
124 @Override public void mouseReleased(MouseEvent ev) {
125 if (ev.getButton() != MouseEvent.BUTTON1 || ! mousePressed)
126 return;
127 mousePressed = false;
128 if (!visible)
129 return;
130 if (ev.getPoint() != null) {
131 for (Marker mkr : data) {
132 if (mkr.containsPoint(ev.getPoint()))
133 mkr.actionPerformed(new ActionEvent(this, 0, null));
134 }
135 }
136 Main.map.mapView.repaint();
137 }
138 });
139 }
140 });
141 }
142
143 /**
144 * Return a static icon.
145 */
146 @Override public Icon getIcon() {
147 return ImageProvider.get("layer", "marker_small");
148 }
149
150 @Override public void paint(Graphics g, MapView mv) {
151 boolean mousePressedTmp = mousePressed;
152 Point mousePos = mv.getMousePosition();
153 String mkrTextShow = Main.pref.get("marker.show "+name, "show");
154
155 g.setColor(Main.pref.getColor(marktr("gps marker"), "layer "+name, Color.gray));
156
157 for (Marker mkr : data) {
158 if (mousePos != null && mkr.containsPoint(mousePos)) {
159 mkr.paint(g, mv, mousePressedTmp, mkrTextShow);
160 mousePressedTmp = false;
161 } else {
162 mkr.paint(g, mv, false, mkrTextShow);
163 }
164 }
165 }
166
167 @Override public String getToolTipText() {
168 return data.size()+" "+trn("marker", "markers", data.size());
169 }
170
171 @Override public void mergeFrom(Layer from) {
172 MarkerLayer layer = (MarkerLayer)from;
173 data.addAll(layer.data);
174 }
175
176 @Override public boolean isMergable(Layer other) {
177 return other instanceof MarkerLayer;
178 }
179
180 @Override public void visitBoundingBox(BoundingXYVisitor v) {
181 for (Marker mkr : data)
182 v.visit(mkr.eastNorth);
183 }
184
185 @Override public Object getInfoComponent() {
186 return "<html>"+trn("{0} consists of {1} marker", "{0} consists of {1} markers", data.size(), name, data.size()) + "</html>";
187 }
188
189 @Override public Component[] getMenuEntries() {
190 JMenuItem color = new JMenuItem(tr("Customize Color"), ImageProvider.get("colorchooser"));
191 color.putClientProperty("help", "Action/LayerCustomizeColor");
192 color.addActionListener(new ActionListener(){
193 public void actionPerformed(ActionEvent e) {
194 JColorChooser c = new JColorChooser(Main.pref.getColor(marktr("gps marker"), "layer "+name, Color.gray));
195 Object[] options = new Object[]{tr("OK"), tr("Cancel"), tr("Default")};
196 int answer = JOptionPane.showOptionDialog(Main.parent, c, tr("Choose a color"), JOptionPane.OK_CANCEL_OPTION,
197 JOptionPane.PLAIN_MESSAGE, null, options, options[0]);
198 switch (answer) {
199 case 0:
200 Main.pref.putColor("layer "+name, c.getColor());
201 break;
202 case 1:
203 return;
204 case 2:
205 Main.pref.putColor("layer "+name, null);
206 break;
207 }
208 Main.map.repaint();
209 }
210 });
211
212 JMenuItem syncaudio = new JMenuItem(tr("Synchronize Audio"), ImageProvider.get("audio-sync"));
213 syncaudio.putClientProperty("help", "Action/SynchronizeAudio");
214 syncaudio.addActionListener(new ActionListener(){
215 public void actionPerformed(ActionEvent e) {
216 if (! AudioPlayer.paused()) {
217 JOptionPane.showMessageDialog(Main.parent,tr("You need to pause audio at the moment when you hear your synchronization cue."));
218 return;
219 }
220 AudioMarker recent = AudioMarker.recentlyPlayedMarker();
221 if (synchronizeAudioMarkers(recent)) {
222 JOptionPane.showMessageDialog(Main.parent, tr("Audio synchronized at point {0}.", recent.text));
223 } else {
224 JOptionPane.showMessageDialog(Main.parent,tr("Unable to synchronize in layer being played."));
225 }
226 }
227 });
228
229 JMenuItem moveaudio = new JMenuItem(tr("Make Audio Marker At Play Head"), ImageProvider.get("addmarkers"));
230 moveaudio.putClientProperty("help", "Action/MakeAudioMarkerAtPlayHead");
231 moveaudio.addActionListener(new ActionListener(){
232 public void actionPerformed(ActionEvent e) {
233 if (! AudioPlayer.paused()) {
234 JOptionPane.showMessageDialog(Main.parent,tr("You need to have paused audio at the point on the track where you want the marker."));
235 return;
236 }
237 PlayHeadMarker playHeadMarker = Main.map.mapView.playHeadMarker;
238 if (playHeadMarker == null)
239 return;
240 addAudioMarker(playHeadMarker.time, playHeadMarker.eastNorth);
241 Main.map.mapView.repaint();
242 }
243 });
244
245 Collection<Component> components = new ArrayList<Component>();
246 components.add(new JMenuItem(new LayerListDialog.ShowHideLayerAction(this)));
247 components.add(new JMenuItem(new LayerListDialog.ShowHideMarkerText(this)));
248 components.add(new JMenuItem(new LayerListDialog.DeleteLayerAction(this)));
249 components.add(new JSeparator());
250 components.add(color);
251 components.add(new JSeparator());
252 components.add(syncaudio);
253 if (Main.pref.getBoolean("marker.traceaudio", true)) {
254 components.add (moveaudio);
255 }
256 components.add(new JMenuItem(new RenameLayerAction(associatedFile, this)));
257 components.add(new JSeparator());
258 components.add(new JMenuItem(new LayerListPopup.InfoAction(this)));
259 return components.toArray(new Component[0]);
260 }
261
262 public boolean synchronizeAudioMarkers(AudioMarker startMarker) {
263 if (startMarker != null && ! data.contains(startMarker)) {
264 startMarker = null;
265 }
266 if (startMarker == null) {
267 // find the first audioMarker in this layer
268 for (Marker m : data) {
269 if (m instanceof AudioMarker) {
270 startMarker = (AudioMarker) m;
271 break;
272 }
273 }
274 }
275 if (startMarker == null)
276 return false;
277
278 // apply adjustment to all subsequent audio markers in the layer
279 double adjustment = AudioPlayer.position() - startMarker.offset; // in seconds
280 boolean seenStart = false;
281 URL url = startMarker.url();
282 for (Marker m : data) {
283 if (m == startMarker)
284 seenStart = true;
285 if (seenStart) {
286 AudioMarker ma = (AudioMarker) m; // it must be an AudioMarker
287 if (ma.url().equals(url))
288 ma.adjustOffset(adjustment);
289 }
290 }
291 return true;
292 }
293
294 public AudioMarker addAudioMarker(double time, EastNorth en) {
295 // find first audio marker to get absolute start time
296 double offset = 0.0;
297 AudioMarker am = null;
298 for (Marker m : data) {
299 if (m.getClass() == AudioMarker.class) {
300 am = (AudioMarker)m;
301 offset = time - am.time;
302 break;
303 }
304 }
305 if (am == null) {
306 JOptionPane.showMessageDialog(Main.parent,tr("No existing audio markers in this layer to offset from."));
307 return null;
308 }
309
310 // make our new marker
311 AudioMarker newAudioMarker = AudioMarker.create(Main.proj.eastNorth2latlon(en),
312 AudioMarker.inventName(offset), AudioPlayer.url().toString(), this, time, offset);
313
314 // insert it at the right place in a copy the collection
315 Collection<Marker> newData = new ArrayList<Marker>();
316 am = null;
317 AudioMarker ret = newAudioMarker; // save to have return value
318 for (Marker m : data) {
319 if (m.getClass() == AudioMarker.class) {
320 am = (AudioMarker) m;
321 if (newAudioMarker != null && offset < am.offset) {
322 newAudioMarker.adjustOffset(am.syncOffset()); // i.e. same as predecessor
323 newData.add(newAudioMarker);
324 newAudioMarker = null;
325 }
326 }
327 newData.add(m);
328 }
329
330 if (newAudioMarker != null) {
331 if (am != null)
332 newAudioMarker.adjustOffset(am.syncOffset()); // i.e. same as predecessor
333 newData.add(newAudioMarker); // insert at end
334 }
335
336 // replace the collection
337 data.clear();
338 data.addAll(newData);
339 return ret;
340 }
341
342 public static void playAudio() {
343 if (Main.map == null || Main.map.mapView == null)
344 return;
345 for (Layer layer : Main.map.mapView.getAllLayers()) {
346 if (layer.getClass() == MarkerLayer.class) {
347 MarkerLayer markerLayer = (MarkerLayer) layer;
348 for (Marker marker : markerLayer.data) {
349 if (marker.getClass() == AudioMarker.class) {
350 ((AudioMarker)marker).play();
351 break;
352 }
353 }
354 }
355 }
356 }
357
358 public static void playNextMarker() {
359 playAdjacentMarker(true);
360 }
361
362 public static void playPreviousMarker() {
363 playAdjacentMarker(false);
364 }
365
366 private static void playAdjacentMarker(boolean next) {
367 Marker startMarker = AudioMarker.recentlyPlayedMarker();
368 if (startMarker == null) {
369 // message?
370 return;
371 }
372 Marker previousMarker = null;
373 boolean nextTime = false;
374 if (Main.map == null || Main.map.mapView == null)
375 return;
376 for (Layer layer : Main.map.mapView.getAllLayers()) {
377 if (layer.getClass() == MarkerLayer.class) {
378 MarkerLayer markerLayer = (MarkerLayer) layer;
379 for (Marker marker : markerLayer.data) {
380 if (marker == startMarker) {
381 if (next) {
382 nextTime = true;
383 } else {
384 if (previousMarker == null)
385 previousMarker = startMarker; // if no previous one, play the first one again
386 ((AudioMarker)previousMarker).play();
387 break;
388 }
389 } else if (nextTime && marker.getClass() == AudioMarker.class) {
390 ((AudioMarker)marker).play();
391 return;
392 }
393 if (marker.getClass() == AudioMarker.class)
394 previousMarker = marker;
395 }
396 if (nextTime) {
397 // there was no next marker in that layer, so play the last one again
398 ((AudioMarker)startMarker).play();
399 return;
400 }
401 }
402 }
403 }
404
405}
Note: See TracBrowser for help on using the repository browser.