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

Last change on this file since 1890 was 1890, checked in by Gubaer, 15 years ago

update: rewrite of layer dialog
new: allows multiple selection of layers in the dialog
new: move up, move down, toggle visibility, and delete on multiple layers
new: merge from an arbitrary layer into another layer, not only from the first into the second
new: new action for merging of the currently selected primitives on an arbitrary layer
new: make "active" layer explicit (special icon); activating a layer automatically moves it in the first position
refactoring: public fields 'name' and 'visible' on Layer are @deprecated. Use the setter/getters instead, Layer now emits PropertyChangeEvents if name or visibility are changed.

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