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

Revision 4831, 18.4 KB checked in by bastiK, 4 months ago (diff)

fixed #6851 - Regression: Voice Files (*.wav) not playing

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