001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.AlphaComposite;
007import java.awt.Color;
008import java.awt.Dimension;
009import java.awt.Graphics;
010import java.awt.Graphics2D;
011import java.awt.Point;
012import java.awt.Rectangle;
013import java.awt.event.ComponentAdapter;
014import java.awt.event.ComponentEvent;
015import java.awt.event.KeyEvent;
016import java.awt.event.MouseAdapter;
017import java.awt.event.MouseEvent;
018import java.awt.event.MouseMotionListener;
019import java.awt.geom.Area;
020import java.awt.geom.GeneralPath;
021import java.awt.image.BufferedImage;
022import java.beans.PropertyChangeEvent;
023import java.beans.PropertyChangeListener;
024import java.util.ArrayList;
025import java.util.Arrays;
026import java.util.Collection;
027import java.util.Collections;
028import java.util.EnumSet;
029import java.util.LinkedHashSet;
030import java.util.List;
031import java.util.ListIterator;
032import java.util.Set;
033import java.util.concurrent.CopyOnWriteArrayList;
034
035import javax.swing.AbstractButton;
036import javax.swing.ActionMap;
037import javax.swing.InputMap;
038import javax.swing.JComponent;
039import javax.swing.JFrame;
040import javax.swing.JPanel;
041
042import org.openstreetmap.josm.Main;
043import org.openstreetmap.josm.actions.mapmode.MapMode;
044import org.openstreetmap.josm.data.Bounds;
045import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent;
046import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
047import org.openstreetmap.josm.data.SelectionChangedListener;
048import org.openstreetmap.josm.data.ViewportData;
049import org.openstreetmap.josm.data.coor.EastNorth;
050import org.openstreetmap.josm.data.coor.LatLon;
051import org.openstreetmap.josm.data.imagery.ImageryInfo;
052import org.openstreetmap.josm.data.osm.DataSet;
053import org.openstreetmap.josm.data.osm.OsmPrimitive;
054import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
055import org.openstreetmap.josm.data.osm.visitor.paint.Rendering;
056import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache;
057import org.openstreetmap.josm.gui.layer.GpxLayer;
058import org.openstreetmap.josm.gui.layer.ImageryLayer;
059import org.openstreetmap.josm.gui.layer.Layer;
060import org.openstreetmap.josm.gui.layer.MapViewPaintable;
061import org.openstreetmap.josm.gui.layer.OsmDataLayer;
062import org.openstreetmap.josm.gui.layer.NativeScaleLayer;
063import org.openstreetmap.josm.gui.layer.geoimage.GeoImageLayer;
064import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
065import org.openstreetmap.josm.gui.layer.markerlayer.PlayHeadMarker;
066import org.openstreetmap.josm.gui.util.GuiHelper;
067import org.openstreetmap.josm.tools.AudioPlayer;
068import org.openstreetmap.josm.tools.BugReportExceptionHandler;
069import org.openstreetmap.josm.tools.Shortcut;
070import org.openstreetmap.josm.tools.Utils;
071
072/**
073 * This is a component used in the {@link MapFrame} for browsing the map. It use is to
074 * provide the MapMode's enough capabilities to operate.<br><br>
075 *
076 * {@code MapView} holds meta-data about the data set currently displayed, as scale level,
077 * center point viewed, what scrolling mode or editing mode is selected or with
078 * what projection the map is viewed etc..<br><br>
079 *
080 * {@code MapView} is able to administrate several layers.
081 *
082 * @author imi
083 */
084public class MapView extends NavigatableComponent
085implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.LayerStateChangeListener {
086
087    /**
088     * Interface to notify listeners of a layer change.
089     * @author imi
090     */
091    public interface LayerChangeListener {
092
093        /**
094         * Notifies this listener that the active layer has changed.
095         * @param oldLayer The previous active layer
096         * @param newLayer The new activer layer
097         */
098        void activeLayerChange(Layer oldLayer, Layer newLayer);
099
100        /**
101         * Notifies this listener that a layer has been added.
102         * @param newLayer The new added layer
103         */
104        void layerAdded(Layer newLayer);
105
106        /**
107         * Notifies this listener that a layer has been removed.
108         * @param oldLayer The old removed layer
109         */
110        void layerRemoved(Layer oldLayer);
111    }
112
113    /**
114     * An interface that needs to be implemented in order to listen for changes to the active edit layer.
115     */
116    public interface EditLayerChangeListener {
117
118        /**
119         * Called after the active edit layer was changed.
120         * @param oldLayer The old edit layer
121         * @param newLayer The current (new) edit layer
122         */
123        void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer);
124    }
125
126    public boolean viewportFollowing;
127
128    /**
129     * the layer listeners
130     */
131    private static final CopyOnWriteArrayList<LayerChangeListener> layerChangeListeners = new CopyOnWriteArrayList<>();
132    private static final CopyOnWriteArrayList<EditLayerChangeListener> editLayerChangeListeners = new CopyOnWriteArrayList<>();
133
134    /**
135     * Removes a layer change listener
136     *
137     * @param listener the listener. Ignored if null or already registered.
138     */
139    public static void removeLayerChangeListener(LayerChangeListener listener) {
140        layerChangeListeners.remove(listener);
141    }
142
143    public static void removeEditLayerChangeListener(EditLayerChangeListener listener) {
144        editLayerChangeListeners.remove(listener);
145    }
146
147    /**
148     * Adds a layer change listener
149     *
150     * @param listener the listener. Ignored if null or already registered.
151     */
152    public static void addLayerChangeListener(LayerChangeListener listener) {
153        if (listener != null) {
154            layerChangeListeners.addIfAbsent(listener);
155        }
156    }
157
158    /**
159     * Adds a layer change listener
160     *
161     * @param listener the listener. Ignored if null or already registered.
162     * @param initialFire fire an active-layer-changed-event right after adding
163     * the listener in case there is a layer present (should be)
164     */
165    public static void addLayerChangeListener(LayerChangeListener listener, boolean initialFire) {
166        addLayerChangeListener(listener);
167        if (initialFire && Main.isDisplayingMapView()) {
168            listener.activeLayerChange(null, Main.map.mapView.getActiveLayer());
169        }
170    }
171
172    /**
173     * Adds an edit layer change listener
174     *
175     * @param listener the listener. Ignored if null or already registered.
176     * @param initialFire fire an edit-layer-changed-event right after adding
177     * the listener in case there is an edit layer present
178     */
179    public static void addEditLayerChangeListener(EditLayerChangeListener listener, boolean initialFire) {
180        addEditLayerChangeListener(listener);
181        if (initialFire && Main.isDisplayingMapView() && Main.map.mapView.getEditLayer() != null) {
182            listener.editLayerChanged(null, Main.map.mapView.getEditLayer());
183        }
184    }
185
186    /**
187     * Adds an edit layer change listener
188     *
189     * @param listener the listener. Ignored if null or already registered.
190     */
191    public static void addEditLayerChangeListener(EditLayerChangeListener listener) {
192        if (listener != null) {
193            editLayerChangeListeners.addIfAbsent(listener);
194        }
195    }
196
197    /**
198     * Calls the {@link LayerChangeListener#activeLayerChange(Layer, Layer)} method of all listeners.
199     *
200     * @param oldLayer The old layer
201     * @param newLayer The new active layer.
202     */
203    protected void fireActiveLayerChanged(Layer oldLayer, Layer newLayer) {
204        for (LayerChangeListener l : layerChangeListeners) {
205            l.activeLayerChange(oldLayer, newLayer);
206        }
207    }
208
209    protected void fireLayerAdded(Layer newLayer) {
210        for (MapView.LayerChangeListener l : MapView.layerChangeListeners) {
211            l.layerAdded(newLayer);
212        }
213    }
214
215    protected void fireLayerRemoved(Layer layer) {
216        for (MapView.LayerChangeListener l : MapView.layerChangeListeners) {
217            l.layerRemoved(layer);
218        }
219    }
220
221    protected void fireEditLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
222        for (EditLayerChangeListener l : editLayerChangeListeners) {
223            l.editLayerChanged(oldLayer, newLayer);
224        }
225    }
226
227    /**
228     * A list of all layers currently loaded.
229     */
230    private final transient List<Layer> layers = new ArrayList<>();
231
232    /**
233     * The play head marker: there is only one of these so it isn't in any specific layer
234     */
235    public transient PlayHeadMarker playHeadMarker;
236
237    /**
238     * The layer from the layers list that is currently active.
239     */
240    private transient Layer activeLayer;
241
242    /**
243     * The edit layer is the current active data layer.
244     */
245    private transient OsmDataLayer editLayer;
246
247    /**
248     * The last event performed by mouse.
249     */
250    public MouseEvent lastMEvent = new MouseEvent(this, 0, 0, 0, 0, 0, 0, false); // In case somebody reads it before first mouse move
251
252    /**
253     * Temporary layers (selection rectangle, etc.) that are never cached and
254     * drawn on top of regular layers.
255     * Access must be synchronized.
256     */
257    private final transient Set<MapViewPaintable> temporaryLayers = new LinkedHashSet<>();
258
259    private transient BufferedImage nonChangedLayersBuffer;
260    private transient BufferedImage offscreenBuffer;
261    // Layers that wasn't changed since last paint
262    private final transient List<Layer> nonChangedLayers = new ArrayList<>();
263    private transient Layer changedLayer;
264    private int lastViewID;
265    private boolean paintPreferencesChanged = true;
266    private Rectangle lastClipBounds = new Rectangle();
267    private transient MapMover mapMover;
268
269    /**
270     * Constructs a new {@code MapView}.
271     * @param contentPane The content pane used to register shortcuts in its
272     * {@link InputMap} and {@link ActionMap}
273     * @param viewportData the initial viewport of the map. Can be null, then
274     * the viewport is derived from the layer data.
275     */
276    public MapView(final JPanel contentPane, final ViewportData viewportData) {
277        initialViewport = viewportData;
278        Main.pref.addPreferenceChangeListener(this);
279
280        addComponentListener(new ComponentAdapter() {
281            @Override public void componentResized(ComponentEvent e) {
282                removeComponentListener(this);
283
284                for (JComponent c : getMapNavigationComponents(MapView.this)) {
285                    MapView.this.add(c);
286                }
287
288                mapMover = new MapMover(MapView.this, contentPane);
289            }
290        });
291
292        // listend to selection changes to redraw the map
293        DataSet.addSelectionListener(repaintSelectionChangedListener);
294
295        //store the last mouse action
296        this.addMouseMotionListener(new MouseMotionListener() {
297            @Override
298            public void mouseDragged(MouseEvent e) {
299                mouseMoved(e);
300            }
301
302            @Override
303            public void mouseMoved(MouseEvent e) {
304                lastMEvent = e;
305            }
306        });
307        this.addMouseListener(new MouseAdapter() {
308            @Override
309            public void mousePressed(MouseEvent me) {
310                // focus the MapView component when mouse is pressed inside it
311                requestFocus();
312            }
313        });
314
315        if (Shortcut.findShortcut(KeyEvent.VK_TAB, 0) != null) {
316            setFocusTraversalKeysEnabled(false);
317        }
318    }
319
320    /**
321     * Adds the map navigation components to a
322     * @param forMapView The map view to get the components for.
323     * @return A list containing the correctly positioned map navigation components.
324     */
325    public static List<? extends JComponent> getMapNavigationComponents(MapView forMapView) {
326        MapSlider zoomSlider = new MapSlider(forMapView);
327        zoomSlider.setBounds(3, 0, 114, 30);
328        zoomSlider.setFocusTraversalKeysEnabled(Shortcut.findShortcut(KeyEvent.VK_TAB, 0) == null);
329
330        MapScaler scaler = new MapScaler(forMapView);
331        scaler.setLocation(10, 30);
332
333        return Arrays.asList(zoomSlider, scaler);
334    }
335
336    // remebered geometry of the component
337    private Dimension oldSize;
338    private Point oldLoc;
339
340    /**
341     * Call this method to keep map position on screen during next repaint
342     */
343    public void rememberLastPositionOnScreen() {
344        oldSize = getSize();
345        oldLoc  = getLocationOnScreen();
346    }
347
348    /**
349     * Adds a GPX layer. A GPX layer is added below the lowest data layer.
350     * <p>
351     * Does not call {@link #fireLayerAdded(Layer)}.
352     *
353     * @param layer the GPX layer
354     */
355    protected void addGpxLayer(GpxLayer layer) {
356        synchronized (layers) {
357            if (layers.isEmpty()) {
358                layers.add(layer);
359                return;
360            }
361            for (int i = layers.size()-1; i >= 0; i--) {
362                if (layers.get(i) instanceof OsmDataLayer) {
363                    if (i == layers.size()-1) {
364                        layers.add(layer);
365                    } else {
366                        layers.add(i+1, layer);
367                    }
368                    return;
369                }
370            }
371            layers.add(0, layer);
372        }
373    }
374
375    /**
376     * Add a layer to the current MapView. The layer will be added at topmost
377     * position.
378     * @param layer The layer to add
379     */
380    public void addLayer(Layer layer) {
381        boolean isOsmDataLayer = layer instanceof OsmDataLayer;
382        EnumSet<LayerListenerType> listenersToFire = EnumSet.noneOf(LayerListenerType.class);
383        Layer oldActiveLayer = activeLayer;
384        OsmDataLayer oldEditLayer = editLayer;
385
386        synchronized (layers) {
387            if (layer instanceof MarkerLayer && playHeadMarker == null) {
388                playHeadMarker = PlayHeadMarker.create();
389            }
390
391            if (layer instanceof GpxLayer) {
392                addGpxLayer((GpxLayer) layer);
393            } else if (layers.isEmpty()) {
394                layers.add(layer);
395            } else if (layer.isBackgroundLayer()) {
396                int i = 0;
397                for (; i < layers.size(); i++) {
398                    if (layers.get(i).isBackgroundLayer()) {
399                        break;
400                    }
401                }
402                layers.add(i, layer);
403            } else {
404                layers.add(0, layer);
405            }
406
407            if (isOsmDataLayer || oldActiveLayer == null) {
408                // autoselect the new layer
409                listenersToFire.addAll(setActiveLayer(layer, true));
410            }
411
412            if (isOsmDataLayer) {
413                ((OsmDataLayer) layer).addLayerStateChangeListener(this);
414            }
415
416            if (layer instanceof NativeScaleLayer) {
417                Main.map.mapView.setNativeScaleLayer((NativeScaleLayer) layer);
418            }
419
420            layer.addPropertyChangeListener(this);
421            Main.addProjectionChangeListener(layer);
422            AudioPlayer.reset();
423        }
424        fireLayerAdded(layer);
425        onActiveEditLayerChanged(oldActiveLayer, oldEditLayer, listenersToFire);
426
427        if (!listenersToFire.isEmpty()) {
428            repaint();
429        }
430    }
431
432    @Override
433    protected DataSet getCurrentDataSet() {
434        synchronized (layers) {
435            if (editLayer != null)
436                return editLayer.data;
437            else
438                return null;
439        }
440    }
441
442    /**
443     * Replies true if the active data layer (edit layer) is drawable.
444     *
445     * @return true if the active data layer (edit layer) is drawable, false otherwise
446     */
447    public boolean isActiveLayerDrawable() {
448        synchronized (layers) {
449            return editLayer != null;
450        }
451    }
452
453    /**
454     * Replies true if the active data layer (edit layer) is visible.
455     *
456     * @return true if the active data layer (edit layer) is visible, false otherwise
457     */
458    public boolean isActiveLayerVisible() {
459        synchronized (layers) {
460            return isActiveLayerDrawable() && editLayer.isVisible();
461        }
462    }
463
464    /**
465     * Determines the next active data layer according to the following
466     * rules:
467     * <ul>
468     *   <li>if there is at least one {@link OsmDataLayer} the first one
469     *     becomes active</li>
470     *   <li>otherwise, the top most layer of any type becomes active</li>
471     * </ul>
472     * @param layersList lit of layers
473     *
474     * @return the next active data layer
475     */
476    protected Layer determineNextActiveLayer(List<Layer> layersList) {
477        // First look for data layer
478        for (Layer layer:layersList) {
479            if (layer instanceof OsmDataLayer)
480                return layer;
481        }
482
483        // Then any layer
484        if (!layersList.isEmpty())
485            return layersList.get(0);
486
487        // and then give up
488        return null;
489
490    }
491
492    /**
493     * Remove the layer from the mapview. If the layer was in the list before,
494     * an LayerChange event is fired.
495     * @param layer The layer to remove
496     */
497    public void removeLayer(Layer layer) {
498        EnumSet<LayerListenerType> listenersToFire = EnumSet.noneOf(LayerListenerType.class);
499        Layer oldActiveLayer = activeLayer;
500        OsmDataLayer oldEditLayer = editLayer;
501
502        synchronized (layers) {
503            List<Layer> layersList = new ArrayList<>(layers);
504
505            if (!layersList.remove(layer))
506                return;
507
508            listenersToFire = setEditLayer(layersList);
509
510            if (layer == activeLayer) {
511                listenersToFire.addAll(setActiveLayer(determineNextActiveLayer(layersList), false));
512            }
513
514            if (layer instanceof OsmDataLayer) {
515                ((OsmDataLayer) layer).removeLayerPropertyChangeListener(this);
516            }
517
518            layers.remove(layer);
519            Main.removeProjectionChangeListener(layer);
520            layer.removePropertyChangeListener(this);
521            layer.destroy();
522            AudioPlayer.reset();
523        }
524        onActiveEditLayerChanged(oldActiveLayer, oldEditLayer, listenersToFire);
525        fireLayerRemoved(layer);
526
527        repaint();
528    }
529
530    private void onEditLayerChanged(OsmDataLayer oldEditLayer) {
531        fireEditLayerChanged(oldEditLayer, editLayer);
532        refreshTitle();
533    }
534
535    private boolean virtualNodesEnabled;
536
537    public void setVirtualNodesEnabled(boolean enabled) {
538        if (virtualNodesEnabled != enabled) {
539            virtualNodesEnabled = enabled;
540            repaint();
541        }
542    }
543
544    /**
545     * Checks if virtual nodes should be drawn. Default is <code>false</code>
546     * @return The virtual nodes property.
547     * @see Rendering#render(DataSet, boolean, Bounds)
548     */
549    public boolean isVirtualNodesEnabled() {
550        return virtualNodesEnabled;
551    }
552
553    /**
554     * Moves the layer to the given new position. No event is fired, but repaints
555     * according to the new Z-Order of the layers.
556     *
557     * @param layer     The layer to move
558     * @param pos       The new position of the layer
559     */
560    public void moveLayer(Layer layer, int pos) {
561        EnumSet<LayerListenerType> listenersToFire;
562        Layer oldActiveLayer = activeLayer;
563        OsmDataLayer oldEditLayer = editLayer;
564
565        synchronized (layers) {
566            int curLayerPos = layers.indexOf(layer);
567            if (curLayerPos == -1)
568                throw new IllegalArgumentException(tr("Layer not in list."));
569            if (pos == curLayerPos)
570                return; // already in place.
571            layers.remove(curLayerPos);
572            if (pos >= layers.size()) {
573                layers.add(layer);
574            } else {
575                layers.add(pos, layer);
576            }
577            listenersToFire = setEditLayer(layers);
578            AudioPlayer.reset();
579        }
580        onActiveEditLayerChanged(oldActiveLayer, oldEditLayer, listenersToFire);
581
582        repaint();
583    }
584
585    /**
586     * Gets the index of the layer in the layer list.
587     * @param layer The layer to search for.
588     * @return The index in the list.
589     * @throws IllegalArgumentException if that layer does not belong to this view.
590     */
591    public int getLayerPos(Layer layer) {
592        int curLayerPos;
593        synchronized (layers) {
594            curLayerPos = layers.indexOf(layer);
595        }
596        if (curLayerPos == -1)
597            throw new IllegalArgumentException(tr("Layer not in list."));
598        return curLayerPos;
599    }
600
601    /**
602     * Creates a list of the visible layers in Z-Order, the layer with the lowest Z-Order
603     * first, layer with the highest Z-Order last.
604     * <p>
605     * The active data layer is pulled above all adjacent data layers.
606     *
607     * @return a list of the visible in Z-Order, the layer with the lowest Z-Order
608     * first, layer with the highest Z-Order last.
609     */
610    public List<Layer> getVisibleLayersInZOrder() {
611        synchronized (layers) {
612            List<Layer> ret = new ArrayList<>();
613            // This is set while we delay the addition of the active layer.
614            boolean activeLayerDelayed = false;
615            for (ListIterator<Layer> iterator = layers.listIterator(layers.size()); iterator.hasPrevious();) {
616                Layer l = iterator.previous();
617                if (!l.isVisible()) {
618                    // ignored
619                } else if (l == activeLayer && l instanceof OsmDataLayer) {
620                    // delay and add after the current block of OsmDataLayer
621                    activeLayerDelayed = true;
622                } else {
623                    if (activeLayerDelayed && !(l instanceof OsmDataLayer)) {
624                        // add active layer before the current one.
625                        ret.add(activeLayer);
626                        activeLayerDelayed = false;
627                    }
628                    // Add this layer now
629                    ret.add(l);
630                }
631            }
632            if (activeLayerDelayed) {
633                ret.add(activeLayer);
634            }
635            return ret;
636        }
637    }
638
639    private void paintLayer(Layer layer, Graphics2D g, Bounds box) {
640        if (layer.getOpacity() < 1) {
641            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) layer.getOpacity()));
642        }
643        layer.paint(g, this, box);
644        g.setPaintMode();
645    }
646
647    /**
648     * Draw the component.
649     */
650    @Override
651    public void paint(Graphics g) {
652        if (!prepareToDraw()) {
653            return;
654        }
655
656        List<Layer> visibleLayers = getVisibleLayersInZOrder();
657
658        int nonChangedLayersCount = 0;
659        for (Layer l: visibleLayers) {
660            if (l.isChanged() || l == changedLayer) {
661                break;
662            } else {
663                nonChangedLayersCount++;
664            }
665        }
666
667        boolean canUseBuffer;
668
669        synchronized (this) {
670            canUseBuffer = !paintPreferencesChanged;
671            paintPreferencesChanged = false;
672        }
673        canUseBuffer = canUseBuffer && nonChangedLayers.size() <= nonChangedLayersCount &&
674        lastViewID == getViewID() && lastClipBounds.contains(g.getClipBounds());
675        if (canUseBuffer) {
676            for (int i = 0; i < nonChangedLayers.size(); i++) {
677                if (visibleLayers.get(i) != nonChangedLayers.get(i)) {
678                    canUseBuffer = false;
679                    break;
680                }
681            }
682        }
683
684        if (null == offscreenBuffer || offscreenBuffer.getWidth() != getWidth() || offscreenBuffer.getHeight() != getHeight()) {
685            offscreenBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_3BYTE_BGR);
686        }
687
688        Graphics2D tempG = offscreenBuffer.createGraphics();
689        tempG.setClip(g.getClip());
690        Bounds box = getLatLonBounds(g.getClipBounds());
691
692        if (!canUseBuffer || nonChangedLayersBuffer == null) {
693            if (null == nonChangedLayersBuffer
694                    || nonChangedLayersBuffer.getWidth() != getWidth() || nonChangedLayersBuffer.getHeight() != getHeight()) {
695                nonChangedLayersBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_3BYTE_BGR);
696            }
697            Graphics2D g2 = nonChangedLayersBuffer.createGraphics();
698            g2.setClip(g.getClip());
699            g2.setColor(PaintColors.getBackgroundColor());
700            g2.fillRect(0, 0, getWidth(), getHeight());
701
702            for (int i = 0; i < nonChangedLayersCount; i++) {
703                paintLayer(visibleLayers.get(i), g2, box);
704            }
705        } else {
706            // Maybe there were more unchanged layers then last time - draw them to buffer
707            if (nonChangedLayers.size() != nonChangedLayersCount) {
708                Graphics2D g2 = nonChangedLayersBuffer.createGraphics();
709                g2.setClip(g.getClip());
710                for (int i = nonChangedLayers.size(); i < nonChangedLayersCount; i++) {
711                    paintLayer(visibleLayers.get(i), g2, box);
712                }
713            }
714        }
715
716        nonChangedLayers.clear();
717        changedLayer = null;
718        for (int i = 0; i < nonChangedLayersCount; i++) {
719            nonChangedLayers.add(visibleLayers.get(i));
720        }
721        lastViewID = getViewID();
722        lastClipBounds = g.getClipBounds();
723
724        tempG.drawImage(nonChangedLayersBuffer, 0, 0, null);
725
726        for (int i = nonChangedLayersCount; i < visibleLayers.size(); i++) {
727            paintLayer(visibleLayers.get(i), tempG, box);
728        }
729
730        synchronized (temporaryLayers) {
731            for (MapViewPaintable mvp : temporaryLayers) {
732                mvp.paint(tempG, this, box);
733            }
734        }
735
736        // draw world borders
737        tempG.setColor(Color.WHITE);
738        Bounds b = getProjection().getWorldBoundsLatLon();
739        double lat = b.getMinLat();
740        double lon = b.getMinLon();
741
742        Point p = getPoint(b.getMin());
743
744        GeneralPath path = new GeneralPath();
745
746        double d = 1.0;
747        path.moveTo(p.x, p.y);
748        double max = b.getMax().lat();
749        for (; lat <= max; lat += d) {
750            p = getPoint(new LatLon(lat >= max ? max : lat, lon));
751            path.lineTo(p.x, p.y);
752        }
753        lat = max; max = b.getMax().lon();
754        for (; lon <= max; lon += d) {
755            p = getPoint(new LatLon(lat, lon >= max ? max : lon));
756            path.lineTo(p.x, p.y);
757        }
758        lon = max; max = b.getMinLat();
759        for (; lat >= max; lat -= d) {
760            p = getPoint(new LatLon(lat <= max ? max : lat, lon));
761            path.lineTo(p.x, p.y);
762        }
763        lat = max; max = b.getMinLon();
764        for (; lon >= max; lon -= d) {
765            p = getPoint(new LatLon(lat, lon <= max ? max : lon));
766            path.lineTo(p.x, p.y);
767        }
768
769        int w = getWidth();
770        int h = getHeight();
771
772        // Work around OpenJDK having problems when drawing out of bounds
773        final Area border = new Area(path);
774        // Make the viewport 1px larger in every direction to prevent an
775        // additional 1px border when zooming in
776        final Area viewport = new Area(new Rectangle(-1, -1, w + 2, h + 2));
777        border.intersect(viewport);
778        tempG.draw(border);
779
780        if (Main.isDisplayingMapView() && Main.map.filterDialog != null) {
781            Main.map.filterDialog.drawOSDText(tempG);
782        }
783
784        if (playHeadMarker != null) {
785            playHeadMarker.paint(tempG, this);
786        }
787
788        g.drawImage(offscreenBuffer, 0, 0, null);
789        super.paint(g);
790    }
791
792    /**
793     * Sets up the viewport to prepare for drawing the view.
794     * @return <code>true</code> if the view can be drawn, <code>false</code> otherwise.
795     */
796    public boolean prepareToDraw() {
797        if (initialViewport != null) {
798            zoomTo(initialViewport);
799            initialViewport = null;
800        }
801        if (BugReportExceptionHandler.exceptionHandlingInProgress())
802            return false;
803
804        if (getCenter() == null)
805            return false; // no data loaded yet.
806
807        // if the position was remembered, we need to adjust center once before repainting
808        if (oldLoc != null && oldSize != null) {
809            Point l1  = getLocationOnScreen();
810            final EastNorth newCenter = new EastNorth(
811                    getCenter().getX()+ (l1.x-oldLoc.x - (oldSize.width-getWidth())/2.0)*getScale(),
812                    getCenter().getY()+ (oldLoc.y-l1.y + (oldSize.height-getHeight())/2.0)*getScale()
813                    );
814            oldLoc = null; oldSize = null;
815            zoomTo(newCenter);
816        }
817
818        return true;
819    }
820
821    /**
822     * @return An unmodifiable collection of all layers
823     */
824    public Collection<Layer> getAllLayers() {
825        synchronized (layers) {
826            return Collections.unmodifiableCollection(new ArrayList<>(layers));
827        }
828    }
829
830    /**
831     * @return An unmodifiable ordered list of all layers
832     */
833    public List<Layer> getAllLayersAsList() {
834        synchronized (layers) {
835            return Collections.unmodifiableList(new ArrayList<>(layers));
836        }
837    }
838
839    /**
840     * Replies an unmodifiable list of layers of a certain type.
841     *
842     * Example:
843     * <pre>
844     *     List&lt;WMSLayer&gt; wmsLayers = getLayersOfType(WMSLayer.class);
845     * </pre>
846     * @param <T> layer type
847     *
848     * @param ofType The layer type.
849     * @return an unmodifiable list of layers of a certain type.
850     */
851    public <T extends Layer> List<T> getLayersOfType(Class<T> ofType) {
852        return new ArrayList<>(Utils.filteredCollection(getAllLayers(), ofType));
853    }
854
855    /**
856     * Replies the number of layers managed by this map view
857     *
858     * @return the number of layers managed by this map view
859     */
860    public int getNumLayers() {
861        synchronized (layers) {
862            return layers.size();
863        }
864    }
865
866    /**
867     * Replies true if there is at least one layer in this map view
868     *
869     * @return true if there is at least one layer in this map view
870     */
871    public boolean hasLayers() {
872        return getNumLayers() > 0;
873    }
874
875    /**
876     * Sets the active edit layer.
877     * <p>
878     * @param layersList A list to select that layer from.
879     * @return A list of change listeners that should be fired using {@link #onActiveEditLayerChanged(Layer, OsmDataLayer, EnumSet)}
880     */
881    private EnumSet<LayerListenerType> setEditLayer(List<Layer> layersList) {
882        final OsmDataLayer newEditLayer = findNewEditLayer(layersList);
883
884        // Set new edit layer
885        if (newEditLayer != editLayer) {
886            if (newEditLayer == null) {
887                // Note: Unsafe to call while layer write lock is held.
888                getCurrentDataSet().setSelected();
889            }
890
891            editLayer = newEditLayer;
892            return EnumSet.of(LayerListenerType.EDIT_LAYER_CHANGE);
893        } else {
894            return EnumSet.noneOf(LayerListenerType.class);
895        }
896
897    }
898
899    private OsmDataLayer findNewEditLayer(List<Layer> layersList) {
900        OsmDataLayer newEditLayer = layersList.contains(editLayer) ? editLayer : null;
901        // Find new edit layer
902        if (activeLayer != editLayer || !layersList.contains(editLayer)) {
903            if (activeLayer instanceof OsmDataLayer && layersList.contains(activeLayer)) {
904                newEditLayer = (OsmDataLayer) activeLayer;
905            } else {
906                for (Layer layer:layersList) {
907                    if (layer instanceof OsmDataLayer) {
908                        newEditLayer = (OsmDataLayer) layer;
909                        break;
910                    }
911                }
912            }
913        }
914        return newEditLayer;
915    }
916
917    /**
918     * Sets the active layer to <code>layer</code>. If <code>layer</code> is an instance
919     * of {@link OsmDataLayer} also sets {@link #editLayer} to <code>layer</code>.
920     *
921     * @param layer the layer to be activate; must be one of the layers in the list of layers
922     * @throws IllegalArgumentException if layer is not in the list of layers
923     */
924    public void setActiveLayer(Layer layer) {
925        EnumSet<LayerListenerType> listenersToFire;
926        Layer oldActiveLayer;
927        OsmDataLayer oldEditLayer;
928
929        synchronized (layers) {
930            oldActiveLayer = activeLayer;
931            oldEditLayer = editLayer;
932            listenersToFire = setActiveLayer(layer, true);
933        }
934        onActiveEditLayerChanged(oldActiveLayer, oldEditLayer, listenersToFire);
935
936        repaint();
937    }
938
939    /**
940     * Sets the active layer. Propagates this change to all map buttons.
941     * @param layer The layer to be active.
942     * @param setEditLayer if this is <code>true</code>, the edit layer is also set.
943     * @return A list of change listeners that should be fired using {@link #onActiveEditLayerChanged(Layer, OsmDataLayer, EnumSet)}
944     */
945    private EnumSet<LayerListenerType> setActiveLayer(final Layer layer, boolean setEditLayer) {
946        if (layer != null && !layers.contains(layer))
947            throw new IllegalArgumentException(tr("Layer ''{0}'' must be in list of layers", layer.toString()));
948
949        if (layer == activeLayer)
950            return EnumSet.noneOf(LayerListenerType.class);
951
952        activeLayer = layer;
953        EnumSet<LayerListenerType> listenersToFire = EnumSet.of(LayerListenerType.ACTIVE_LAYER_CHANGE);
954        if (setEditLayer) {
955            listenersToFire.addAll(setEditLayer(layers));
956        }
957
958        return listenersToFire;
959    }
960
961    /**
962     * Replies the currently active layer
963     *
964     * @return the currently active layer (may be null)
965     */
966    public Layer getActiveLayer() {
967        synchronized (layers) {
968            return activeLayer;
969        }
970    }
971
972    private enum LayerListenerType {
973        ACTIVE_LAYER_CHANGE,
974        EDIT_LAYER_CHANGE
975    }
976
977    /**
978     * This is called whenever one of active layer/edit layer or both may have been changed,
979     * @param oldActive The old active layer
980     * @param oldEdit The old edit layer.
981     * @param listenersToFire A mask of listeners to fire using {@link LayerListenerType}s
982     */
983    private void onActiveEditLayerChanged(final Layer oldActive, final OsmDataLayer oldEdit, EnumSet<LayerListenerType> listenersToFire) {
984        if (listenersToFire.contains(LayerListenerType.EDIT_LAYER_CHANGE)) {
985            onEditLayerChanged(oldEdit);
986        }
987        if (listenersToFire.contains(LayerListenerType.ACTIVE_LAYER_CHANGE)) {
988            onActiveLayerChanged(oldActive);
989        }
990    }
991
992    private void onActiveLayerChanged(final Layer old) {
993        fireActiveLayerChanged(old, activeLayer);
994
995        /* This only makes the buttons look disabled. Disabling the actions as well requires
996         * the user to re-select the tool after i.e. moving a layer. While testing I found
997         * that I switch layers and actions at the same time and it was annoying to mind the
998         * order. This way it works as visual clue for new users */
999        for (final AbstractButton b: Main.map.allMapModeButtons) {
1000            MapMode mode = (MapMode) b.getAction();
1001            final boolean activeLayerSupported = mode.layerIsSupported(activeLayer);
1002            if (activeLayerSupported) {
1003                Main.registerActionShortcut(mode, mode.getShortcut()); //fix #6876
1004            } else {
1005                Main.unregisterShortcut(mode.getShortcut());
1006            }
1007            GuiHelper.runInEDTAndWait(new Runnable() {
1008                @Override public void run() {
1009                    b.setEnabled(activeLayerSupported);
1010                }
1011            });
1012        }
1013        AudioPlayer.reset();
1014        repaint();
1015    }
1016
1017    /**
1018     * Replies the current edit layer, if any
1019     *
1020     * @return the current edit layer. May be null.
1021     */
1022    public OsmDataLayer getEditLayer() {
1023        synchronized (layers) {
1024            return editLayer;
1025        }
1026    }
1027
1028    /**
1029     * replies true if the list of layers managed by this map view contain layer
1030     *
1031     * @param layer the layer
1032     * @return true if the list of layers managed by this map view contain layer
1033     */
1034    public boolean hasLayer(Layer layer) {
1035        synchronized (layers) {
1036            return layers.contains(layer);
1037        }
1038    }
1039
1040    /**
1041     * Adds a new temporary layer.
1042     * <p>
1043     * A temporary layer is a layer that is painted above all normal layers. Layers are painted in the order they are added.
1044     *
1045     * @param mvp The layer to paint.
1046     * @return <code>true</code> if the layer was added.
1047     */
1048    public boolean addTemporaryLayer(MapViewPaintable mvp) {
1049        synchronized (temporaryLayers) {
1050            return temporaryLayers.add(mvp);
1051        }
1052    }
1053
1054    /**
1055     * Removes a layer previously added as temporary layer.
1056     * @param mvp The layer to remove.
1057     * @return <code>true</code> if that layer was removed.
1058     */
1059    public boolean removeTemporaryLayer(MapViewPaintable mvp) {
1060        synchronized (temporaryLayers) {
1061            return temporaryLayers.remove(mvp);
1062        }
1063    }
1064
1065    /**
1066     * Gets a list of temporary layers.
1067     * @return The layers in the order they are added.
1068     */
1069    public List<MapViewPaintable> getTemporaryLayers() {
1070        synchronized (temporaryLayers) {
1071            return Collections.unmodifiableList(new ArrayList<>(temporaryLayers));
1072        }
1073    }
1074
1075    @Override
1076    public void propertyChange(PropertyChangeEvent evt) {
1077        if (evt.getPropertyName().equals(Layer.VISIBLE_PROP)) {
1078            repaint();
1079        } else if (evt.getPropertyName().equals(Layer.OPACITY_PROP) ||
1080                evt.getPropertyName().equals(Layer.FILTER_STATE_PROP)) {
1081            Layer l = (Layer) evt.getSource();
1082            if (l.isVisible()) {
1083                changedLayer = l;
1084                repaint();
1085            }
1086        } else if (evt.getPropertyName().equals(OsmDataLayer.REQUIRES_SAVE_TO_DISK_PROP)
1087                || evt.getPropertyName().equals(OsmDataLayer.REQUIRES_UPLOAD_TO_SERVER_PROP)) {
1088            OsmDataLayer layer = (OsmDataLayer) evt.getSource();
1089            if (layer == getEditLayer()) {
1090                refreshTitle();
1091            }
1092        }
1093    }
1094
1095    /**
1096     * Sets the title of the JOSM main window, adding a star if there are dirty layers.
1097     * @see Main#parent
1098     */
1099    protected void refreshTitle() {
1100        if (Main.parent != null) {
1101            synchronized (layers) {
1102                boolean dirty = editLayer != null &&
1103                        (editLayer.requiresSaveToFile() || (editLayer.requiresUploadToServer() && !editLayer.isUploadDiscouraged()));
1104                ((JFrame) Main.parent).setTitle((dirty ? "* " : "") + tr("Java OpenStreetMap Editor"));
1105                ((JFrame) Main.parent).getRootPane().putClientProperty("Window.documentModified", dirty);
1106            }
1107        }
1108    }
1109
1110    @Override
1111    public void preferenceChanged(PreferenceChangeEvent e) {
1112        synchronized (this) {
1113            paintPreferencesChanged = true;
1114        }
1115    }
1116
1117    private final transient SelectionChangedListener repaintSelectionChangedListener = new SelectionChangedListener() {
1118        @Override
1119        public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
1120            repaint();
1121        }
1122    };
1123
1124    public void destroy() {
1125        Main.pref.removePreferenceChangeListener(this);
1126        DataSet.removeSelectionListener(repaintSelectionChangedListener);
1127        MultipolygonCache.getInstance().clear(this);
1128        if (mapMover != null) {
1129            mapMover.destroy();
1130        }
1131        synchronized (layers) {
1132            activeLayer = null;
1133            changedLayer = null;
1134            editLayer = null;
1135            layers.clear();
1136            nonChangedLayers.clear();
1137        }
1138        synchronized (temporaryLayers) {
1139            temporaryLayers.clear();
1140        }
1141    }
1142
1143    @Override
1144    public void uploadDiscouragedChanged(OsmDataLayer layer, boolean newValue) {
1145        if (layer == getEditLayer()) {
1146            refreshTitle();
1147        }
1148    }
1149
1150    /**
1151     * Get a string representation of all layers suitable for the {@code source} changeset tag.
1152     * @return A String of sources separated by ';'
1153     */
1154    public String getLayerInformationForSourceTag() {
1155        final Collection<String> layerInfo = new ArrayList<>();
1156        if (!getLayersOfType(GpxLayer.class).isEmpty()) {
1157            // no i18n for international values
1158            layerInfo.add("survey");
1159        }
1160        for (final GeoImageLayer i : getLayersOfType(GeoImageLayer.class)) {
1161            if (i.isVisible()) {
1162                layerInfo.add(i.getName());
1163            }
1164        }
1165        for (final ImageryLayer i : getLayersOfType(ImageryLayer.class)) {
1166            if (i.isVisible()) {
1167                layerInfo.add(ImageryInfo.ImageryType.BING.equals(i.getInfo().getImageryType()) ? "Bing" : i.getName());
1168            }
1169        }
1170        return Utils.join("; ", layerInfo);
1171    }
1172
1173    /**
1174     * This is a listener that gets informed whenever repaint is called for this MapView.
1175     * <p>
1176     * This is the only safe method to find changes to the map view, since many components call MapView.repaint() directly.
1177     * @author Michael Zangl
1178     */
1179    public interface RepaintListener {
1180        /**
1181         * Called when any repaint method is called (using default arguments if required).
1182         * @param tm see {@link JComponent#repaint(long, int, int, int, int)}
1183         * @param x see {@link JComponent#repaint(long, int, int, int, int)}
1184         * @param y see {@link JComponent#repaint(long, int, int, int, int)}
1185         * @param width see {@link JComponent#repaint(long, int, int, int, int)}
1186         * @param height see {@link JComponent#repaint(long, int, int, int, int)}
1187         */
1188        void repaint(long tm, int x, int y, int width, int height);
1189    }
1190
1191    private final transient CopyOnWriteArrayList<RepaintListener> repaintListeners = new CopyOnWriteArrayList<>();
1192
1193    /**
1194     * Adds a listener that gets informed whenever repaint() is called for this class.
1195     * @param l The listener.
1196     */
1197    public void addRepaintListener(RepaintListener l) {
1198        repaintListeners.add(l);
1199    }
1200
1201    /**
1202     * Removes a registered repaint listener.
1203     * @param l The listener.
1204     */
1205    public void removeRepaintListener(RepaintListener l) {
1206        repaintListeners.remove(l);
1207    }
1208
1209    @Override
1210    public void repaint(long tm, int x, int y, int width, int height) {
1211        // This is the main repaint method, all other methods are convenience methods and simply call this method.
1212        // This is just an observation, not a must, but seems to be true for all implementations I found so far.
1213        if (repaintListeners != null) {
1214            // Might get called early in super constructor
1215            for (RepaintListener l : repaintListeners) {
1216                l.repaint(tm, x, y, width, height);
1217            }
1218        }
1219        super.repaint(tm, x, y, width, height);
1220    }
1221}