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

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

use LatLon.ZERO instead of new LatLon(0, 0)

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