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

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

fix Sonar issue squid:S2444 - Lazy initialization of "static" fields should be "synchronized"

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