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

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

allow to color the entries in layer list dialog according to assigned layer drawing color

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