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

Last change on this file since 6084 was 6084, checked in by bastiK, 11 years ago

see #8902 - add missing @Override annotations (patch by shinigami)

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