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

Last change on this file since 8342 was 8342, checked in by Don-vip, 9 years ago

code style - Close curly brace and the next "else", "catch" and "finally" keywords should be located on the same line

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