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

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

sonar - squid:S2221 - "Exception" should not be caught when not required by called methods

  • Property svn:eol-style set to native
File size: 13.0 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 playHead = new PlayHeadMarker();
54 }
55 return playHead;
56 }
57
58 private PlayHeadMarker() {
59 super(LatLon.ZERO, "",
60 Main.pref.get("marker.audiotracericon", "audio-tracer"),
61 null, -1.0, 0.0);
62 enabled = Main.pref.getBoolean("marker.traceaudio", true);
63 if (!enabled) return;
64 dropTolerance = Main.pref.getInteger("marker.playHeadDropTolerance", 50);
65 if (Main.isDisplayingMapView()) {
66 Main.map.mapView.addMouseListener(new MouseAdapter() {
67 @Override public void mousePressed(MouseEvent ev) {
68 Point p = ev.getPoint();
69 if (ev.getButton() != MouseEvent.BUTTON1 || p == null)
70 return;
71 if (playHead.containsPoint(p)) {
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 = Main.map.mapMode;
75 oldCoor = getCoor();
76 PlayHeadDragMode playHeadDragMode = new PlayHeadDragMode(playHead);
77 Main.map.selectMapMode(playHeadDragMode);
78 playHeadDragMode.mousePressed(ev);
79 }
80 }
81 });
82 }
83 }
84
85 @Override
86 public boolean containsPoint(Point p) {
87 Point screen = Main.map.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 (Exception ex) {
106 AudioPlayer.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 (Exception ex) {
120 AudioPlayer.audioMalfunction(ex);
121 }
122 }
123 if (reset) {
124 setCoor(oldCoor);
125 }
126 Main.map.selectMapMode(oldMode);
127 Main.map.mapView.repaint();
128 timer.start();
129 }
130
131 /**
132 * apply the new position resulting from a drag in progress
133 * @param en the new position in map terms
134 */
135 public void drag(EastNorth en) {
136 setEastNorth(en);
137 Main.map.mapView.repaint();
138 }
139
140 /**
141 * reposition the play head at the point on the track nearest position given,
142 * providing we are within reasonable distance from the track; otherwise reset to the
143 * original position.
144 * @param en the position to start looking from
145 */
146 public void reposition(EastNorth en) {
147 WayPoint cw = null;
148 AudioMarker recent = AudioMarker.recentlyPlayedMarker();
149 if (recent != null && recent.parentLayer != null && recent.parentLayer.fromLayer != null) {
150 /* work out EastNorth equivalent of 50 (default) pixels tolerance */
151 Point p = Main.map.mapView.getPoint(en);
152 EastNorth enPlus25px = Main.map.mapView.getEastNorth(p.x+dropTolerance, p.y);
153 cw = recent.parentLayer.fromLayer.data.nearestPointOnTrack(en, enPlus25px.east() - en.east());
154 }
155
156 AudioMarker ca = null;
157 /* Find the prior audio marker (there should always be one in the
158 * layer, even if it is only one at the start of the track) to
159 * offset the audio from */
160 if (cw != null && recent != null && recent.parentLayer != null) {
161 for (Marker m : recent.parentLayer.data) {
162 if (m instanceof AudioMarker) {
163 AudioMarker a = (AudioMarker) m;
164 if (a.time > cw.time) {
165 break;
166 }
167 ca = a;
168 }
169 }
170 }
171
172 if (ca == null) {
173 /* Not close enough to track, or no audio marker found for some other reason */
174 JOptionPane.showMessageDialog(
175 Main.parent,
176 tr("You need to drag the play head near to the GPX track " +
177 "whose associated sound track you were playing (after the first marker)."),
178 tr("Warning"),
179 JOptionPane.WARNING_MESSAGE
180 );
181 endDrag(true);
182 } else {
183 if (cw != null) {
184 setCoor(cw.getCoor());
185 ca.play(cw.time - ca.time);
186 }
187 endDrag(false);
188 }
189 }
190
191 /**
192 * Synchronize the audio at the position where the play head was paused before
193 * dragging with the position on the track where it was dropped.
194 * If this is quite near an audio marker, we use that
195 * marker as the sync. location, otherwise we create a new marker at the
196 * trackpoint nearest the end point of the drag point to apply the
197 * sync to.
198 * @param en : the EastNorth end point of the drag
199 */
200 public void synchronize(EastNorth en) {
201 AudioMarker recent = AudioMarker.recentlyPlayedMarker();
202 if (recent == null)
203 return;
204 /* First, see if we dropped onto an existing audio marker in the layer being played */
205 Point startPoint = Main.map.mapView.getPoint(en);
206 AudioMarker ca = null;
207 if (recent.parentLayer != null) {
208 double closestAudioMarkerDistanceSquared = 1.0E100;
209 for (Marker m : recent.parentLayer.data) {
210 if (m instanceof AudioMarker) {
211 double distanceSquared = m.getEastNorth().distanceSq(en);
212 if (distanceSquared < closestAudioMarkerDistanceSquared) {
213 ca = (AudioMarker) m;
214 closestAudioMarkerDistanceSquared = distanceSquared;
215 }
216 }
217 }
218 }
219
220 /* We found the closest marker: did we actually hit it? */
221 if (ca != null && !ca.containsPoint(startPoint)) {
222 ca = null;
223 }
224
225 /* If we didn't hit an audio marker, we need to create one at the nearest point on the track */
226 if (ca == null) {
227 /* work out EastNorth equivalent of 50 (default) pixels tolerance */
228 Point p = Main.map.mapView.getPoint(en);
229 EastNorth enPlus25px = Main.map.mapView.getEastNorth(p.x+dropTolerance, p.y);
230 WayPoint cw = recent.parentLayer.fromLayer.data.nearestPointOnTrack(en, enPlus25px.east() - en.east());
231 if (cw == null) {
232 JOptionPane.showMessageDialog(
233 Main.parent,
234 tr("You need to SHIFT-drag the play head onto an audio marker or onto the track point where you want to synchronize."),
235 tr("Warning"),
236 JOptionPane.WARNING_MESSAGE
237 );
238 endDrag(true);
239 return;
240 }
241 ca = recent.parentLayer.addAudioMarker(cw.time, cw.getCoor());
242 }
243
244 /* Actually do the synchronization */
245 if (ca == null) {
246 JOptionPane.showMessageDialog(
247 Main.parent,
248 tr("Unable to create new audio marker."),
249 tr("Error"),
250 JOptionPane.ERROR_MESSAGE
251 );
252 endDrag(true);
253 } else if (recent.parentLayer.synchronizeAudioMarkers(ca)) {
254 JOptionPane.showMessageDialog(
255 Main.parent,
256 tr("Audio synchronized at point {0}.", recent.parentLayer.syncAudioMarker.getText()),
257 tr("Information"),
258 JOptionPane.INFORMATION_MESSAGE
259 );
260 setCoor(recent.parentLayer.syncAudioMarker.getCoor());
261 endDrag(false);
262 } else {
263 JOptionPane.showMessageDialog(
264 Main.parent,
265 tr("Unable to synchronize in layer being played."),
266 tr("Error"),
267 JOptionPane.ERROR_MESSAGE
268 );
269 endDrag(true);
270 }
271 }
272
273 /**
274 * Paint the marker icon in the given graphics context.
275 * @param g The graphics context
276 * @param mv The map
277 */
278 public void paint(Graphics g, MapView mv) {
279 if (time < 0.0) return;
280 Point screen = mv.getPoint(getEastNorth());
281 paintIcon(mv, g, screen.x, screen.y);
282 }
283
284 /**
285 * Animates the marker along the track.
286 */
287 public void animate() {
288 if (!enabled) return;
289 jumpToMarker = true;
290 if (timer == null) {
291 animationInterval = Main.pref.getDouble("marker.audioanimationinterval", 1.0); //milliseconds
292 timer = new Timer((int) (animationInterval * 1000.0), new ActionListener() {
293 @Override
294 public void actionPerformed(ActionEvent e) {
295 timerAction();
296 }
297 });
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.tracks) {
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 if (jumpToMarker) {
354 jumpToMarker = false;
355 Main.map.mapView.zoomTo(w1.getEastNorth());
356 }
357 Main.map.mapView.repaint();
358 }
359}
Note: See TracBrowser for help on using the repository browser.