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

Last change on this file since 4604 was 4604, checked in by jttt, 13 years ago

Multikey action improvements (see #5515):

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