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

Last change on this file since 5681 was 5681, checked in by bastiK, 11 years ago

some simple refactoring

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