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

Last change on this file since 3408 was 3408, checked in by jttt, 14 years ago

Show only actions that can work on all selected layers in LayerListDialog popup menu

  • Property svn:eol-style set to native
File size: 17.8 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.Graphics2D;
11import java.awt.Point;
12import java.awt.event.ActionEvent;
13import java.awt.event.MouseAdapter;
14import java.awt.event.MouseEvent;
15import java.io.File;
16import java.net.URL;
17import java.util.ArrayList;
18import java.util.Collection;
19import java.util.List;
20
21import javax.swing.AbstractAction;
22import javax.swing.Action;
23import javax.swing.Icon;
24import javax.swing.JCheckBoxMenuItem;
25import javax.swing.JColorChooser;
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.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(Graphics2D g, MapView mv, Bounds box) {
156 boolean showTextOrIcon = isTextOrIconShown();
157 g.setColor(getColor(getName()));
158
159 if (mousePressed) {
160 boolean mousePressedTmp = mousePressed;
161 Point mousePos = mv.getMousePosition(); // Get mouse position only when necessary (it's the slowest part of marker layer painting)
162 for (Marker mkr : data) {
163 if (mousePos != null && mkr.containsPoint(mousePos)) {
164 mkr.paint(g, mv, mousePressedTmp, showTextOrIcon);
165 mousePressedTmp = false;
166 }
167 }
168 } else {
169 for (Marker mkr : data) {
170 mkr.paint(g, mv, false, showTextOrIcon);
171 }
172 }
173 }
174
175 @Override public String getToolTipText() {
176 return data.size()+" "+trn("marker", "markers", data.size());
177 }
178
179 @Override public void mergeFrom(Layer from) {
180 MarkerLayer layer = (MarkerLayer)from;
181 data.addAll(layer.data);
182 }
183
184 @Override public boolean isMergable(Layer other) {
185 return other instanceof MarkerLayer;
186 }
187
188 @Override public void visitBoundingBox(BoundingXYVisitor v) {
189 for (Marker mkr : data) {
190 v.visit(mkr.getEastNorth());
191 }
192 }
193
194 @Override public Object getInfoComponent() {
195 return "<html>"+trn("{0} consists of {1} marker", "{0} consists of {1} markers", data.size(), getName(), data.size()) + "</html>";
196 }
197
198 @Override public Action[] getMenuEntries() {
199 Collection<Action> components = new ArrayList<Action>();
200 components.add(LayerListDialog.getInstance().createShowHideLayerAction());
201 components.add(new ShowHideMarkerText(this));
202 components.add(LayerListDialog.getInstance().createDeleteLayerAction());
203 components.add(SeparatorLayerAction.INSTANCE);
204 components.add(new CustomizeColor());
205 components.add(SeparatorLayerAction.INSTANCE);
206 components.add(new SynchronizeAudio());
207 if (Main.pref.getBoolean("marker.traceaudio", true)) {
208 components.add (new MoveAudio());
209 }
210 components.add(new RenameLayerAction(getAssociatedFile(), this));
211 components.add(SeparatorLayerAction.INSTANCE);
212 components.add(new LayerListPopup.InfoAction(this));
213 return components.toArray(new Action[0]);
214 }
215
216 public boolean synchronizeAudioMarkers(AudioMarker startMarker) {
217 if (startMarker != null && ! data.contains(startMarker)) {
218 startMarker = null;
219 }
220 if (startMarker == null) {
221 // find the first audioMarker in this layer
222 for (Marker m : data) {
223 if (m instanceof AudioMarker) {
224 startMarker = (AudioMarker) m;
225 break;
226 }
227 }
228 }
229 if (startMarker == null)
230 return false;
231
232 // apply adjustment to all subsequent audio markers in the layer
233 double adjustment = AudioPlayer.position() - startMarker.offset; // in seconds
234 boolean seenStart = false;
235 URL url = startMarker.url();
236 for (Marker m : data) {
237 if (m == startMarker) {
238 seenStart = true;
239 }
240 if (seenStart) {
241 AudioMarker ma = (AudioMarker) m; // it must be an AudioMarker
242 if (ma.url().equals(url)) {
243 ma.adjustOffset(adjustment);
244 }
245 }
246 }
247 return true;
248 }
249
250 public AudioMarker addAudioMarker(double time, LatLon coor) {
251 // find first audio marker to get absolute start time
252 double offset = 0.0;
253 AudioMarker am = null;
254 for (Marker m : data) {
255 if (m.getClass() == AudioMarker.class) {
256 am = (AudioMarker)m;
257 offset = time - am.time;
258 break;
259 }
260 }
261 if (am == null) {
262 JOptionPane.showMessageDialog(
263 Main.parent,
264 tr("No existing audio markers in this layer to offset from."),
265 tr("Error"),
266 JOptionPane.ERROR_MESSAGE
267 );
268 return null;
269 }
270
271 // make our new marker
272 AudioMarker newAudioMarker = AudioMarker.create(coor,
273 AudioMarker.inventName(offset), AudioPlayer.url().toString(), this, time, offset);
274
275 // insert it at the right place in a copy the collection
276 Collection<Marker> newData = new ArrayList<Marker>();
277 am = null;
278 AudioMarker ret = newAudioMarker; // save to have return value
279 for (Marker m : data) {
280 if (m.getClass() == AudioMarker.class) {
281 am = (AudioMarker) m;
282 if (newAudioMarker != null && offset < am.offset) {
283 newAudioMarker.adjustOffset(am.syncOffset()); // i.e. same as predecessor
284 newData.add(newAudioMarker);
285 newAudioMarker = null;
286 }
287 }
288 newData.add(m);
289 }
290
291 if (newAudioMarker != null) {
292 if (am != null) {
293 newAudioMarker.adjustOffset(am.syncOffset()); // i.e. same as predecessor
294 }
295 newData.add(newAudioMarker); // insert at end
296 }
297
298 // replace the collection
299 data.clear();
300 data.addAll(newData);
301 return ret;
302 }
303
304 public static void playAudio() {
305 playAdjacentMarker(null, true);
306 }
307
308 public static void playNextMarker() {
309 playAdjacentMarker(AudioMarker.recentlyPlayedMarker(), true);
310 }
311
312 public static void playPreviousMarker() {
313 playAdjacentMarker(AudioMarker.recentlyPlayedMarker(), false);
314 }
315
316 private static Marker getAdjacentMarker(Marker startMarker, boolean next, Layer layer) {
317 Marker previousMarker = null;
318 boolean nextTime = false;
319 if (layer.getClass() == MarkerLayer.class) {
320 MarkerLayer markerLayer = (MarkerLayer) layer;
321 for (Marker marker : markerLayer.data) {
322 if (marker == startMarker) {
323 if (next) {
324 nextTime = true;
325 } else {
326 if (previousMarker == null) {
327 previousMarker = startMarker; // if no previous one, play the first one again
328 }
329 return previousMarker;
330 }
331 }
332 else if (marker.getClass() == AudioMarker.class)
333 {
334 if(nextTime || startMarker == null)
335 return marker;
336 previousMarker = marker;
337 }
338 }
339 if (nextTime) // there was no next marker in that layer, so play the last one again
340 return startMarker;
341 }
342 return null;
343 }
344
345 private static void playAdjacentMarker(Marker startMarker, boolean next) {
346 Marker m = null;
347 if (Main.map == null || Main.map.mapView == null)
348 return;
349 Layer l = Main.map.mapView.getActiveLayer();
350 if(l != null) {
351 m = getAdjacentMarker(startMarker, next, l);
352 }
353 if(m == null)
354 {
355 for (Layer layer : Main.map.mapView.getAllLayers())
356 {
357 m = getAdjacentMarker(startMarker, next, layer);
358 if(m != null) {
359 break;
360 }
361 }
362 }
363 if(m != null) {
364 ((AudioMarker)m).play();
365 }
366 }
367
368 private boolean isTextOrIconShown() {
369 String current = Main.pref.get("marker.show "+getName(),"show");
370 return "show".equalsIgnoreCase(current);
371 }
372
373 public static final class ShowHideMarkerText extends AbstractAction implements LayerAction {
374 private final MarkerLayer layer;
375
376 public ShowHideMarkerText(MarkerLayer layer) {
377 super(tr("Show Text/Icons"), ImageProvider.get("dialogs", "showhide"));
378 putValue(SHORT_DESCRIPTION, tr("Toggle visible state of the marker text and icons."));
379 putValue("help", "Action/ShowHideTextIcons");
380 this.layer = layer;
381 }
382
383
384 public void actionPerformed(ActionEvent e) {
385 Main.pref.put("marker.show "+layer.getName(), layer.isTextOrIconShown() ? "hide" : "show");
386 Main.map.mapView.repaint();
387 }
388
389
390 @Override
391 public Component createMenuComponent() {
392 JCheckBoxMenuItem showMarkerTextItem = new JCheckBoxMenuItem(this);
393 showMarkerTextItem.setState(layer.isTextOrIconShown());
394 return showMarkerTextItem;
395 }
396
397
398 @Override
399 public boolean supportLayers(List<Layer> layers) {
400 return layers.size() == 1 && layers.get(0) instanceof MarkerLayer;
401 }
402 }
403
404 private class CustomizeColor extends AbstractAction {
405
406 public CustomizeColor() {
407 super(tr("Customize Color"), ImageProvider.get("colorchooser"));
408 putValue("help", "Action/LayerCustomizeColor");
409 }
410
411 @Override
412 public void actionPerformed(ActionEvent e) {
413 JColorChooser c = new JColorChooser(getColor(getName()));
414 Object[] options = new Object[]{tr("OK"), tr("Cancel"), tr("Default")};
415 int answer = JOptionPane.showOptionDialog(
416 Main.parent,
417 c,
418 tr("Choose a color"),
419 JOptionPane.OK_CANCEL_OPTION,
420 JOptionPane.PLAIN_MESSAGE,
421 null,
422 options,
423 options[0]
424 );
425 switch (answer) {
426 case 0:
427 Main.pref.putColor("layer "+getName(), c.getColor());
428 break;
429 case 1:
430 return;
431 case 2:
432 Main.pref.putColor("layer "+getName(), null);
433 break;
434 }
435 Main.map.repaint();
436 }
437 }
438
439 private class SynchronizeAudio extends AbstractAction {
440
441 public SynchronizeAudio() {
442 super(tr("Synchronize Audio"), ImageProvider.get("audio-sync"));
443 putValue("help", "Action/SynchronizeAudio");
444 }
445
446 @Override
447 public void actionPerformed(ActionEvent e) {
448 if (! AudioPlayer.paused()) {
449 JOptionPane.showMessageDialog(
450 Main.parent,
451 tr("You need to pause audio at the moment when you hear your synchronization cue."),
452 tr("Warning"),
453 JOptionPane.WARNING_MESSAGE
454 );
455 return;
456 }
457 AudioMarker recent = AudioMarker.recentlyPlayedMarker();
458 if (synchronizeAudioMarkers(recent)) {
459 JOptionPane.showMessageDialog(
460 Main.parent,
461 tr("Audio synchronized at point {0}.", recent.text),
462 tr("Information"),
463 JOptionPane.INFORMATION_MESSAGE
464 );
465 } else {
466 JOptionPane.showMessageDialog(
467 Main.parent,
468 tr("Unable to synchronize in layer being played."),
469 tr("Error"),
470 JOptionPane.ERROR_MESSAGE
471 );
472 }
473 }
474 }
475
476 private class MoveAudio extends AbstractAction {
477
478 public MoveAudio() {
479 super(tr("Make Audio Marker at Play Head"), ImageProvider.get("addmarkers"));
480 putValue("help", "Action/MakeAudioMarkerAtPlayHead");
481 }
482
483 @Override
484 public void actionPerformed(ActionEvent e) {
485 if (! AudioPlayer.paused()) {
486 JOptionPane.showMessageDialog(
487 Main.parent,
488 tr("You need to have paused audio at the point on the track where you want the marker."),
489 tr("Warning"),
490 JOptionPane.WARNING_MESSAGE
491 );
492 return;
493 }
494 PlayHeadMarker playHeadMarker = Main.map.mapView.playHeadMarker;
495 if (playHeadMarker == null)
496 return;
497 addAudioMarker(playHeadMarker.time, playHeadMarker.getCoor());
498 Main.map.mapView.repaint();
499 }
500 }
501}
Note: See TracBrowser for help on using the repository browser.