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

Last change on this file since 1865 was 1865, checked in by Gubaer, 15 years ago

replaced JOptionPane.show* by OptionPaneUtil.show*
ExtendeDialog now always on top, too

  • Property svn:eol-style set to native
File size: 16.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.marktr;
5import static org.openstreetmap.josm.tools.I18n.tr;
6import static org.openstreetmap.josm.tools.I18n.trn;
7
8import java.awt.Color;
9import java.awt.Component;
10import java.awt.Graphics;
11import java.awt.Point;
12import java.awt.event.ActionEvent;
13import java.awt.event.ActionListener;
14import java.awt.event.MouseAdapter;
15import java.awt.event.MouseEvent;
16import java.io.File;
17import java.net.URL;
18import java.util.ArrayList;
19import java.util.Collection;
20
21import javax.swing.Icon;
22import javax.swing.JColorChooser;
23import javax.swing.JMenuItem;
24import javax.swing.JOptionPane;
25import javax.swing.JSeparator;
26import javax.swing.SwingUtilities;
27
28import org.openstreetmap.josm.Main;
29import org.openstreetmap.josm.actions.RenameLayerAction;
30import org.openstreetmap.josm.data.coor.LatLon;
31import org.openstreetmap.josm.data.gpx.GpxData;
32import org.openstreetmap.josm.data.gpx.GpxLink;
33import org.openstreetmap.josm.data.gpx.WayPoint;
34import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
35import org.openstreetmap.josm.gui.MapView;
36import org.openstreetmap.josm.gui.OptionPaneUtil;
37import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
38import org.openstreetmap.josm.gui.dialogs.LayerListPopup;
39import org.openstreetmap.josm.gui.layer.GpxLayer;
40import org.openstreetmap.josm.gui.layer.Layer;
41import org.openstreetmap.josm.tools.AudioPlayer;
42import org.openstreetmap.josm.tools.ImageProvider;
43
44/**
45 * A layer holding markers.
46 *
47 * Markers are GPS points with a name and, optionally, a symbol code attached;
48 * marker layers can be created from waypoints when importing raw GPS data,
49 * but they may also come from other sources.
50 *
51 * The symbol code is for future use.
52 *
53 * The data is read only.
54 */
55public class MarkerLayer extends Layer {
56
57 /**
58 * A list of markers.
59 */
60 public final Collection<Marker> data;
61 private boolean mousePressed = false;
62 public GpxLayer fromLayer = null;
63
64 /*
65 private Icon audioTracerIcon = null;
66 private EastNorth playheadPosition = null;
67 private static Timer timer = null;
68 private static double audioAnimationInterval = 0.0; // seconds
69 private static double playheadTime = -1.0;
70 */
71 public MarkerLayer(GpxData indata, String name, File associatedFile, GpxLayer fromLayer) {
72
73 super(name);
74 this.setAssociatedFile(associatedFile);
75 this.data = new ArrayList<Marker>();
76 this.fromLayer = fromLayer;
77 double firstTime = -1.0;
78 String lastLinkedFile = "";
79
80 for (WayPoint wpt : indata.waypoints) {
81 /* calculate time differences in waypoints */
82 double time = wpt.time;
83 boolean wpt_has_link = wpt.attr.containsKey(GpxData.META_LINKS);
84 if (firstTime < 0 && wpt_has_link) {
85 firstTime = time;
86 for (GpxLink oneLink : (Collection<GpxLink>) wpt.attr.get(GpxData.META_LINKS)) {
87 lastLinkedFile = oneLink.uri;
88 break;
89 }
90 }
91 if (wpt_has_link) {
92 for (GpxLink oneLink : (Collection<GpxLink>) wpt.attr.get(GpxData.META_LINKS)) {
93 if (!oneLink.uri.equals(lastLinkedFile)) {
94 firstTime = time;
95 }
96 lastLinkedFile = oneLink.uri;
97 break;
98 }
99 }
100 Marker m = Marker.createMarker(wpt, indata.storageFile, this, time, time - firstTime);
101 if (m != null) {
102 data.add(m);
103 }
104 }
105
106 SwingUtilities.invokeLater(new Runnable(){
107 public void run() {
108 Main.map.mapView.addMouseListener(new MouseAdapter() {
109 @Override public void mousePressed(MouseEvent e) {
110 if (e.getButton() != MouseEvent.BUTTON1)
111 return;
112 boolean mousePressedInButton = false;
113 if (e.getPoint() != null) {
114 for (Marker mkr : data) {
115 if (mkr.containsPoint(e.getPoint())) {
116 mousePressedInButton = true;
117 break;
118 }
119 }
120 }
121 if (! mousePressedInButton)
122 return;
123 mousePressed = true;
124 if (visible) {
125 Main.map.mapView.repaint();
126 }
127 }
128 @Override public void mouseReleased(MouseEvent ev) {
129 if (ev.getButton() != MouseEvent.BUTTON1 || ! mousePressed)
130 return;
131 mousePressed = false;
132 if (!visible)
133 return;
134 if (ev.getPoint() != null) {
135 for (Marker mkr : data) {
136 if (mkr.containsPoint(ev.getPoint())) {
137 mkr.actionPerformed(new ActionEvent(this, 0, null));
138 }
139 }
140 }
141 Main.map.mapView.repaint();
142 }
143 });
144 }
145 });
146 }
147
148 /**
149 * Return a static icon.
150 */
151 @Override public Icon getIcon() {
152 return ImageProvider.get("layer", "marker_small");
153 }
154
155 static public Color getColor(String name)
156 {
157 return Main.pref.getColor(marktr("gps marker"), name != null ? "layer "+name : null, Color.gray);
158 }
159
160 @Override public void paint(Graphics g, MapView mv) {
161 boolean mousePressedTmp = mousePressed;
162 Point mousePos = mv.getMousePosition();
163 String mkrTextShow = Main.pref.get("marker.show "+name, "show");
164
165 g.setColor(getColor(name));
166
167 for (Marker mkr : data) {
168 if (mousePos != null && mkr.containsPoint(mousePos)) {
169 mkr.paint(g, mv, mousePressedTmp, mkrTextShow);
170 mousePressedTmp = false;
171 } else {
172 mkr.paint(g, mv, false, mkrTextShow);
173 }
174 }
175 }
176
177 @Override public String getToolTipText() {
178 return data.size()+" "+trn("marker", "markers", data.size());
179 }
180
181 @Override public void mergeFrom(Layer from) {
182 MarkerLayer layer = (MarkerLayer)from;
183 data.addAll(layer.data);
184 }
185
186 @Override public boolean isMergable(Layer other) {
187 return other instanceof MarkerLayer;
188 }
189
190 @Override public void visitBoundingBox(BoundingXYVisitor v) {
191 for (Marker mkr : data) {
192 v.visit(mkr.getEastNorth());
193 }
194 }
195
196 @Override public Object getInfoComponent() {
197 return "<html>"+trn("{0} consists of {1} marker", "{0} consists of {1} markers", data.size(), name, data.size()) + "</html>";
198 }
199
200 @Override public Component[] getMenuEntries() {
201 JMenuItem color = new JMenuItem(tr("Customize Color"), ImageProvider.get("colorchooser"));
202 color.putClientProperty("help", "Action/LayerCustomizeColor");
203 color.addActionListener(new ActionListener(){
204 public void actionPerformed(ActionEvent e) {
205 JColorChooser c = new JColorChooser(getColor(name));
206 Object[] options = new Object[]{tr("OK"), tr("Cancel"), tr("Default")};
207 int answer = OptionPaneUtil.showOptionDialog(
208 Main.parent,
209 c,
210 tr("Choose a color"),
211 JOptionPane.OK_CANCEL_OPTION,
212 JOptionPane.PLAIN_MESSAGE,
213 options,
214 options[0]
215 );
216 switch (answer) {
217 case 0:
218 Main.pref.putColor("layer "+name, c.getColor());
219 break;
220 case 1:
221 return;
222 case 2:
223 Main.pref.putColor("layer "+name, null);
224 break;
225 }
226 Main.map.repaint();
227 }
228 });
229
230 JMenuItem syncaudio = new JMenuItem(tr("Synchronize Audio"), ImageProvider.get("audio-sync"));
231 syncaudio.putClientProperty("help", "Action/SynchronizeAudio");
232 syncaudio.addActionListener(new ActionListener(){
233 public void actionPerformed(ActionEvent e) {
234 if (! AudioPlayer.paused()) {
235 OptionPaneUtil.showMessageDialog(
236 Main.parent,
237 tr("You need to pause audio at the moment when you hear your synchronization cue."),
238 tr("Warning"),
239 JOptionPane.WARNING_MESSAGE
240 );
241 return;
242 }
243 AudioMarker recent = AudioMarker.recentlyPlayedMarker();
244 if (synchronizeAudioMarkers(recent)) {
245 OptionPaneUtil.showMessageDialog(
246 Main.parent,
247 tr("Audio synchronized at point {0}.", recent.text),
248 tr("Information"),
249 JOptionPane.INFORMATION_MESSAGE
250 );
251 } else {
252 OptionPaneUtil.showMessageDialog(
253 Main.parent,
254 tr("Unable to synchronize in layer being played."),
255 tr("Error"),
256 JOptionPane.ERROR_MESSAGE
257 );
258 }
259 }
260 });
261
262 JMenuItem moveaudio = new JMenuItem(tr("Make Audio Marker at Play Head"), ImageProvider.get("addmarkers"));
263 moveaudio.putClientProperty("help", "Action/MakeAudioMarkerAtPlayHead");
264 moveaudio.addActionListener(new ActionListener(){
265 public void actionPerformed(ActionEvent e) {
266 if (! AudioPlayer.paused()) {
267 OptionPaneUtil.showMessageDialog(
268 Main.parent,
269 tr("You need to have paused audio at the point on the track where you want the marker."),
270 tr("Warning"),
271 JOptionPane.WARNING_MESSAGE
272 );
273 return;
274 }
275 PlayHeadMarker playHeadMarker = Main.map.mapView.playHeadMarker;
276 if (playHeadMarker == null)
277 return;
278 addAudioMarker(playHeadMarker.time, playHeadMarker.getCoor());
279 Main.map.mapView.repaint();
280 }
281 });
282
283 Collection<Component> components = new ArrayList<Component>();
284 components.add(new JMenuItem(new LayerListDialog.ShowHideLayerAction(this)));
285 components.add(new JMenuItem(new LayerListDialog.ShowHideMarkerText(this)));
286 components.add(new JMenuItem(new LayerListDialog.DeleteLayerAction(this)));
287 components.add(new JSeparator());
288 components.add(color);
289 components.add(new JSeparator());
290 components.add(syncaudio);
291 if (Main.pref.getBoolean("marker.traceaudio", true)) {
292 components.add (moveaudio);
293 }
294 components.add(new JMenuItem(new RenameLayerAction(getAssociatedFile(), this)));
295 components.add(new JSeparator());
296 components.add(new JMenuItem(new LayerListPopup.InfoAction(this)));
297 return components.toArray(new Component[0]);
298 }
299
300 public boolean synchronizeAudioMarkers(AudioMarker startMarker) {
301 if (startMarker != null && ! data.contains(startMarker)) {
302 startMarker = null;
303 }
304 if (startMarker == null) {
305 // find the first audioMarker in this layer
306 for (Marker m : data) {
307 if (m instanceof AudioMarker) {
308 startMarker = (AudioMarker) m;
309 break;
310 }
311 }
312 }
313 if (startMarker == null)
314 return false;
315
316 // apply adjustment to all subsequent audio markers in the layer
317 double adjustment = AudioPlayer.position() - startMarker.offset; // in seconds
318 boolean seenStart = false;
319 URL url = startMarker.url();
320 for (Marker m : data) {
321 if (m == startMarker) {
322 seenStart = true;
323 }
324 if (seenStart) {
325 AudioMarker ma = (AudioMarker) m; // it must be an AudioMarker
326 if (ma.url().equals(url)) {
327 ma.adjustOffset(adjustment);
328 }
329 }
330 }
331 return true;
332 }
333
334 public AudioMarker addAudioMarker(double time, LatLon coor) {
335 // find first audio marker to get absolute start time
336 double offset = 0.0;
337 AudioMarker am = null;
338 for (Marker m : data) {
339 if (m.getClass() == AudioMarker.class) {
340 am = (AudioMarker)m;
341 offset = time - am.time;
342 break;
343 }
344 }
345 if (am == null) {
346 OptionPaneUtil.showMessageDialog(
347 Main.parent,
348 tr("No existing audio markers in this layer to offset from."),
349 tr("Error"),
350 JOptionPane.ERROR_MESSAGE
351 );
352 return null;
353 }
354
355 // make our new marker
356 AudioMarker newAudioMarker = AudioMarker.create(coor,
357 AudioMarker.inventName(offset), AudioPlayer.url().toString(), this, time, offset);
358
359 // insert it at the right place in a copy the collection
360 Collection<Marker> newData = new ArrayList<Marker>();
361 am = null;
362 AudioMarker ret = newAudioMarker; // save to have return value
363 for (Marker m : data) {
364 if (m.getClass() == AudioMarker.class) {
365 am = (AudioMarker) m;
366 if (newAudioMarker != null && offset < am.offset) {
367 newAudioMarker.adjustOffset(am.syncOffset()); // i.e. same as predecessor
368 newData.add(newAudioMarker);
369 newAudioMarker = null;
370 }
371 }
372 newData.add(m);
373 }
374
375 if (newAudioMarker != null) {
376 if (am != null) {
377 newAudioMarker.adjustOffset(am.syncOffset()); // i.e. same as predecessor
378 }
379 newData.add(newAudioMarker); // insert at end
380 }
381
382 // replace the collection
383 data.clear();
384 data.addAll(newData);
385 return ret;
386 }
387
388 public static void playAudio() {
389 playAdjacentMarker(null, true);
390 }
391
392 public static void playNextMarker() {
393 playAdjacentMarker(AudioMarker.recentlyPlayedMarker(), true);
394 }
395
396 public static void playPreviousMarker() {
397 playAdjacentMarker(AudioMarker.recentlyPlayedMarker(), false);
398 }
399
400 private static Marker getAdjacentMarker(Marker startMarker, boolean next, Layer layer) {
401 Marker previousMarker = null;
402 boolean nextTime = false;
403 if (layer.getClass() == MarkerLayer.class) {
404 MarkerLayer markerLayer = (MarkerLayer) layer;
405 for (Marker marker : markerLayer.data) {
406 if (marker == startMarker) {
407 if (next) {
408 nextTime = true;
409 } else {
410 if (previousMarker == null) {
411 previousMarker = startMarker; // if no previous one, play the first one again
412 }
413 return previousMarker;
414 }
415 }
416 else if (marker.getClass() == AudioMarker.class)
417 {
418 if(nextTime || startMarker == null)
419 return marker;
420 previousMarker = marker;
421 }
422 }
423 if (nextTime) // there was no next marker in that layer, so play the last one again
424 return startMarker;
425 }
426 return null;
427 }
428
429 private static void playAdjacentMarker(Marker startMarker, boolean next) {
430 Marker m = null;
431 if (Main.map == null || Main.map.mapView == null)
432 return;
433 Layer l = Main.map.mapView.getActiveLayer();
434 if(l != null) {
435 m = getAdjacentMarker(startMarker, next, l);
436 }
437 if(m == null)
438 {
439 for (Layer layer : Main.map.mapView.getAllLayers())
440 {
441 m = getAdjacentMarker(startMarker, next, layer);
442 if(m != null) {
443 break;
444 }
445 }
446 }
447 if(m != null) {
448 ((AudioMarker)m).play();
449 }
450 }
451
452}
Note: See TracBrowser for help on using the repository browser.