source: josm/trunk/src/org/openstreetmap/josm/gui/layer/markerlayer/MarkerLayer.java@ 547

Last change on this file since 547 was 547, checked in by david, 16 years ago

much improved audio handling: threaded audio, controls, sync with waypoints

File size: 10.7 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.gui.layer.markerlayer;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trn;
6
7import java.awt.Color;
8import java.awt.Component;
9import java.awt.Graphics;
10import java.awt.Point;
11import java.awt.event.ActionEvent;
12import java.awt.event.ActionListener;
13import java.awt.event.MouseAdapter;
14import java.awt.event.MouseEvent;
15import java.io.File;
16import java.util.ArrayList;
17import java.util.Collection;
18import java.util.Iterator;
19import java.util.Date;
20import java.text.SimpleDateFormat;
21import java.text.ParsePosition;
22import java.text.ParseException;
23import java.net.URL;
24
25import javax.swing.Icon;
26import javax.swing.JColorChooser;
27import javax.swing.JFileChooser;
28import javax.swing.JMenuItem;
29import javax.swing.JOptionPane;
30import javax.swing.JSeparator;
31import javax.swing.SwingUtilities;
32import javax.swing.filechooser.FileFilter;
33
34import org.openstreetmap.josm.Main;
35import org.openstreetmap.josm.actions.RenameLayerAction;
36import org.openstreetmap.josm.data.gpx.GpxData;
37import org.openstreetmap.josm.data.gpx.WayPoint;
38import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
39import org.openstreetmap.josm.gui.MapView;
40import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
41import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
42import org.openstreetmap.josm.gui.layer.Layer;
43import org.openstreetmap.josm.gui.layer.markerlayer.AudioMarker;
44import org.openstreetmap.josm.tools.ColorHelper;
45import org.openstreetmap.josm.tools.ImageProvider;
46import org.openstreetmap.josm.tools.AudioPlayer;
47
48/**
49 * A layer holding markers.
50 *
51 * Markers are GPS points with a name and, optionally, a symbol code attached;
52 * marker layers can be created from waypoints when importing raw GPS data,
53 * but they may also come from other sources.
54 *
55 * The symbol code is for future use.
56 *
57 * The data is read only.
58 */
59public class MarkerLayer extends Layer {
60
61 /**
62 * A list of markers.
63 */
64 public final Collection<Marker> data;
65 private boolean mousePressed = false;
66
67 public MarkerLayer(GpxData indata, String name, File associatedFile) {
68
69 super(name);
70 this.associatedFile = associatedFile;
71 this.data = new ArrayList<Marker>();
72 double offset = 0.0;
73 Date firstDate = null;
74
75 for (WayPoint wpt : indata.waypoints) {
76 /* calculate time differences in waypoints */
77 if (wpt.attr.containsKey("time")) {
78 SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); // ignore timezone, as it is all relative
79 Date d = f.parse(wpt.attr.get("time").toString(), new ParsePosition(0));
80 if (d == null /* failed to parse */) {
81 offset = 0.0;
82 } else if (firstDate == null) {
83 firstDate = d;
84 offset = 0.0;
85 } else {
86 offset = (d.getTime() - firstDate.getTime()) / 1000.0; /* ms => seconds */
87 }
88 }
89
90 Marker m = Marker.createMarker(wpt, indata.storageFile, offset);
91 if (m != null)
92 data.add(m);
93 }
94
95 SwingUtilities.invokeLater(new Runnable(){
96 public void run() {
97 Main.map.mapView.addMouseListener(new MouseAdapter() {
98 @Override public void mousePressed(MouseEvent e) {
99 if (e.getButton() != MouseEvent.BUTTON1)
100 return;
101 boolean mousePressedInButton = false;
102 if (e.getPoint() != null) {
103 for (Marker mkr : data) {
104 if (mkr.containsPoint(e.getPoint())) {
105 mousePressedInButton = true;
106 break;
107 }
108 }
109 }
110 if (! mousePressedInButton)
111 return;
112 mousePressed = true;
113 if (visible)
114 Main.map.mapView.repaint();
115 }
116 @Override public void mouseReleased(MouseEvent ev) {
117 if (ev.getButton() != MouseEvent.BUTTON1 || ! mousePressed)
118 return;
119 mousePressed = false;
120 if (!visible)
121 return;
122 if (ev.getPoint() != null) {
123 for (Marker mkr : data) {
124 if (mkr.containsPoint(ev.getPoint()))
125 mkr.actionPerformed(new ActionEvent(this, 0, null));
126 }
127 }
128 Main.map.mapView.repaint();
129 }
130 });
131 }
132 });
133 }
134
135 /**
136 * Return a static icon.
137 */
138 @Override public Icon getIcon() {
139 return ImageProvider.get("layer", "marker_small");
140 }
141
142 @Override public void paint(Graphics g, MapView mv) {
143 boolean mousePressedTmp = mousePressed;
144 Point mousePos = mv.getMousePosition();
145 String mkrCol = Main.pref.get("color.gps marker");
146 String mkrColSpecial = Main.pref.get("color.layer "+name);
147 String mkrTextShow = Main.pref.get("marker.show "+name, "show");
148
149 if (!mkrColSpecial.equals(""))
150 g.setColor(ColorHelper.html2color(mkrColSpecial));
151 else if (!mkrCol.equals(""))
152 g.setColor(ColorHelper.html2color(mkrCol));
153 else
154 g.setColor(Color.GRAY);
155
156 for (Marker mkr : data) {
157 if (mousePos != null && mkr.containsPoint(mousePos)) {
158 mkr.paint(g, mv, mousePressedTmp, mkrTextShow);
159 mousePressedTmp = false;
160 } else {
161 mkr.paint(g, mv, false, mkrTextShow);
162 }
163 }
164 }
165
166 @Override public String getToolTipText() {
167 return data.size()+" "+trn("marker", "markers", data.size());
168 }
169
170 @Override public void mergeFrom(Layer from) {
171 MarkerLayer layer = (MarkerLayer)from;
172 data.addAll(layer.data);
173 }
174
175 @Override public boolean isMergable(Layer other) {
176 return other instanceof MarkerLayer;
177 }
178
179 @Override public void visitBoundingBox(BoundingXYVisitor v) {
180 for (Marker mkr : data)
181 v.visit(mkr.eastNorth);
182 }
183
184 public void applyAudio(File wavFile) {
185 String uri = "file:".concat(wavFile.getAbsolutePath());
186 Collection<Marker> markers = new ArrayList<Marker>();
187 for (Marker mkr : data) {
188 AudioMarker audioMarker = mkr.audioMarkerFromMarker(uri);
189 if (audioMarker == null) {
190 markers.add(mkr);
191 } else {
192 markers.add(audioMarker);
193 }
194 }
195 data.clear();
196 data.addAll(markers);
197 }
198
199 @Override public Object getInfoComponent() {
200 return "<html>"+trn("{0} consists of {1} marker", "{0} consists of {1} markers", data.size(), name, data.size()) + "</html>";
201 }
202
203 @Override public Component[] getMenuEntries() {
204 JMenuItem color = new JMenuItem(tr("Customize Color"), ImageProvider.get("colorchooser"));
205 color.addActionListener(new ActionListener(){
206 public void actionPerformed(ActionEvent e) {
207 String col = Main.pref.get("color.layer "+name, Main.pref.get("color.gps marker", ColorHelper.color2html(Color.gray)));
208 JColorChooser c = new JColorChooser(ColorHelper.html2color(col));
209 Object[] options = new Object[]{tr("OK"), tr("Cancel"), tr("Default")};
210 int answer = JOptionPane.showOptionDialog(Main.parent, c, tr("Choose a color"), JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE, null, options, options[0]);
211 switch (answer) {
212 case 0:
213 Main.pref.put("color.layer "+name, ColorHelper.color2html(c.getColor()));
214 break;
215 case 1:
216 return;
217 case 2:
218 Main.pref.put("color.layer "+name, null);
219 break;
220 }
221 Main.map.repaint();
222 }
223 });
224
225 JMenuItem applyaudio = new JMenuItem(tr("Apply Audio"), ImageProvider.get("applyaudio"));
226 applyaudio.addActionListener(new ActionListener(){
227 public void actionPerformed(ActionEvent e) {
228 JFileChooser fc = new JFileChooser(Main.pref.get("tagimages.lastdirectory"));
229 fc.setFileSelectionMode(JFileChooser.FILES_ONLY);
230 fc.setAcceptAllFileFilterUsed(false);
231 fc.setFileFilter(new FileFilter(){
232 @Override public boolean accept(File f) {
233 return f.isDirectory() || f.getName().toLowerCase().endsWith(".wav");
234 }
235 @Override public String getDescription() {
236 return tr("Wave Audio files (*.wav)");
237 }
238 });
239 fc.showOpenDialog(Main.parent);
240 File sel = fc.getSelectedFile();
241 if (sel == null)
242 return;
243 applyAudio(sel);
244 Main.map.repaint();
245 }
246 });
247
248 JMenuItem syncaudio = new JMenuItem(tr("Synchronize Audio"), ImageProvider.get("audio-sync"));
249 syncaudio.addActionListener(new ActionListener(){
250 public void actionPerformed(ActionEvent e) {
251 adjustOffsetsOnAudioMarkers();
252 }
253 });
254
255 return new Component[] {
256 new JMenuItem(new LayerListDialog.ShowHideLayerAction(this)),
257 new JMenuItem(new LayerListDialog.ShowHideMarkerText(this)),
258 new JMenuItem(new LayerListDialog.DeleteLayerAction(this)),
259 new JSeparator(),
260 color,
261 new JSeparator(),
262 syncaudio,
263 applyaudio,
264 new JMenuItem(new RenameLayerAction(associatedFile, this)),
265 new JSeparator(),
266 new JMenuItem(new LayerListPopup.InfoAction(this))
267 };
268 }
269
270 private void adjustOffsetsOnAudioMarkers() {
271 Marker startMarker = AudioMarker.recentlyPlayedMarker();
272 if (startMarker != null && ! data.contains(startMarker)) {
273 // message?
274 startMarker = null;
275 }
276 if (startMarker == null) {
277 // find the first audioMarker in this layer
278 for (Marker m : data) {
279 if (m.getClass() == AudioMarker.class) {
280 startMarker = m;
281 break;
282 }
283 }
284 }
285 if (startMarker == null) {
286 // still no marker to work from - message?
287 return;
288 }
289 // apply adjustment to all subsequent audio markers in the layer
290 double adjustment = AudioPlayer.position(); // in seconds
291 boolean seenStart = false;
292 URL url = ((AudioMarker)startMarker).url();
293 for (Marker m : data) {
294 if (m == startMarker)
295 seenStart = true;
296 if (seenStart) {
297 AudioMarker ma = (AudioMarker) m; // it must be an AudioMarker
298 if (! ma.url().equals(url))
299 break;
300 ma.adjustOffset(adjustment);
301 }
302 }
303 }
304
305 public static void playAudio() {
306 if (Main.map == null || Main.map.mapView == null)
307 return;
308 for (Layer layer : Main.map.mapView.getAllLayers()) {
309 if (layer.getClass() == MarkerLayer.class) {
310 MarkerLayer markerLayer = (MarkerLayer) layer;
311 for (Marker marker : markerLayer.data) {
312 if (marker.getClass() == AudioMarker.class) {
313 ((AudioMarker)marker).play();
314 break;
315 }
316 }
317 }
318 }
319 }
320
321 public static void playNextMarker() {
322 playAdjacentMarker(true);
323 }
324
325 public static void playPreviousMarker() {
326 playAdjacentMarker(false);
327 }
328
329 private static void playAdjacentMarker(boolean next) {
330 Marker startMarker = AudioMarker.recentlyPlayedMarker();
331 if (startMarker == null) {
332 // message?
333 return;
334 }
335 Marker previousMarker = null;
336 Marker targetMarker = null;
337 boolean nextTime = false;
338 if (Main.map == null || Main.map.mapView == null)
339 return;
340 for (Layer layer : Main.map.mapView.getAllLayers()) {
341 if (layer.getClass() == MarkerLayer.class) {
342 MarkerLayer markerLayer = (MarkerLayer) layer;
343 for (Marker marker : markerLayer.data) {
344 if (marker == startMarker) {
345 if (next) {
346 nextTime = true;
347 } else {
348 ((AudioMarker)previousMarker).play();
349 break;
350 }
351 } else if (nextTime) {
352 ((AudioMarker)marker).play();
353 return;
354 }
355 previousMarker = marker;
356 }
357 }
358 }
359 }
360
361}
Note: See TracBrowser for help on using the repository browser.