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

Last change on this file was 18972, checked in by taylor.smock, 2 months ago

See #23465: Add additional javadoc comments

This also fixes some sonarlint issues

  • Property svn:eol-style set to native
File size: 29.8 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.BasicStroke;
10import java.awt.Color;
11import java.awt.Component;
12import java.awt.Graphics2D;
13import java.awt.Point;
14import java.awt.event.ActionEvent;
15import java.awt.event.MouseAdapter;
16import java.awt.event.MouseEvent;
17import java.io.File;
18import java.net.URI;
19import java.net.URISyntaxException;
20import java.util.ArrayList;
21import java.util.Collection;
22import java.util.Collections;
23import java.util.Comparator;
24import java.util.HashMap;
25import java.util.List;
26import java.util.ListIterator;
27import java.util.Map;
28import java.util.Objects;
29import java.util.Optional;
30
31import javax.swing.AbstractAction;
32import javax.swing.Action;
33import javax.swing.Icon;
34import javax.swing.JCheckBoxMenuItem;
35import javax.swing.JOptionPane;
36
37import org.openstreetmap.josm.actions.AutoScaleAction;
38import org.openstreetmap.josm.actions.RenameLayerAction;
39import org.openstreetmap.josm.data.Bounds;
40import org.openstreetmap.josm.data.coor.LatLon;
41import org.openstreetmap.josm.data.gpx.GpxConstants;
42import org.openstreetmap.josm.data.gpx.GpxData;
43import org.openstreetmap.josm.data.gpx.GpxExtension;
44import org.openstreetmap.josm.data.gpx.GpxLink;
45import org.openstreetmap.josm.data.gpx.IGpxLayerPrefs;
46import org.openstreetmap.josm.data.gpx.WayPoint;
47import org.openstreetmap.josm.data.imagery.street_level.IImageEntry;
48import org.openstreetmap.josm.data.osm.BBox;
49import org.openstreetmap.josm.data.osm.QuadBuckets;
50import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
51import org.openstreetmap.josm.data.preferences.IntegerProperty;
52import org.openstreetmap.josm.data.preferences.NamedColorProperty;
53import org.openstreetmap.josm.data.preferences.StrokeProperty;
54import org.openstreetmap.josm.gui.MainApplication;
55import org.openstreetmap.josm.gui.MapView;
56import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
57import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
58import org.openstreetmap.josm.gui.layer.CustomizeColor;
59import org.openstreetmap.josm.gui.layer.GpxLayer;
60import org.openstreetmap.josm.gui.layer.JumpToMarkerActions.JumpToMarkerLayer;
61import org.openstreetmap.josm.gui.layer.JumpToMarkerActions.JumpToNextMarker;
62import org.openstreetmap.josm.gui.layer.JumpToMarkerActions.JumpToPreviousMarker;
63import org.openstreetmap.josm.gui.layer.Layer;
64import org.openstreetmap.josm.gui.layer.geoimage.IGeoImageLayer;
65import org.openstreetmap.josm.gui.layer.geoimage.RemoteEntry;
66import org.openstreetmap.josm.gui.layer.gpx.ConvertFromMarkerLayerAction;
67import org.openstreetmap.josm.gui.preferences.display.GPXSettingsPanel;
68import org.openstreetmap.josm.io.audio.AudioPlayer;
69import org.openstreetmap.josm.spi.preferences.Config;
70import org.openstreetmap.josm.tools.ColorHelper;
71import org.openstreetmap.josm.tools.ImageProvider;
72import org.openstreetmap.josm.tools.ListenerList;
73import org.openstreetmap.josm.tools.Logging;
74import org.openstreetmap.josm.tools.Utils;
75
76/**
77 * A layer holding markers.
78 * <p>
79 * Markers are GPS points with a name and, optionally, a symbol code attached;
80 * marker layers can be created from waypoints when importing raw GPS data,
81 * but they may also come from other sources.
82 * <p>
83 * The symbol code is for future use.
84 * <p>
85 * The data is read only.
86 */
87public class MarkerLayer extends Layer implements JumpToMarkerLayer, IGeoImageLayer {
88
89 /**
90 * A list of markers.
91 */
92 public final MarkerData data;
93 private boolean mousePressed;
94 public GpxLayer fromLayer;
95 private Marker currentMarker;
96 public AudioMarker syncAudioMarker;
97 private Color color;
98 private Color realcolor;
99 final int markerSize = new IntegerProperty("draw.rawgps.markers.size", 4).get();
100 final BasicStroke markerStroke = new StrokeProperty("draw.rawgps.markers.stroke", "1").get();
101
102 private final ListenerList<IGeoImageLayer.ImageChangeListener> imageChangeListenerListenerList = ListenerList.create();
103 private MarkerMouseAdapter mouseAdapter;
104 private MapView mapView;
105
106 /**
107 * The default color that is used for drawing markers.
108 */
109 public static final NamedColorProperty DEFAULT_COLOR_PROPERTY = new NamedColorProperty(marktr("gps marker"), Color.magenta);
110
111 /**
112 * Constructs a new {@code MarkerLayer}.
113 * @param indata The GPX data for this layer
114 * @param name The marker layer name
115 * @param associatedFile The associated GPX file
116 * @param fromLayer The associated GPX layer
117 */
118 public MarkerLayer(GpxData indata, String name, File associatedFile, GpxLayer fromLayer) {
119 super(name);
120 this.setAssociatedFile(associatedFile);
121 this.data = new MarkerData();
122 this.fromLayer = fromLayer;
123 double firstTime = -1.0;
124 String lastLinkedFile = "";
125
126 if (fromLayer == null || fromLayer.data == null) {
127 data.ownLayerPrefs = indata.getLayerPrefs();
128 }
129
130 String cs = GPXSettingsPanel.tryGetDataPrefLocal(data, "markers.color");
131 Color c = null;
132 if (cs != null) {
133 c = ColorHelper.html2color(cs);
134 if (c == null) {
135 Logging.warn("Could not read marker color: " + cs);
136 }
137 }
138 setPrivateColors(c);
139
140 for (WayPoint wpt : indata.waypoints) {
141 /* calculate time differences in waypoints */
142 double time = wpt.getTime();
143 boolean wptHasLink = wpt.attr.containsKey(GpxConstants.META_LINKS);
144 if (firstTime < 0 && wptHasLink) {
145 firstTime = time;
146 for (GpxLink oneLink : wpt.<GpxLink>getCollection(GpxConstants.META_LINKS)) {
147 lastLinkedFile = oneLink.uri;
148 break;
149 }
150 }
151 if (wptHasLink) {
152 for (GpxLink oneLink : wpt.<GpxLink>getCollection(GpxConstants.META_LINKS)) {
153 String uri = oneLink.uri;
154 if (uri != null) {
155 if (!uri.equals(lastLinkedFile)) {
156 firstTime = time;
157 }
158 lastLinkedFile = uri;
159 break;
160 }
161 }
162 }
163 Double offset = null;
164 // If we have an explicit offset, take it.
165 // Otherwise, for a group of markers with the same Link-URI (e.g. an
166 // audio file) calculate the offset relative to the first marker of
167 // that group. This way the user can jump to the corresponding
168 // playback positions in a long audio track.
169 GpxExtension offsetExt = wpt.getExtensions().get("josm", "offset");
170 if (offsetExt != null && offsetExt.getValue() != null) {
171 try {
172 offset = Double.valueOf(offsetExt.getValue());
173 } catch (NumberFormatException nfe) {
174 Logging.warn(nfe);
175 }
176 }
177 if (offset == null) {
178 offset = time - firstTime;
179 }
180 final Collection<Marker> markers = Marker.createMarkers(wpt, indata.storageFile, this, time, offset);
181 if (markers != null) {
182 data.addAll(markers);
183 }
184 }
185 }
186
187 @Override
188 public synchronized void destroy() {
189 if (data.contains(AudioMarker.recentlyPlayedMarker())) {
190 AudioMarker.resetRecentlyPlayedMarker();
191 }
192 syncAudioMarker = null;
193 currentMarker = null;
194 fromLayer = null;
195 data.forEach(Marker::destroy);
196 data.clear();
197 if (mouseAdapter != null && mapView != null)
198 mapView.removeMouseListener(mouseAdapter);
199 super.destroy();
200 }
201
202 @Override
203 public LayerPainter attachToMapView(MapViewEvent event) {
204 if (mapView != null) {
205 Logging.warn("MarkerLayer was already attached to a MapView");
206 }
207 mapView = event.getMapView();
208 mouseAdapter = new MarkerMouseAdapter();
209 mapView.addMouseListener(mouseAdapter);
210
211 if (event.getMapView().playHeadMarker == null) {
212 event.getMapView().playHeadMarker = PlayHeadMarker.create();
213 }
214
215 return super.attachToMapView(event);
216 }
217
218 /**
219 * Return a static icon.
220 */
221 @Override
222 public Icon getIcon() {
223 return ImageProvider.get("layer", "marker_small");
224 }
225
226 @Override
227 public void paint(Graphics2D g, MapView mv, Bounds box) {
228 boolean showTextOrIcon = isTextOrIconShown();
229 g.setColor(realcolor);
230 if (mousePressed) {
231 boolean mousePressedTmp = mousePressed;
232 Point mousePos = mv.getMousePosition(); // Get mouse position only when necessary (it's the slowest part of marker layer painting)
233 for (Marker mkr : data) {
234 if (mousePos != null && mkr.containsPoint(mousePos)) {
235 mkr.paint(g, mv, mousePressedTmp, showTextOrIcon);
236 mousePressedTmp = false;
237 }
238 }
239 } else {
240 for (Marker mkr : data) {
241 mkr.paint(g, mv, false, showTextOrIcon);
242 }
243 }
244 }
245
246 @Override
247 public String getToolTipText() {
248 return Integer.toString(data.size())+' '+trn("marker", "markers", data.size());
249 }
250
251 @Override
252 public void mergeFrom(Layer from) {
253 if (from instanceof MarkerLayer) {
254 data.addAll(((MarkerLayer) from).data);
255 data.sort(Comparator.comparingDouble(o -> o.time));
256 }
257 }
258
259 @Override public boolean isMergable(Layer other) {
260 return other instanceof MarkerLayer;
261 }
262
263 @Override public void visitBoundingBox(BoundingXYVisitor v) {
264 for (Marker mkr : data) {
265 v.visit(mkr);
266 }
267 }
268
269 @Override public Object getInfoComponent() {
270 return "<html>"+trn("{0} consists of {1} marker", "{0} consists of {1} markers",
271 data.size(), Utils.escapeReservedCharactersHTML(getName()), data.size()) + "</html>";
272 }
273
274 @Override public Action[] getMenuEntries() {
275 Collection<Action> components = new ArrayList<>();
276 components.add(LayerListDialog.getInstance().createShowHideLayerAction());
277 components.add(new ShowHideMarkerText(this));
278 components.add(LayerListDialog.getInstance().createDeleteLayerAction());
279 components.add(MainApplication.getMenu().autoScaleActions.get(AutoScaleAction.AutoScaleMode.LAYER));
280 components.add(LayerListDialog.getInstance().createMergeLayerAction(this));
281 components.add(SeparatorLayerAction.INSTANCE);
282 components.add(new CustomizeColor(this));
283 components.add(SeparatorLayerAction.INSTANCE);
284 components.add(new SynchronizeAudio());
285 if (Config.getPref().getBoolean("marker.traceaudio", true)) {
286 components.add(new MoveAudio());
287 }
288 components.add(new JumpToNextMarker(this));
289 components.add(new JumpToPreviousMarker(this));
290 components.add(new ConvertFromMarkerLayerAction(this));
291 components.add(new RenameLayerAction(getAssociatedFile(), this));
292 components.add(SeparatorLayerAction.INSTANCE);
293 components.add(new LayerListPopup.InfoAction(this));
294 return components.toArray(new Action[0]);
295 }
296
297 public boolean synchronizeAudioMarkers(final AudioMarker startMarker) {
298 syncAudioMarker = startMarker;
299 if (syncAudioMarker != null && !data.contains(syncAudioMarker)) {
300 syncAudioMarker = null;
301 }
302 if (syncAudioMarker == null) {
303 // find the first audioMarker in this layer
304 syncAudioMarker = Utils.filteredCollection(data, AudioMarker.class).stream()
305 .findFirst().orElse(syncAudioMarker);
306 }
307 if (syncAudioMarker == null)
308 return false;
309
310 // apply adjustment to all subsequent audio markers in the layer
311 double adjustment = AudioPlayer.position() - syncAudioMarker.offset; // in seconds
312 boolean seenStart = false;
313 try {
314 URI uri = syncAudioMarker.url().toURI();
315 for (Marker m : data) {
316 if (m == syncAudioMarker) {
317 seenStart = true;
318 }
319 if (seenStart && m instanceof AudioMarker) {
320 AudioMarker ma = (AudioMarker) m;
321 // Do not ever call URL.equals but use URI.equals instead to avoid Internet connection
322 // See http://michaelscharf.blogspot.fr/2006/11/javaneturlequals-and-hashcode-make.html for details
323 if (ma.url().toURI().equals(uri)) {
324 ma.adjustOffset(adjustment);
325 }
326 }
327 }
328 } catch (URISyntaxException e) {
329 Logging.warn(e);
330 }
331 return true;
332 }
333
334 public AudioMarker addAudioMarker(double time, LatLon coor) {
335 // find first audio marker to get absolute start time
336 double offset = 0.0;
337 AudioMarker am = null;
338 for (Marker m : data) {
339 if (m.getClass() == AudioMarker.class) {
340 am = (AudioMarker) m;
341 offset = time - am.time;
342 break;
343 }
344 }
345 if (am == null) {
346 JOptionPane.showMessageDialog(
347 MainApplication.getMainFrame(),
348 tr("No existing audio markers in this layer to offset from."),
349 tr("Error"),
350 JOptionPane.ERROR_MESSAGE
351 );
352 return null;
353 }
354
355 // make our new marker
356 AudioMarker newAudioMarker = new AudioMarker(coor,
357 null, AudioPlayer.url(), this, time, offset);
358
359 // insert it at the right place in a copy the collection
360 Collection<Marker> newData = new ArrayList<>();
361 am = null;
362 AudioMarker ret = newAudioMarker; // save to have return value
363 for (Marker m : data) {
364 if (m.getClass() == AudioMarker.class) {
365 am = (AudioMarker) m;
366 if (newAudioMarker != null && offset < am.offset) {
367 newAudioMarker.adjustOffset(am.syncOffset()); // i.e. same as predecessor
368 newData.add(newAudioMarker);
369 newAudioMarker = null;
370 }
371 }
372 newData.add(m);
373 }
374
375 if (newAudioMarker != null) {
376 if (am != null) {
377 newAudioMarker.adjustOffset(am.syncOffset()); // i.e. same as predecessor
378 }
379 newData.add(newAudioMarker); // insert at end
380 }
381
382 // replace the collection
383 data.clear();
384 data.addAll(newData);
385 return ret;
386 }
387
388 @Override
389 public void jumpToNextMarker() {
390 if (currentMarker == null) {
391 currentMarker = data.get(0);
392 } else {
393 boolean foundCurrent = false;
394 for (Marker m: data) {
395 if (foundCurrent) {
396 currentMarker = m;
397 break;
398 } else if (currentMarker == m) {
399 foundCurrent = true;
400 }
401 }
402 }
403 MainApplication.getMap().mapView.zoomTo(currentMarker);
404 }
405
406 @Override
407 public void jumpToPreviousMarker() {
408 if (currentMarker == null) {
409 currentMarker = data.get(data.size() - 1);
410 } else {
411 boolean foundCurrent = false;
412 for (int i = data.size() - 1; i >= 0; i--) {
413 Marker m = data.get(i);
414 if (foundCurrent) {
415 currentMarker = m;
416 break;
417 } else if (currentMarker == m) {
418 foundCurrent = true;
419 }
420 }
421 }
422 MainApplication.getMap().mapView.zoomTo(currentMarker);
423 }
424
425 /**
426 * Set the current marker
427 * @param newMarker The marker to set
428 */
429 void setCurrentMarker(Marker newMarker) {
430 this.currentMarker = newMarker;
431 }
432
433 public static void playAudio() {
434 playAdjacentMarker(null, true);
435 }
436
437 public static void playNextMarker() {
438 playAdjacentMarker(AudioMarker.recentlyPlayedMarker(), true);
439 }
440
441 public static void playPreviousMarker() {
442 playAdjacentMarker(AudioMarker.recentlyPlayedMarker(), false);
443 }
444
445 private static Marker getAdjacentMarker(Marker startMarker, boolean next, Layer layer) {
446 Marker previousMarker = null;
447 boolean nextTime = false;
448 if (layer.getClass() == MarkerLayer.class) {
449 MarkerLayer markerLayer = (MarkerLayer) layer;
450 for (Marker marker : markerLayer.data) {
451 if (marker == startMarker) {
452 if (next) {
453 nextTime = true;
454 } else {
455 if (previousMarker == null) {
456 previousMarker = startMarker; // if no previous one, play the first one again
457 }
458 return previousMarker;
459 }
460 } else if (marker.getClass() == AudioMarker.class) {
461 if (nextTime || startMarker == null)
462 return marker;
463 previousMarker = marker;
464 }
465 }
466 if (nextTime) // there was no next marker in that layer, so play the last one again
467 return startMarker;
468 }
469 return null;
470 }
471
472 private static void playAdjacentMarker(Marker startMarker, boolean next) {
473 if (!MainApplication.isDisplayingMapView())
474 return;
475 Marker m = null;
476 Layer l = MainApplication.getLayerManager().getActiveLayer();
477 if (l != null) {
478 m = getAdjacentMarker(startMarker, next, l);
479 }
480 if (m == null) {
481 for (Layer layer : MainApplication.getLayerManager().getLayers()) {
482 m = getAdjacentMarker(startMarker, next, layer);
483 if (m != null) {
484 break;
485 }
486 }
487 }
488 if (m != null) {
489 ((AudioMarker) m).play();
490 }
491 }
492
493 /**
494 * Get state of text display.
495 * @return <code>true</code> if text should be shown, <code>false</code> otherwise.
496 */
497 private boolean isTextOrIconShown() {
498 return Boolean.parseBoolean(GPXSettingsPanel.getDataPref(data, "markers.show-text"));
499 }
500
501 @Override
502 public boolean hasColor() {
503 return true;
504 }
505
506 @Override
507 public Color getColor() {
508 return color;
509 }
510
511 @Override
512 public void setColor(Color color) {
513 setPrivateColors(color);
514 String cs = null;
515 if (color != null) {
516 cs = ColorHelper.color2html(color);
517 }
518 GPXSettingsPanel.putDataPrefLocal(data, "markers.color", cs);
519 invalidate();
520 }
521
522 private void setPrivateColors(Color color) {
523 this.color = color;
524 this.realcolor = Optional.ofNullable(color).orElse(DEFAULT_COLOR_PROPERTY.get());
525 }
526
527 @Override
528 public void clearSelection() {
529 this.currentMarker = null;
530 }
531
532 @Override
533 public List<? extends IImageEntry<?>> getSelection() {
534 if (this.currentMarker instanceof ImageMarker) {
535 final RemoteEntry remoteEntry = ((ImageMarker) this.currentMarker).getRemoteEntry();
536 if (remoteEntry != null) {
537 return Collections.singletonList(remoteEntry);
538 }
539 }
540 return Collections.emptyList();
541 }
542
543 @Override
544 public boolean containsImage(IImageEntry<?> imageEntry) {
545 if (imageEntry instanceof RemoteEntry) {
546 RemoteEntry entry = (RemoteEntry) imageEntry;
547 if (entry.getPos() != null && entry.getPos().isLatLonKnown()) {
548 List<Marker> markers = this.data.search(new BBox(entry.getPos()));
549 return checkIfListContainsEntry(markers, entry);
550 } else if (entry.getExifCoor() != null && entry.getExifCoor().isLatLonKnown()) {
551 List<Marker> markers = this.data.search(new BBox(entry.getExifCoor()));
552 return checkIfListContainsEntry(markers, entry);
553 } else {
554 return checkIfListContainsEntry(this.data, entry);
555 }
556 }
557 return false;
558 }
559
560 /**
561 * Check if a list contains an entry
562 * @param markerList The list to look through
563 * @param imageEntry The image entry to check
564 * @return {@code true} if the entry is in the list
565 */
566 private static boolean checkIfListContainsEntry(List<Marker> markerList, RemoteEntry imageEntry) {
567 for (Marker marker : markerList) {
568 if (marker instanceof ImageMarker) {
569 ImageMarker imageMarker = (ImageMarker) marker;
570 try {
571 if (Objects.equals(imageMarker.imageUrl.toURI(), imageEntry.getImageURI())) {
572 return true;
573 }
574 } catch (URISyntaxException e) {
575 Logging.trace(e);
576 }
577 }
578 }
579 return false;
580 }
581
582 @Override
583 public void addImageChangeListener(ImageChangeListener listener) {
584 this.imageChangeListenerListenerList.addListener(listener);
585 }
586
587 @Override
588 public void removeImageChangeListener(ImageChangeListener listener) {
589 this.imageChangeListenerListenerList.removeListener(listener);
590 }
591
592 private final class MarkerMouseAdapter extends MouseAdapter {
593 @Override
594 public void mousePressed(MouseEvent e) {
595 if (e.getButton() != MouseEvent.BUTTON1)
596 return;
597 boolean mousePressedInButton = data.stream().anyMatch(mkr -> mkr.containsPoint(e.getPoint()));
598 if (!mousePressedInButton)
599 return;
600 mousePressed = true;
601 if (isVisible()) {
602 invalidate();
603 }
604 }
605
606 @Override
607 public void mouseReleased(MouseEvent ev) {
608 if (ev.getButton() != MouseEvent.BUTTON1 || !mousePressed)
609 return;
610 mousePressed = false;
611 if (!isVisible())
612 return;
613 for (Marker mkr : data) {
614 if (mkr.containsPoint(ev.getPoint())) {
615 mkr.actionPerformed(new ActionEvent(this, 0, null));
616 }
617 }
618 invalidate();
619 }
620 }
621
622 /**
623 * Toggle visibility of the marker text and icons
624 */
625 public static final class ShowHideMarkerText extends AbstractAction implements LayerAction {
626 private final transient MarkerLayer layer;
627
628 /**
629 * Create a new {@link ShowHideMarkerText} action
630 * @param layer The layer to toggle the visible state of the marker text and icons
631 */
632 public ShowHideMarkerText(MarkerLayer layer) {
633 super(tr("Show Text/Icons"));
634 new ImageProvider("dialogs", "showhide").getResource().attachImageIcon(this, true);
635 putValue(SHORT_DESCRIPTION, tr("Toggle visible state of the marker text and icons."));
636 putValue("help", ht("/Action/ShowHideTextIcons"));
637 this.layer = layer;
638 }
639
640 @Override
641 public void actionPerformed(ActionEvent e) {
642 GPXSettingsPanel.putDataPrefLocal(layer.data, "markers.show-text", Boolean.toString(!layer.isTextOrIconShown()));
643 layer.invalidate();
644 }
645
646 @Override
647 public Component createMenuComponent() {
648 JCheckBoxMenuItem showMarkerTextItem = new JCheckBoxMenuItem(this);
649 showMarkerTextItem.setState(layer.isTextOrIconShown());
650 return showMarkerTextItem;
651 }
652
653 @Override
654 public boolean supportLayers(List<Layer> layers) {
655 return layers.size() == 1 && layers.get(0) instanceof MarkerLayer;
656 }
657 }
658
659 private class SynchronizeAudio extends AbstractAction {
660
661 /**
662 * Constructs a new {@code SynchronizeAudio} action.
663 */
664 SynchronizeAudio() {
665 super(tr("Synchronize Audio"));
666 new ImageProvider("audio-sync").getResource().attachImageIcon(this, true);
667 putValue("help", ht("/Action/SynchronizeAudio"));
668 }
669
670 @Override
671 public void actionPerformed(ActionEvent e) {
672 if (!AudioPlayer.paused()) {
673 JOptionPane.showMessageDialog(
674 MainApplication.getMainFrame(),
675 tr("You need to pause audio at the moment when you hear your synchronization cue."),
676 tr("Warning"),
677 JOptionPane.WARNING_MESSAGE
678 );
679 return;
680 }
681 AudioMarker recent = AudioMarker.recentlyPlayedMarker();
682 if (synchronizeAudioMarkers(recent)) {
683 JOptionPane.showMessageDialog(
684 MainApplication.getMainFrame(),
685 tr("Audio synchronized at point {0}.", syncAudioMarker.getText()),
686 tr("Information"),
687 JOptionPane.INFORMATION_MESSAGE
688 );
689 } else {
690 JOptionPane.showMessageDialog(
691 MainApplication.getMainFrame(),
692 tr("Unable to synchronize in layer being played."),
693 tr("Error"),
694 JOptionPane.ERROR_MESSAGE
695 );
696 }
697 }
698 }
699
700 private class MoveAudio extends AbstractAction {
701
702 MoveAudio() {
703 super(tr("Make Audio Marker at Play Head"));
704 new ImageProvider("addmarkers").getResource().attachImageIcon(this, true);
705 putValue("help", ht("/Action/MakeAudioMarkerAtPlayHead"));
706 }
707
708 @Override
709 public void actionPerformed(ActionEvent e) {
710 if (!AudioPlayer.paused()) {
711 JOptionPane.showMessageDialog(
712 MainApplication.getMainFrame(),
713 tr("You need to have paused audio at the point on the track where you want the marker."),
714 tr("Warning"),
715 JOptionPane.WARNING_MESSAGE
716 );
717 return;
718 }
719 PlayHeadMarker playHeadMarker = MainApplication.getMap().mapView.playHeadMarker;
720 if (playHeadMarker == null)
721 return;
722 addAudioMarker(playHeadMarker.time, playHeadMarker.getCoor());
723 invalidate();
724 }
725 }
726
727 /**
728 * the data of a MarkerLayer
729 * @since 18287
730 */
731 public class MarkerData extends QuadBuckets<Marker> implements List<Marker>, IGpxLayerPrefs {
732
733 private Map<String, String> ownLayerPrefs;
734 private final List<Marker> markerList = new ArrayList<>();
735
736 @Override
737 public Map<String, String> getLayerPrefs() {
738 if (ownLayerPrefs == null && fromLayer != null && fromLayer.data != null) {
739 return fromLayer.data.getLayerPrefs();
740 }
741 // fallback to own layerPrefs if the corresponding gpxLayer has already been deleted
742 // by the user or never existed when loaded from a session file
743 if (ownLayerPrefs == null) {
744 ownLayerPrefs = new HashMap<>();
745 }
746 return ownLayerPrefs;
747 }
748
749 /**
750 * Transfers the layerPrefs from the GpxData to MarkerData (when GpxData is deleted)
751 * @param gpxLayerPrefs the layerPrefs from the GpxData object
752 */
753 public void transferLayerPrefs(Map<String, String> gpxLayerPrefs) {
754 ownLayerPrefs = new HashMap<>(gpxLayerPrefs);
755 }
756
757 @Override
758 public void setModified(boolean value) {
759 if (fromLayer != null && fromLayer.data != null) {
760 fromLayer.data.setModified(value);
761 }
762 }
763
764 @Override
765 public boolean addAll(int index, Collection<? extends Marker> c) {
766 c.forEach(this::add);
767 return this.markerList.addAll(index, c);
768 }
769
770 @Override
771 public boolean addAll(Collection<? extends Marker> objects) {
772 return this.markerList.addAll(objects) && super.addAll(objects);
773 }
774
775 @Override
776 public Marker get(int index) {
777 return this.markerList.get(index);
778 }
779
780 @Override
781 public Marker set(int index, Marker element) {
782 Marker original = this.markerList.set(index, element);
783 this.remove(original);
784 return original;
785 }
786
787 @Override
788 public void add(int index, Marker element) {
789 this.add(element);
790 this.markerList.add(index, element);
791 }
792
793 @Override
794 public Marker remove(int index) {
795 Marker toRemove = this.markerList.remove(index);
796 this.remove(toRemove);
797 return toRemove;
798 }
799
800 @Override
801 public int indexOf(Object o) {
802 return this.markerList.indexOf(o);
803 }
804
805 @Override
806 public int lastIndexOf(Object o) {
807 return this.markerList.lastIndexOf(o);
808 }
809
810 @Override
811 public ListIterator<Marker> listIterator() {
812 return this.markerList.listIterator();
813 }
814
815 @Override
816 public ListIterator<Marker> listIterator(int index) {
817 return this.markerList.listIterator(index);
818 }
819
820 @Override
821 public List<Marker> subList(int fromIndex, int toIndex) {
822 return this.markerList.subList(fromIndex, toIndex);
823 }
824
825 @Override
826 public boolean retainAll(Collection<?> objects) {
827 return this.markerList.retainAll(objects) && super.retainAll(objects);
828 }
829
830 @Override
831 public boolean contains(Object o) {
832 return this.markerList.contains(o) && super.contains(o);
833 }
834 }
835}
Note: See TracBrowser for help on using the repository browser.