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

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

fixed #3193: No layer is left highlighted when the bottom most one is deleted
fixed some references to Way.nodes
clean up of warnings
added documentation in LayerListDialog

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