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

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

see #15182 - deprecate Main.map and Main.isDisplayingMapView(). Replacements: gui.MainApplication.getMap() / gui.MainApplication.isDisplayingMapView()

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