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

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

PMD - Strict Exceptions

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