source: josm/trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/PlayHeadMarker.java@ 12846

Last change on this file since 12846 was 12846, checked in by bastiK, 7 years ago

see #15229 - use Config.getPref() wherever possible

  • Property svn:eol-style set to native
File size: 13.3 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.layer.markerlayer;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Graphics;
7import java.awt.Point;
8import java.awt.Rectangle;
9import java.awt.event.MouseAdapter;
10import java.awt.event.MouseEvent;
11import java.io.IOException;
12
13import javax.swing.JOptionPane;
14import javax.swing.Timer;
15
16import org.openstreetmap.josm.Main;
17import org.openstreetmap.josm.actions.mapmode.MapMode;
18import org.openstreetmap.josm.actions.mapmode.PlayHeadDragMode;
19import org.openstreetmap.josm.data.coor.EastNorth;
20import org.openstreetmap.josm.data.coor.LatLon;
21import org.openstreetmap.josm.data.gpx.GpxTrack;
22import org.openstreetmap.josm.data.gpx.GpxTrackSegment;
23import org.openstreetmap.josm.data.gpx.WayPoint;
24import org.openstreetmap.josm.gui.MainApplication;
25import org.openstreetmap.josm.gui.MapFrame;
26import org.openstreetmap.josm.gui.MapView;
27import org.openstreetmap.josm.gui.layer.GpxLayer;
28import org.openstreetmap.josm.io.audio.AudioPlayer;
29import org.openstreetmap.josm.io.audio.AudioUtil;
30import org.openstreetmap.josm.spi.preferences.Config;
31
32/**
33 * Singleton marker class to track position of audio.
34 *
35 * @author David Earl <david@frankieandshadow.com>
36 * @since 572
37 */
38public final class PlayHeadMarker extends Marker {
39
40 private Timer timer;
41 private double animationInterval; // seconds
42 private static volatile PlayHeadMarker playHead;
43 private MapMode oldMode;
44 private LatLon oldCoor;
45 private final boolean enabled;
46 private boolean wasPlaying;
47 private int dropTolerance; /* pixels */
48 private boolean jumpToMarker;
49
50 /**
51 * Returns the unique instance of {@code PlayHeadMarker}.
52 * @return The unique instance of {@code PlayHeadMarker}.
53 */
54 public static PlayHeadMarker create() {
55 if (playHead == null) {
56 playHead = new PlayHeadMarker();
57 }
58 return playHead;
59 }
60
61 private PlayHeadMarker() {
62 super(LatLon.ZERO, "",
63 Config.getPref().get("marker.audiotracericon", "audio-tracer"),
64 null, -1.0, 0.0);
65 enabled = Config.getPref().getBoolean("marker.traceaudio", true);
66 if (!enabled) return;
67 dropTolerance = Config.getPref().getInt("marker.playHeadDropTolerance", 50);
68 if (MainApplication.isDisplayingMapView()) {
69 MapFrame map = MainApplication.getMap();
70 map.mapView.addMouseListener(new MouseAdapter() {
71 @Override public void mousePressed(MouseEvent ev) {
72 if (ev.getButton() == MouseEvent.BUTTON1 && playHead.containsPoint(ev.getPoint())) {
73 /* when we get a click on the marker, we need to switch mode to avoid
74 * getting confused with other drag operations (like select) */
75 oldMode = map.mapMode;
76 oldCoor = getCoor();
77 PlayHeadDragMode playHeadDragMode = new PlayHeadDragMode(playHead);
78 map.selectMapMode(playHeadDragMode);
79 playHeadDragMode.mousePressed(ev);
80 }
81 }
82 });
83 }
84 }
85
86 @Override
87 public boolean containsPoint(Point p) {
88 Point screen = MainApplication.getMap().mapView.getPoint(this);
89 Rectangle r = new Rectangle(screen.x, screen.y, symbol.getIconWidth(),
90 symbol.getIconHeight());
91 return r.contains(p);
92 }
93
94 /**
95 * called back from drag mode to say when we started dragging for real
96 * (at least a short distance)
97 */
98 public void startDrag() {
99 if (timer != null) {
100 timer.stop();
101 }
102 wasPlaying = AudioPlayer.playing();
103 if (wasPlaying) {
104 try {
105 AudioPlayer.pause();
106 } catch (IOException | InterruptedException ex) {
107 AudioUtil.audioMalfunction(ex);
108 }
109 }
110 }
111
112 /**
113 * reinstate the old map mode after switching temporarily to do a play head drag
114 * @param reset whether to reset state (pause audio and restore old coordinates)
115 */
116 private void endDrag(boolean reset) {
117 if (!wasPlaying || reset) {
118 try {
119 AudioPlayer.pause();
120 } catch (IOException | InterruptedException ex) {
121 AudioUtil.audioMalfunction(ex);
122 }
123 }
124 if (reset) {
125 setCoor(oldCoor);
126 }
127 MapFrame map = MainApplication.getMap();
128 map.selectMapMode(oldMode);
129 map.mapView.repaint();
130 if (timer != null) {
131 timer.start();
132 }
133 }
134
135 /**
136 * apply the new position resulting from a drag in progress
137 * @param en the new position in map terms
138 */
139 public void drag(EastNorth en) {
140 setEastNorth(en);
141 MainApplication.getMap().mapView.repaint();
142 }
143
144 /**
145 * reposition the play head at the point on the track nearest position given,
146 * providing we are within reasonable distance from the track; otherwise reset to the
147 * original position.
148 * @param en the position to start looking from
149 */
150 public void reposition(EastNorth en) {
151 WayPoint cw = null;
152 AudioMarker recent = AudioMarker.recentlyPlayedMarker();
153 if (recent != null && recent.parentLayer != null && recent.parentLayer.fromLayer != null) {
154 /* work out EastNorth equivalent of 50 (default) pixels tolerance */
155 MapView mapView = MainApplication.getMap().mapView;
156 Point p = mapView.getPoint(en);
157 EastNorth enPlus25px = mapView.getEastNorth(p.x+dropTolerance, p.y);
158 cw = recent.parentLayer.fromLayer.data.nearestPointOnTrack(en, enPlus25px.east() - en.east());
159 }
160
161 AudioMarker ca = null;
162 /* Find the prior audio marker (there should always be one in the
163 * layer, even if it is only one at the start of the track) to
164 * offset the audio from */
165 if (cw != null && recent != null && recent.parentLayer != null) {
166 for (Marker m : recent.parentLayer.data) {
167 if (m instanceof AudioMarker) {
168 AudioMarker a = (AudioMarker) m;
169 if (a.time > cw.time) {
170 break;
171 }
172 ca = a;
173 }
174 }
175 }
176
177 if (ca == null) {
178 /* Not close enough to track, or no audio marker found for some other reason */
179 JOptionPane.showMessageDialog(
180 Main.parent,
181 tr("You need to drag the play head near to the GPX track " +
182 "whose associated sound track you were playing (after the first marker)."),
183 tr("Warning"),
184 JOptionPane.WARNING_MESSAGE
185 );
186 endDrag(true);
187 } else {
188 if (cw != null) {
189 setCoor(cw.getCoor());
190 ca.play(cw.time - ca.time);
191 }
192 endDrag(false);
193 }
194 }
195
196 /**
197 * Synchronize the audio at the position where the play head was paused before
198 * dragging with the position on the track where it was dropped.
199 * If this is quite near an audio marker, we use that
200 * marker as the sync. location, otherwise we create a new marker at the
201 * trackpoint nearest the end point of the drag point to apply the
202 * sync to.
203 * @param en : the EastNorth end point of the drag
204 */
205 public void synchronize(EastNorth en) {
206 AudioMarker recent = AudioMarker.recentlyPlayedMarker();
207 if (recent == null)
208 return;
209 /* First, see if we dropped onto an existing audio marker in the layer being played */
210 MapView mapView = MainApplication.getMap().mapView;
211 Point startPoint = mapView.getPoint(en);
212 AudioMarker ca = null;
213 if (recent.parentLayer != null) {
214 double closestAudioMarkerDistanceSquared = 1.0E100;
215 for (Marker m : recent.parentLayer.data) {
216 if (m instanceof AudioMarker) {
217 double distanceSquared = m.getEastNorth(Main.getProjection()).distanceSq(en);
218 if (distanceSquared < closestAudioMarkerDistanceSquared) {
219 ca = (AudioMarker) m;
220 closestAudioMarkerDistanceSquared = distanceSquared;
221 }
222 }
223 }
224 }
225
226 /* We found the closest marker: did we actually hit it? */
227 if (ca != null && !ca.containsPoint(startPoint)) {
228 ca = null;
229 }
230
231 /* If we didn't hit an audio marker, we need to create one at the nearest point on the track */
232 if (ca == null) {
233 /* work out EastNorth equivalent of 50 (default) pixels tolerance */
234 Point p = mapView.getPoint(en);
235 EastNorth enPlus25px = mapView.getEastNorth(p.x+dropTolerance, p.y);
236 WayPoint cw = recent.parentLayer.fromLayer.data.nearestPointOnTrack(en, enPlus25px.east() - en.east());
237 if (cw == null) {
238 JOptionPane.showMessageDialog(
239 Main.parent,
240 tr("You need to SHIFT-drag the play head onto an audio marker or onto the track point where you want to synchronize."),
241 tr("Warning"),
242 JOptionPane.WARNING_MESSAGE
243 );
244 endDrag(true);
245 return;
246 }
247 ca = recent.parentLayer.addAudioMarker(cw.time, cw.getCoor());
248 }
249
250 /* Actually do the synchronization */
251 if (ca == null) {
252 JOptionPane.showMessageDialog(
253 Main.parent,
254 tr("Unable to create new audio marker."),
255 tr("Error"),
256 JOptionPane.ERROR_MESSAGE
257 );
258 endDrag(true);
259 } else if (recent.parentLayer.synchronizeAudioMarkers(ca)) {
260 JOptionPane.showMessageDialog(
261 Main.parent,
262 tr("Audio synchronized at point {0}.", recent.parentLayer.syncAudioMarker.getText()),
263 tr("Information"),
264 JOptionPane.INFORMATION_MESSAGE
265 );
266 setCoor(recent.parentLayer.syncAudioMarker.getCoor());
267 endDrag(false);
268 } else {
269 JOptionPane.showMessageDialog(
270 Main.parent,
271 tr("Unable to synchronize in layer being played."),
272 tr("Error"),
273 JOptionPane.ERROR_MESSAGE
274 );
275 endDrag(true);
276 }
277 }
278
279 /**
280 * Paint the marker icon in the given graphics context.
281 * @param g The graphics context
282 * @param mv The map
283 */
284 public void paint(Graphics g, MapView mv) {
285 if (time < 0.0) return;
286 Point screen = mv.getPoint(this);
287 paintIcon(mv, g, screen.x, screen.y);
288 }
289
290 /**
291 * Animates the marker along the track.
292 */
293 public void animate() {
294 if (!enabled) return;
295 jumpToMarker = true;
296 if (timer == null) {
297 animationInterval = Config.getPref().getDouble("marker.audioanimationinterval", 1.0); //milliseconds
298 timer = new Timer((int) (animationInterval * 1000.0), e -> timerAction());
299 timer.setInitialDelay(0);
300 } else {
301 timer.stop();
302 }
303 timer.start();
304 }
305
306 /**
307 * callback for moving play head marker according to audio player position
308 */
309 public void timerAction() {
310 AudioMarker recentlyPlayedMarker = AudioMarker.recentlyPlayedMarker();
311 if (recentlyPlayedMarker == null)
312 return;
313 double audioTime = recentlyPlayedMarker.time +
314 AudioPlayer.position() -
315 recentlyPlayedMarker.offset -
316 recentlyPlayedMarker.syncOffset;
317 if (Math.abs(audioTime - time) < animationInterval)
318 return;
319 if (recentlyPlayedMarker.parentLayer == null) return;
320 GpxLayer trackLayer = recentlyPlayedMarker.parentLayer.fromLayer;
321 if (trackLayer == null)
322 return;
323 /* find the pair of track points for this position (adjusted by the syncOffset)
324 * and interpolate between them
325 */
326 WayPoint w1 = null;
327 WayPoint w2 = null;
328
329 for (GpxTrack track : trackLayer.data.getTracks()) {
330 for (GpxTrackSegment trackseg : track.getSegments()) {
331 for (WayPoint w: trackseg.getWayPoints()) {
332 if (audioTime < w.time) {
333 w2 = w;
334 break;
335 }
336 w1 = w;
337 }
338 if (w2 != null) {
339 break;
340 }
341 }
342 if (w2 != null) {
343 break;
344 }
345 }
346
347 if (w1 == null)
348 return;
349 setEastNorth(w2 == null ?
350 w1.getEastNorth(Main.getProjection()) :
351 w1.getEastNorth(Main.getProjection()).interpolate(w2.getEastNorth(Main.getProjection()),
352 (audioTime - w1.time)/(w2.time - w1.time)));
353 time = audioTime;
354 MapView mapView = MainApplication.getMap().mapView;
355 if (jumpToMarker) {
356 jumpToMarker = false;
357 mapView.zoomTo(w1);
358 }
359 mapView.repaint();
360 }
361}
Note: See TracBrowser for help on using the repository browser.