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

Last change on this file since 3754 was 3754, checked in by stoecker, 13 years ago

some cleanups in help page marking

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