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

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

javadoc update

  • 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 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 * @param reset whether to reset state (pause audio and restore old coordinates)
116 */
117 private void endDrag(boolean reset) {
118 if (!wasPlaying || reset) {
119 try {
120 AudioPlayer.pause();
121 } catch (Exception ex) {
122 AudioPlayer.audioMalfunction(ex);
123 }
124 }
125 if (reset) {
126 setCoor(oldCoor);
127 }
128 Main.map.selectMapMode(oldMode);
129 Main.map.mapView.repaint();
130 timer.start();
131 }
132
133 /**
134 * apply the new position resulting from a drag in progress
135 * @param en the new position in map terms
136 */
137 public void drag(EastNorth en) {
138 setEastNorth(en);
139 Main.map.mapView.repaint();
140 }
141
142 /**
143 * reposition the play head at the point on the track nearest position given,
144 * providing we are within reasonable distance from the track; otherwise reset to the
145 * original position.
146 * @param en the position to start looking from
147 */
148 public void reposition(EastNorth en) {
149 WayPoint cw = null;
150 AudioMarker recent = AudioMarker.recentlyPlayedMarker();
151 if (recent != null && recent.parentLayer != null && recent.parentLayer.fromLayer != null) {
152 /* work out EastNorth equivalent of 50 (default) pixels tolerance */
153 Point p = Main.map.mapView.getPoint(en);
154 EastNorth enPlus25px = Main.map.mapView.getEastNorth(p.x+dropTolerance, p.y);
155 cw = recent.parentLayer.fromLayer.data.nearestPointOnTrack(en, enPlus25px.east() - en.east());
156 }
157
158 AudioMarker ca = null;
159 /* Find the prior audio marker (there should always be one in the
160 * layer, even if it is only one at the start of the track) to
161 * offset the audio from */
162 if (cw != null && recent != null && recent.parentLayer != null) {
163 for (Marker m : recent.parentLayer.data) {
164 if (m instanceof AudioMarker) {
165 AudioMarker a = (AudioMarker) m;
166 if (a.time > cw.time) {
167 break;
168 }
169 ca = a;
170 }
171 }
172 }
173
174 if (ca == null) {
175 /* Not close enough to track, or no audio marker found for some other reason */
176 JOptionPane.showMessageDialog(
177 Main.parent,
178 tr("You need to drag the play head near to the GPX track " +
179 "whose associated sound track you were playing (after the first marker)."),
180 tr("Warning"),
181 JOptionPane.WARNING_MESSAGE
182 );
183 endDrag(true);
184 } else {
185 if (cw != null) {
186 setCoor(cw.getCoor());
187 ca.play(cw.time - ca.time);
188 }
189 endDrag(false);
190 }
191 }
192
193 /**
194 * Synchronize the audio at the position where the play head was paused before
195 * dragging with the position on the track where it was dropped.
196 * If this is quite near an audio marker, we use that
197 * marker as the sync. location, otherwise we create a new marker at the
198 * trackpoint nearest the end point of the drag point to apply the
199 * sync to.
200 * @param en : the EastNorth end point of the drag
201 */
202 public void synchronize(EastNorth en) {
203 AudioMarker recent = AudioMarker.recentlyPlayedMarker();
204 if (recent == null)
205 return;
206 /* First, see if we dropped onto an existing audio marker in the layer being played */
207 Point startPoint = Main.map.mapView.getPoint(en);
208 AudioMarker ca = null;
209 if (recent.parentLayer != null) {
210 double closestAudioMarkerDistanceSquared = 1.0E100;
211 for (Marker m : recent.parentLayer.data) {
212 if (m instanceof AudioMarker) {
213 double distanceSquared = m.getEastNorth().distanceSq(en);
214 if (distanceSquared < closestAudioMarkerDistanceSquared) {
215 ca = (AudioMarker) m;
216 closestAudioMarkerDistanceSquared = distanceSquared;
217 }
218 }
219 }
220 }
221
222 /* We found the closest marker: did we actually hit it? */
223 if (ca != null && !ca.containsPoint(startPoint)) {
224 ca = null;
225 }
226
227 /* If we didn't hit an audio marker, we need to create one at the nearest point on the track */
228 if (ca == null) {
229 /* work out EastNorth equivalent of 50 (default) pixels tolerance */
230 Point p = Main.map.mapView.getPoint(en);
231 EastNorth enPlus25px = Main.map.mapView.getEastNorth(p.x+dropTolerance, p.y);
232 WayPoint cw = recent.parentLayer.fromLayer.data.nearestPointOnTrack(en, enPlus25px.east() - en.east());
233 if (cw == null) {
234 JOptionPane.showMessageDialog(
235 Main.parent,
236 tr("You need to SHIFT-drag the play head onto an audio marker or onto the track point where you want to synchronize."),
237 tr("Warning"),
238 JOptionPane.WARNING_MESSAGE
239 );
240 endDrag(true);
241 return;
242 }
243 ca = recent.parentLayer.addAudioMarker(cw.time, cw.getCoor());
244 }
245
246 /* Actually do the synchronization */
247 if (ca == null) {
248 JOptionPane.showMessageDialog(
249 Main.parent,
250 tr("Unable to create new audio marker."),
251 tr("Error"),
252 JOptionPane.ERROR_MESSAGE
253 );
254 endDrag(true);
255 } else if (recent.parentLayer.synchronizeAudioMarkers(ca)) {
256 JOptionPane.showMessageDialog(
257 Main.parent,
258 tr("Audio synchronized at point {0}.", recent.parentLayer.syncAudioMarker.getText()),
259 tr("Information"),
260 JOptionPane.INFORMATION_MESSAGE
261 );
262 setCoor(recent.parentLayer.syncAudioMarker.getCoor());
263 endDrag(false);
264 } else {
265 JOptionPane.showMessageDialog(
266 Main.parent,
267 tr("Unable to synchronize in layer being played."),
268 tr("Error"),
269 JOptionPane.ERROR_MESSAGE
270 );
271 endDrag(true);
272 }
273 }
274
275 /**
276 * Paint the marker icon in the given graphics context.
277 * @param g The graphics context
278 * @param mv The map
279 */
280 public void paint(Graphics g, MapView mv) {
281 if (time < 0.0) return;
282 Point screen = mv.getPoint(getEastNorth());
283 paintIcon(mv, g, screen.x, screen.y);
284 }
285
286 /**
287 * Animates the marker along the track.
288 */
289 public void animate() {
290 if (!enabled) return;
291 jumpToMarker = true;
292 if (timer == null) {
293 animationInterval = Main.pref.getDouble("marker.audioanimationinterval", 1.0); //milliseconds
294 timer = new Timer((int) (animationInterval * 1000.0), new ActionListener() {
295 @Override
296 public void actionPerformed(ActionEvent e) {
297 timerAction();
298 }
299 });
300 timer.setInitialDelay(0);
301 } else {
302 timer.stop();
303 }
304 timer.start();
305 }
306
307 /**
308 * callback for moving play head marker according to audio player position
309 */
310 public void timerAction() {
311 AudioMarker recentlyPlayedMarker = AudioMarker.recentlyPlayedMarker();
312 if (recentlyPlayedMarker == null)
313 return;
314 double audioTime = recentlyPlayedMarker.time +
315 AudioPlayer.position() -
316 recentlyPlayedMarker.offset -
317 recentlyPlayedMarker.syncOffset;
318 if (Math.abs(audioTime - time) < animationInterval)
319 return;
320 if (recentlyPlayedMarker.parentLayer == null) return;
321 GpxLayer trackLayer = recentlyPlayedMarker.parentLayer.fromLayer;
322 if (trackLayer == null)
323 return;
324 /* find the pair of track points for this position (adjusted by the syncOffset)
325 * and interpolate between them
326 */
327 WayPoint w1 = null;
328 WayPoint w2 = null;
329
330 for (GpxTrack track : trackLayer.data.tracks) {
331 for (GpxTrackSegment trackseg : track.getSegments()) {
332 for (WayPoint w: trackseg.getWayPoints()) {
333 if (audioTime < w.time) {
334 w2 = w;
335 break;
336 }
337 w1 = w;
338 }
339 if (w2 != null) {
340 break;
341 }
342 }
343 if (w2 != null) {
344 break;
345 }
346 }
347
348 if (w1 == null)
349 return;
350 setEastNorth(w2 == null ?
351 w1.getEastNorth() :
352 w1.getEastNorth().interpolate(w2.getEastNorth(),
353 (audioTime - w1.time)/(w2.time - w1.time)));
354 time = audioTime;
355 if (jumpToMarker) {
356 jumpToMarker = false;
357 Main.map.mapView.zoomTo(w1.getEastNorth());
358 }
359 Main.map.mapView.repaint();
360 }
361}
Note: See TracBrowser for help on using the repository browser.