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

Last change on this file since 4284 was 4284, checked in by jttt, 13 years ago

Fix "constructor ... is ambiguous" compilation error

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