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

Last change on this file since 5501 was 5501, checked in by bastiK, 12 years ago

add session support for gpx layers (see #4029)

  • Property svn:eol-style set to native
File size: 18.4 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.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.marktr;
6import static org.openstreetmap.josm.tools.I18n.tr;
7import static org.openstreetmap.josm.tools.I18n.trn;
8
9import java.awt.Color;
10import java.awt.Component;
11import java.awt.Graphics2D;
12import java.awt.Point;
13import java.awt.event.ActionEvent;
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;
20import java.util.List;
21
22import javax.swing.AbstractAction;
23import javax.swing.Action;
24import javax.swing.Icon;
25import javax.swing.JCheckBoxMenuItem;
26import javax.swing.JOptionPane;
27import javax.swing.SwingUtilities;
28
29import org.openstreetmap.josm.Main;
30import org.openstreetmap.josm.actions.RenameLayerAction;
31import org.openstreetmap.josm.data.Bounds;
32import org.openstreetmap.josm.data.coor.LatLon;
33import org.openstreetmap.josm.data.gpx.GpxData;
34import org.openstreetmap.josm.data.gpx.GpxLink;
35import org.openstreetmap.josm.data.gpx.WayPoint;
36import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
37import org.openstreetmap.josm.gui.MapView;
38import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
39import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
40import org.openstreetmap.josm.gui.layer.CustomizeColor;
41import org.openstreetmap.josm.gui.layer.GpxLayer;
42import org.openstreetmap.josm.gui.layer.JumpToMarkerActions.JumpToMarkerLayer;
43import org.openstreetmap.josm.gui.layer.JumpToMarkerActions.JumpToNextMarker;
44import org.openstreetmap.josm.gui.layer.JumpToMarkerActions.JumpToPreviousMarker;
45import org.openstreetmap.josm.gui.layer.Layer;
46import org.openstreetmap.josm.tools.AudioPlayer;
47import org.openstreetmap.josm.tools.ImageProvider;
48
49/**
50 * A layer holding markers.
51 *
52 * Markers are GPS points with a name and, optionally, a symbol code attached;
53 * marker layers can be created from waypoints when importing raw GPS data,
54 * but they may also come from other sources.
55 *
56 * The symbol code is for future use.
57 *
58 * The data is read only.
59 */
60public class MarkerLayer extends Layer implements JumpToMarkerLayer {
61
62 /**
63 * A list of markers.
64 */
65 public final List<Marker> data;
66 private boolean mousePressed = false;
67 public GpxLayer fromLayer = null;
68 private Marker currentMarker;
69
70 @Deprecated
71 public MarkerLayer(GpxData indata, String name, File associatedFile, GpxLayer fromLayer, boolean addMouseHandlerInConstructor) {
72 this(indata, name, associatedFile, fromLayer);
73 }
74
75 public MarkerLayer(GpxData indata, String name, File associatedFile, GpxLayer fromLayer) {
76 super(name);
77 this.setAssociatedFile(associatedFile);
78 this.data = new ArrayList<Marker>();
79 this.fromLayer = fromLayer;
80 double firstTime = -1.0;
81 String lastLinkedFile = "";
82
83 for (WayPoint wpt : indata.waypoints) {
84 /* calculate time differences in waypoints */
85 double time = wpt.time;
86 boolean wpt_has_link = wpt.attr.containsKey(GpxData.META_LINKS);
87 if (firstTime < 0 && wpt_has_link) {
88 firstTime = time;
89 for (GpxLink oneLink : (Collection<GpxLink>) wpt.attr.get(GpxData.META_LINKS)) {
90 lastLinkedFile = oneLink.uri;
91 break;
92 }
93 }
94 if (wpt_has_link) {
95 for (GpxLink oneLink : (Collection<GpxLink>) wpt.attr.get(GpxData.META_LINKS)) {
96 if (!oneLink.uri.equals(lastLinkedFile)) {
97 firstTime = time;
98 }
99 lastLinkedFile = oneLink.uri;
100 break;
101 }
102 }
103 Marker m = Marker.createMarker(wpt, indata.storageFile, this, time, time - firstTime);
104 if (m != null) {
105 data.add(m);
106 }
107 }
108 }
109
110 @Override
111 public void hookUpMapView() {
112 Main.map.mapView.addMouseListener(new MouseAdapter() {
113 @Override public void mousePressed(MouseEvent e) {
114 if (e.getButton() != MouseEvent.BUTTON1)
115 return;
116 boolean mousePressedInButton = false;
117 if (e.getPoint() != null) {
118 for (Marker mkr : data) {
119 if (mkr.containsPoint(e.getPoint())) {
120 mousePressedInButton = true;
121 break;
122 }
123 }
124 }
125 if (! mousePressedInButton)
126 return;
127 mousePressed = true;
128 if (isVisible()) {
129 Main.map.mapView.repaint();
130 }
131 }
132 @Override public void mouseReleased(MouseEvent ev) {
133 if (ev.getButton() != MouseEvent.BUTTON1 || ! mousePressed)
134 return;
135 mousePressed = false;
136 if (!isVisible())
137 return;
138 if (ev.getPoint() != null) {
139 for (Marker mkr : data) {
140 if (mkr.containsPoint(ev.getPoint())) {
141 mkr.actionPerformed(new ActionEvent(this, 0, null));
142 }
143 }
144 }
145 Main.map.mapView.repaint();
146 }
147 });
148 }
149
150 /**
151 * Return a static icon.
152 */
153 @Override public Icon getIcon() {
154 return ImageProvider.get("layer", "marker_small");
155 }
156
157 @Override
158 public Color getColor(boolean ignoreCustom)
159 {
160 String name = getName();
161 return Main.pref.getColor(marktr("gps marker"), name != null ? "layer "+name : null, Color.gray);
162 }
163
164 /* for preferences */
165 static public Color getGenericColor()
166 {
167 return Main.pref.getColor(marktr("gps marker"), Color.gray);
168 }
169
170 @Override public void paint(Graphics2D g, MapView mv, Bounds box) {
171 boolean showTextOrIcon = isTextOrIconShown();
172 g.setColor(getColor(true));
173
174 if (mousePressed) {
175 boolean mousePressedTmp = mousePressed;
176 Point mousePos = mv.getMousePosition(); // Get mouse position only when necessary (it's the slowest part of marker layer painting)
177 for (Marker mkr : data) {
178 if (mousePos != null && mkr.containsPoint(mousePos)) {
179 mkr.paint(g, mv, mousePressedTmp, showTextOrIcon);
180 mousePressedTmp = false;
181 }
182 }
183 } else {
184 for (Marker mkr : data) {
185 mkr.paint(g, mv, false, showTextOrIcon);
186 }
187 }
188 }
189
190 @Override public String getToolTipText() {
191 return data.size()+" "+trn("marker", "markers", data.size());
192 }
193
194 @Override public void mergeFrom(Layer from) {
195 MarkerLayer layer = (MarkerLayer)from;
196 data.addAll(layer.data);
197 }
198
199 @Override public boolean isMergable(Layer other) {
200 return other instanceof MarkerLayer;
201 }
202
203 @Override public void visitBoundingBox(BoundingXYVisitor v) {
204 for (Marker mkr : data) {
205 v.visit(mkr.getEastNorth());
206 }
207 }
208
209 @Override public Object getInfoComponent() {
210 return "<html>"+trn("{0} consists of {1} marker", "{0} consists of {1} markers", data.size(), getName(), data.size()) + "</html>";
211 }
212
213 @Override public Action[] getMenuEntries() {
214 Collection<Action> components = new ArrayList<Action>();
215 components.add(LayerListDialog.getInstance().createShowHideLayerAction());
216 components.add(new ShowHideMarkerText(this));
217 components.add(LayerListDialog.getInstance().createDeleteLayerAction());
218 components.add(SeparatorLayerAction.INSTANCE);
219 components.add(new CustomizeColor(this));
220 components.add(SeparatorLayerAction.INSTANCE);
221 components.add(new SynchronizeAudio());
222 if (Main.pref.getBoolean("marker.traceaudio", true)) {
223 components.add (new MoveAudio());
224 }
225 components.add(new JumpToNextMarker(this));
226 components.add(new JumpToPreviousMarker(this));
227 components.add(new RenameLayerAction(getAssociatedFile(), this));
228 components.add(SeparatorLayerAction.INSTANCE);
229 components.add(new LayerListPopup.InfoAction(this));
230 return components.toArray(new Action[0]);
231 }
232
233 public boolean synchronizeAudioMarkers(AudioMarker startMarker) {
234 if (startMarker != null && ! data.contains(startMarker)) {
235 startMarker = null;
236 }
237 if (startMarker == null) {
238 // find the first audioMarker in this layer
239 for (Marker m : data) {
240 if (m instanceof AudioMarker) {
241 startMarker = (AudioMarker) m;
242 break;
243 }
244 }
245 }
246 if (startMarker == null)
247 return false;
248
249 // apply adjustment to all subsequent audio markers in the layer
250 double adjustment = AudioPlayer.position() - startMarker.offset; // in seconds
251 boolean seenStart = false;
252 URL url = startMarker.url();
253 for (Marker m : data) {
254 if (m == startMarker) {
255 seenStart = true;
256 }
257 if (seenStart) {
258 AudioMarker ma = (AudioMarker) m; // it must be an AudioMarker
259 if (ma.url().equals(url)) {
260 ma.adjustOffset(adjustment);
261 }
262 }
263 }
264 return true;
265 }
266
267 public AudioMarker addAudioMarker(double time, LatLon coor) {
268 // find first audio marker to get absolute start time
269 double offset = 0.0;
270 AudioMarker am = null;
271 for (Marker m : data) {
272 if (m.getClass() == AudioMarker.class) {
273 am = (AudioMarker)m;
274 offset = time - am.time;
275 break;
276 }
277 }
278 if (am == null) {
279 JOptionPane.showMessageDialog(
280 Main.parent,
281 tr("No existing audio markers in this layer to offset from."),
282 tr("Error"),
283 JOptionPane.ERROR_MESSAGE
284 );
285 return null;
286 }
287
288 // make our new marker
289 AudioMarker newAudioMarker = new AudioMarker(coor,
290 null, AudioPlayer.url(), this, time, offset);
291
292 // insert it at the right place in a copy the collection
293 Collection<Marker> newData = new ArrayList<Marker>();
294 am = null;
295 AudioMarker ret = newAudioMarker; // save to have return value
296 for (Marker m : data) {
297 if (m.getClass() == AudioMarker.class) {
298 am = (AudioMarker) m;
299 if (newAudioMarker != null && offset < am.offset) {
300 newAudioMarker.adjustOffset(am.syncOffset()); // i.e. same as predecessor
301 newData.add(newAudioMarker);
302 newAudioMarker = null;
303 }
304 }
305 newData.add(m);
306 }
307
308 if (newAudioMarker != null) {
309 if (am != null) {
310 newAudioMarker.adjustOffset(am.syncOffset()); // i.e. same as predecessor
311 }
312 newData.add(newAudioMarker); // insert at end
313 }
314
315 // replace the collection
316 data.clear();
317 data.addAll(newData);
318 return ret;
319 }
320
321 public void jumpToNextMarker() {
322 if (currentMarker == null) {
323 currentMarker = data.get(0);
324 } else {
325 boolean foundCurrent = false;
326 for (Marker m: data) {
327 if (foundCurrent) {
328 currentMarker = m;
329 break;
330 } else if (currentMarker == m) {
331 foundCurrent = true;
332 }
333 }
334 }
335 Main.map.mapView.zoomTo(currentMarker.getEastNorth());
336 }
337
338 public void jumpToPreviousMarker() {
339 if (currentMarker == null) {
340 currentMarker = data.get(data.size() - 1);
341 } else {
342 boolean foundCurrent = false;
343 for (int i=data.size() - 1; i>=0; i--) {
344 Marker m = data.get(i);
345 if (foundCurrent) {
346 currentMarker = m;
347 break;
348 } else if (currentMarker == m) {
349 foundCurrent = true;
350 }
351 }
352 }
353 Main.map.mapView.zoomTo(currentMarker.getEastNorth());
354 }
355
356 public static void playAudio() {
357 playAdjacentMarker(null, true);
358 }
359
360 public static void playNextMarker() {
361 playAdjacentMarker(AudioMarker.recentlyPlayedMarker(), true);
362 }
363
364 public static void playPreviousMarker() {
365 playAdjacentMarker(AudioMarker.recentlyPlayedMarker(), false);
366 }
367
368 private static Marker getAdjacentMarker(Marker startMarker, boolean next, Layer layer) {
369 Marker previousMarker = null;
370 boolean nextTime = false;
371 if (layer.getClass() == MarkerLayer.class) {
372 MarkerLayer markerLayer = (MarkerLayer) layer;
373 for (Marker marker : markerLayer.data) {
374 if (marker == startMarker) {
375 if (next) {
376 nextTime = true;
377 } else {
378 if (previousMarker == null) {
379 previousMarker = startMarker; // if no previous one, play the first one again
380 }
381 return previousMarker;
382 }
383 }
384 else if (marker.getClass() == AudioMarker.class)
385 {
386 if(nextTime || startMarker == null)
387 return marker;
388 previousMarker = marker;
389 }
390 }
391 if (nextTime) // there was no next marker in that layer, so play the last one again
392 return startMarker;
393 }
394 return null;
395 }
396
397 private static void playAdjacentMarker(Marker startMarker, boolean next) {
398 Marker m = null;
399 if (Main.map == null || Main.map.mapView == null)
400 return;
401 Layer l = Main.map.mapView.getActiveLayer();
402 if(l != null) {
403 m = getAdjacentMarker(startMarker, next, l);
404 }
405 if(m == null)
406 {
407 for (Layer layer : Main.map.mapView.getAllLayers())
408 {
409 m = getAdjacentMarker(startMarker, next, layer);
410 if(m != null) {
411 break;
412 }
413 }
414 }
415 if(m != null) {
416 ((AudioMarker)m).play();
417 }
418 }
419
420 /**
421 * Get state of text display.
422 * @return <code>true</code> if text should be shown, <code>false</code> otherwise.
423 */
424 private boolean isTextOrIconShown() {
425 String current = Main.pref.get("marker.show "+getName(),"show");
426 return "show".equalsIgnoreCase(current);
427 }
428
429 public static final class ShowHideMarkerText extends AbstractAction implements LayerAction {
430 private final MarkerLayer layer;
431
432 public ShowHideMarkerText(MarkerLayer layer) {
433 super(tr("Show Text/Icons"), ImageProvider.get("dialogs", "showhide"));
434 putValue(SHORT_DESCRIPTION, tr("Toggle visible state of the marker text and icons."));
435 putValue("help", ht("/Action/ShowHideTextIcons"));
436 this.layer = layer;
437 }
438
439
440 public void actionPerformed(ActionEvent e) {
441 Main.pref.put("marker.show "+layer.getName(), layer.isTextOrIconShown() ? "hide" : "show");
442 Main.map.mapView.repaint();
443 }
444
445
446 @Override
447 public Component createMenuComponent() {
448 JCheckBoxMenuItem showMarkerTextItem = new JCheckBoxMenuItem(this);
449 showMarkerTextItem.setState(layer.isTextOrIconShown());
450 return showMarkerTextItem;
451 }
452
453 @Override
454 public boolean supportLayers(List<Layer> layers) {
455 return layers.size() == 1 && layers.get(0) instanceof MarkerLayer;
456 }
457 }
458
459
460 private class SynchronizeAudio extends AbstractAction {
461
462 public SynchronizeAudio() {
463 super(tr("Synchronize Audio"), ImageProvider.get("audio-sync"));
464 putValue("help", ht("/Action/SynchronizeAudio"));
465 }
466
467 @Override
468 public void actionPerformed(ActionEvent e) {
469 if (! AudioPlayer.paused()) {
470 JOptionPane.showMessageDialog(
471 Main.parent,
472 tr("You need to pause audio at the moment when you hear your synchronization cue."),
473 tr("Warning"),
474 JOptionPane.WARNING_MESSAGE
475 );
476 return;
477 }
478 AudioMarker recent = AudioMarker.recentlyPlayedMarker();
479 if (synchronizeAudioMarkers(recent)) {
480 JOptionPane.showMessageDialog(
481 Main.parent,
482 tr("Audio synchronized at point {0}.", recent.getText()),
483 tr("Information"),
484 JOptionPane.INFORMATION_MESSAGE
485 );
486 } else {
487 JOptionPane.showMessageDialog(
488 Main.parent,
489 tr("Unable to synchronize in layer being played."),
490 tr("Error"),
491 JOptionPane.ERROR_MESSAGE
492 );
493 }
494 }
495 }
496
497 private class MoveAudio extends AbstractAction {
498
499 public MoveAudio() {
500 super(tr("Make Audio Marker at Play Head"), ImageProvider.get("addmarkers"));
501 putValue("help", ht("/Action/MakeAudioMarkerAtPlayHead"));
502 }
503
504 @Override
505 public void actionPerformed(ActionEvent e) {
506 if (! AudioPlayer.paused()) {
507 JOptionPane.showMessageDialog(
508 Main.parent,
509 tr("You need to have paused audio at the point on the track where you want the marker."),
510 tr("Warning"),
511 JOptionPane.WARNING_MESSAGE
512 );
513 return;
514 }
515 PlayHeadMarker playHeadMarker = Main.map.mapView.playHeadMarker;
516 if (playHeadMarker == null)
517 return;
518 addAudioMarker(playHeadMarker.time, playHeadMarker.getCoor());
519 Main.map.mapView.repaint();
520 }
521 }
522
523}
Note: See TracBrowser for help on using the repository browser.