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

Last change on this file since 5481 was 5481, checked in by stoecker, 12 years ago

some cleanup, mainly javadoc

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