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