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     *
468     * @return the next active data layer
469     */
470    protected Layer determineNextActiveLayer(List<Layer> layersList) {
471        // First look for data layer
472        for (Layer layer:layersList) {
473            if (layer instanceof OsmDataLayer)
474                return layer;
475        }
476
477        // Then any layer
478        if (!layersList.isEmpty())
479            return layersList.get(0);
480
481        // and then give up
482        return null;
483
484    }
485
486    /**
487     * Remove the layer from the mapview. If the layer was in the list before,
488     * an LayerChange event is fired.
489     * @param layer The layer to remove
490     */
491    public void removeLayer(Layer layer) {
492        EnumSet<LayerListenerType> listenersToFire = EnumSet.noneOf(LayerListenerType.class);
493        Layer oldActiveLayer = activeLayer;
494        OsmDataLayer oldEditLayer = editLayer;
495
496        synchronized (layers) {
497            List<Layer> layersList = new ArrayList<>(layers);
498
499            if (!layersList.remove(layer))
500                return;
501
502            listenersToFire = setEditLayer(layersList);
503
504            if (layer == activeLayer) {
505                listenersToFire.addAll(setActiveLayer(determineNextActiveLayer(layersList), false));
506            }
507
508            if (layer instanceof OsmDataLayer) {
509                ((OsmDataLayer) layer).removeLayerPropertyChangeListener(this);
510            }
511
512            layers.remove(layer);
513            Main.removeProjectionChangeListener(layer);
514            layer.removePropertyChangeListener(this);
515            layer.destroy();
516            AudioPlayer.reset();
517        }
518        onActiveEditLayerChanged(oldActiveLayer, oldEditLayer, listenersToFire);
519        fireLayerRemoved(layer);
520
521        repaint();
522    }
523
524    private void onEditLayerChanged(OsmDataLayer oldEditLayer) {
525        fireEditLayerChanged(oldEditLayer, editLayer);
526        refreshTitle();
527    }
528
529    private boolean virtualNodesEnabled;
530
531    public void setVirtualNodesEnabled(boolean enabled) {
532        if (virtualNodesEnabled != enabled) {
533            virtualNodesEnabled = enabled;
534            repaint();
535        }
536    }
537
538    /**
539     * Checks if virtual nodes should be drawn. Default is <code>false</code>
540     * @return The virtual nodes property.
541     * @see Rendering#render(DataSet, boolean, Bounds)
542     */
543    public boolean isVirtualNodesEnabled() {
544        return virtualNodesEnabled;
545    }
546
547    /**
548     * Moves the layer to the given new position. No event is fired, but repaints
549     * according to the new Z-Order of the layers.
550     *
551     * @param layer     The layer to move
552     * @param pos       The new position of the layer
553     */
554    public void moveLayer(Layer layer, int pos) {
555        EnumSet<LayerListenerType> listenersToFire;
556        Layer oldActiveLayer = activeLayer;
557        OsmDataLayer oldEditLayer = editLayer;
558
559        synchronized (layers) {
560            int curLayerPos = layers.indexOf(layer);
561            if (curLayerPos == -1)
562                throw new IllegalArgumentException(tr("Layer not in list."));
563            if (pos == curLayerPos)
564                return; // already in place.
565            layers.remove(curLayerPos);
566            if (pos >= layers.size()) {
567                layers.add(layer);
568            } else {
569                layers.add(pos, layer);
570            }
571            listenersToFire = setEditLayer(layers);
572            AudioPlayer.reset();
573        }
574        onActiveEditLayerChanged(oldActiveLayer, oldEditLayer, listenersToFire);
575
576        repaint();
577    }
578
579    /**
580     * Gets the index of the layer in the layer list.
581     * @param layer The layer to search for.
582     * @return The index in the list.
583     * @throws IllegalArgumentException if that layer does not belong to this view.
584     */
585    public int getLayerPos(Layer layer) {
586        int curLayerPos;
587        synchronized (layers) {
588            curLayerPos = layers.indexOf(layer);
589        }
590        if (curLayerPos == -1)
591            throw new IllegalArgumentException(tr("Layer not in list."));
592        return curLayerPos;
593    }
594
595    /**
596     * Creates a list of the visible layers in Z-Order, the layer with the lowest Z-Order
597     * first, layer with the highest Z-Order last.
598     * <p>
599     * The active data layer is pulled above all adjacent data layers.
600     *
601     * @return a list of the visible in Z-Order, the layer with the lowest Z-Order
602     * first, layer with the highest Z-Order last.
603     */
604    public List<Layer> getVisibleLayersInZOrder() {
605        synchronized (layers) {
606            List<Layer> ret = new ArrayList<>();
607            // This is set while we delay the addition of the active layer.
608            boolean activeLayerDelayed = false;
609            for (ListIterator<Layer> iterator = layers.listIterator(layers.size()); iterator.hasPrevious();) {
610                Layer l = iterator.previous();
611                if (!l.isVisible()) {
612                    // ignored
613                } else if (l == activeLayer && l instanceof OsmDataLayer) {
614                    // delay and add after the current block of OsmDataLayer
615                    activeLayerDelayed = true;
616                } else {
617                    if (activeLayerDelayed && !(l instanceof OsmDataLayer)) {
618                        // add active layer before the current one.
619                        ret.add(activeLayer);
620                        activeLayerDelayed = false;
621                    }
622                    // Add this layer now
623                    ret.add(l);
624                }
625            }
626            if (activeLayerDelayed) {
627                ret.add(activeLayer);
628            }
629            return ret;
630        }
631    }
632
633    private void paintLayer(Layer layer, Graphics2D g, Bounds box) {
634        if (layer.getOpacity() < 1) {
635            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) layer.getOpacity()));
636        }
637        layer.paint(g, this, box);
638        g.setPaintMode();
639    }
640
641    /**
642     * Draw the component.
643     */
644    @Override
645    public void paint(Graphics g) {
646        if (!prepareToDraw()) {
647            return;
648        }
649
650        List<Layer> visibleLayers = getVisibleLayersInZOrder();
651
652        int nonChangedLayersCount = 0;
653        for (Layer l: visibleLayers) {
654            if (l.isChanged() || l == changedLayer) {
655                break;
656            } else {
657                nonChangedLayersCount++;
658            }
659        }
660
661        boolean canUseBuffer;
662
663        synchronized (this) {
664            canUseBuffer = !paintPreferencesChanged;
665            paintPreferencesChanged = false;
666        }
667        canUseBuffer = canUseBuffer && nonChangedLayers.size() <= nonChangedLayersCount &&
668        lastViewID == getViewID() && lastClipBounds.contains(g.getClipBounds());
669        if (canUseBuffer) {
670            for (int i = 0; i < nonChangedLayers.size(); i++) {
671                if (visibleLayers.get(i) != nonChangedLayers.get(i)) {
672                    canUseBuffer = false;
673                    break;
674                }
675            }
676        }
677
678        if (null == offscreenBuffer || offscreenBuffer.getWidth() != getWidth() || offscreenBuffer.getHeight() != getHeight()) {
679            offscreenBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_3BYTE_BGR);
680        }
681
682        Graphics2D tempG = offscreenBuffer.createGraphics();
683        tempG.setClip(g.getClip());
684        Bounds box = getLatLonBounds(g.getClipBounds());
685
686        if (!canUseBuffer || nonChangedLayersBuffer == null) {
687            if (null == nonChangedLayersBuffer
688                    || nonChangedLayersBuffer.getWidth() != getWidth() || nonChangedLayersBuffer.getHeight() != getHeight()) {
689                nonChangedLayersBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_3BYTE_BGR);
690            }
691            Graphics2D g2 = nonChangedLayersBuffer.createGraphics();
692            g2.setClip(g.getClip());
693            g2.setColor(PaintColors.getBackgroundColor());
694            g2.fillRect(0, 0, getWidth(), getHeight());
695
696            for (int i = 0; i < nonChangedLayersCount; i++) {
697                paintLayer(visibleLayers.get(i), g2, box);
698            }
699        } else {
700            // Maybe there were more unchanged layers then last time - draw them to buffer
701            if (nonChangedLayers.size() != nonChangedLayersCount) {
702                Graphics2D g2 = nonChangedLayersBuffer.createGraphics();
703                g2.setClip(g.getClip());
704                for (int i = nonChangedLayers.size(); i < nonChangedLayersCount; i++) {
705                    paintLayer(visibleLayers.get(i), g2, box);
706                }
707            }
708        }
709
710        nonChangedLayers.clear();
711        changedLayer = null;
712        for (int i = 0; i < nonChangedLayersCount; i++) {
713            nonChangedLayers.add(visibleLayers.get(i));
714        }
715        lastViewID = getViewID();
716        lastClipBounds = g.getClipBounds();
717
718        tempG.drawImage(nonChangedLayersBuffer, 0, 0, null);
719
720        for (int i = nonChangedLayersCount; i < visibleLayers.size(); i++) {
721            paintLayer(visibleLayers.get(i), tempG, box);
722        }
723
724        synchronized (temporaryLayers) {
725            for (MapViewPaintable mvp : temporaryLayers) {
726                mvp.paint(tempG, this, box);
727            }
728        }
729
730        // draw world borders
731        tempG.setColor(Color.WHITE);
732        Bounds b = getProjection().getWorldBoundsLatLon();
733        double lat = b.getMinLat();
734        double lon = b.getMinLon();
735
736        Point p = getPoint(b.getMin());
737
738        GeneralPath path = new GeneralPath();
739
740        path.moveTo(p.x, p.y);
741        double max = b.getMax().lat();
742        for (; lat <= max; lat += 1.0) {
743            p = getPoint(new LatLon(lat >= max ? max : lat, lon));
744            path.lineTo(p.x, p.y);
745        }
746        lat = max; max = b.getMax().lon();
747        for (; lon <= max; lon += 1.0) {
748            p = getPoint(new LatLon(lat, lon >= max ? max : lon));
749            path.lineTo(p.x, p.y);
750        }
751        lon = max; max = b.getMinLat();
752        for (; lat >= max; lat -= 1.0) {
753            p = getPoint(new LatLon(lat <= max ? max : lat, lon));
754            path.lineTo(p.x, p.y);
755        }
756        lat = max; max = b.getMinLon();
757        for (; lon >= max; lon -= 1.0) {
758            p = getPoint(new LatLon(lat, lon <= max ? max : lon));
759            path.lineTo(p.x, p.y);
760        }
761
762        int w = getWidth();
763        int h = getHeight();
764
765        // Work around OpenJDK having problems when drawing out of bounds
766        final Area border = new Area(path);
767        // Make the viewport 1px larger in every direction to prevent an
768        // additional 1px border when zooming in
769        final Area viewport = new Area(new Rectangle(-1, -1, w + 2, h + 2));
770        border.intersect(viewport);
771        tempG.draw(border);
772
773        if (Main.isDisplayingMapView() && Main.map.filterDialog != null) {
774            Main.map.filterDialog.drawOSDText(tempG);
775        }
776
777        if (playHeadMarker != null) {
778            playHeadMarker.paint(tempG, this);
779        }
780
781        g.drawImage(offscreenBuffer, 0, 0, null);
782        super.paint(g);
783    }
784
785    /**
786     * Sets up the viewport to prepare for drawing the view.
787     * @return <code>true</code> if the view can be drawn, <code>false</code> otherwise.
788     */
789    public boolean prepareToDraw() {
790        if (initialViewport != null) {
791            zoomTo(initialViewport);
792            initialViewport = null;
793        }
794        if (BugReportExceptionHandler.exceptionHandlingInProgress())
795            return false;
796
797        if (getCenter() == null)
798            return false; // no data loaded yet.
799
800        // if the position was remembered, we need to adjust center once before repainting
801        if (oldLoc != null && oldSize != null) {
802            Point l1  = getLocationOnScreen();
803            final EastNorth newCenter = new EastNorth(
804                    getCenter().getX()+ (l1.x-oldLoc.x - (oldSize.width-getWidth())/2.0)*getScale(),
805                    getCenter().getY()+ (oldLoc.y-l1.y + (oldSize.height-getHeight())/2.0)*getScale()
806                    );
807            oldLoc = null; oldSize = null;
808            zoomTo(newCenter);
809        }
810
811        return true;
812    }
813
814    /**
815     * @return An unmodifiable collection of all layers
816     */
817    public Collection<Layer> getAllLayers() {
818        synchronized (layers) {
819            return Collections.unmodifiableCollection(new ArrayList<>(layers));
820        }
821    }
822
823    /**
824     * @return An unmodifiable ordered list of all layers
825     */
826    public List<Layer> getAllLayersAsList() {
827        synchronized (layers) {
828            return Collections.unmodifiableList(new ArrayList<>(layers));
829        }
830    }
831
832    /**
833     * Replies an unmodifiable list of layers of a certain type.
834     *
835     * Example:
836     * <pre>
837     *     List&lt;WMSLayer&gt; wmsLayers = getLayersOfType(WMSLayer.class);
838     * </pre>
839     *
840     * @param ofType The layer type.
841     * @return an unmodifiable list of layers of a certain type.
842     */
843    public <T extends Layer> List<T> getLayersOfType(Class<T> ofType) {
844        return new ArrayList<>(Utils.filteredCollection(getAllLayers(), ofType));
845    }
846
847    /**
848     * Replies the number of layers managed by this map view
849     *
850     * @return the number of layers managed by this map view
851     */
852    public int getNumLayers() {
853        synchronized (layers) {
854            return layers.size();
855        }
856    }
857
858    /**
859     * Replies true if there is at least one layer in this map view
860     *
861     * @return true if there is at least one layer in this map view
862     */
863    public boolean hasLayers() {
864        return getNumLayers() > 0;
865    }
866
867    /**
868     * Sets the active edit layer.
869     * <p>
870     * @param layersList A list to select that layer from.
871     * @return A list of change listeners that should be fired using {@link #onActiveEditLayerChanged(Layer, OsmDataLayer, EnumSet)}
872     */
873    private EnumSet<LayerListenerType> setEditLayer(List<Layer> layersList) {
874        final OsmDataLayer newEditLayer = findNewEditLayer(layersList);
875
876        // Set new edit layer
877        if (newEditLayer != editLayer) {
878            if (newEditLayer == null) {
879                // Note: Unsafe to call while layer write lock is held.
880                getCurrentDataSet().setSelected();
881            }
882
883            editLayer = newEditLayer;
884            return EnumSet.of(LayerListenerType.EDIT_LAYER_CHANGE);
885        } else {
886            return EnumSet.noneOf(LayerListenerType.class);
887        }
888
889    }
890
891    private OsmDataLayer findNewEditLayer(List<Layer> layersList) {
892        OsmDataLayer newEditLayer = layersList.contains(editLayer) ? editLayer : null;
893        // Find new edit layer
894        if (activeLayer != editLayer || !layersList.contains(editLayer)) {
895            if (activeLayer instanceof OsmDataLayer && layersList.contains(activeLayer)) {
896                newEditLayer = (OsmDataLayer) activeLayer;
897            } else {
898                for (Layer layer:layersList) {
899                    if (layer instanceof OsmDataLayer) {
900                        newEditLayer = (OsmDataLayer) layer;
901                        break;
902                    }
903                }
904            }
905        }
906        return newEditLayer;
907    }
908
909    /**
910     * Sets the active layer to <code>layer</code>. If <code>layer</code> is an instance
911     * of {@link OsmDataLayer} also sets {@link #editLayer} to <code>layer</code>.
912     *
913     * @param layer the layer to be activate; must be one of the layers in the list of layers
914     * @throws IllegalArgumentException if layer is not in the lis of layers
915     */
916    public void setActiveLayer(Layer layer) {
917        EnumSet<LayerListenerType> listenersToFire;
918        Layer oldActiveLayer;
919        OsmDataLayer oldEditLayer;
920
921        synchronized (layers) {
922            oldActiveLayer = activeLayer;
923            oldEditLayer = editLayer;
924            listenersToFire = setActiveLayer(layer, true);
925        }
926        onActiveEditLayerChanged(oldActiveLayer, oldEditLayer, listenersToFire);
927
928        repaint();
929    }
930
931    /**
932     * Sets the active layer. Propagates this change to all map buttons.
933     * @param layer The layer to be active.
934     * @param setEditLayer if this is <code>true</code>, the edit layer is also set.
935     * @return A list of change listeners that should be fired using {@link #onActiveEditLayerChanged(Layer, OsmDataLayer, EnumSet)}
936     */
937    private EnumSet<LayerListenerType> setActiveLayer(final Layer layer, boolean setEditLayer) {
938        if (layer != null && !layers.contains(layer))
939            throw new IllegalArgumentException(tr("Layer ''{0}'' must be in list of layers", layer.toString()));
940
941        if (layer == activeLayer)
942            return EnumSet.noneOf(LayerListenerType.class);
943
944        activeLayer = layer;
945        EnumSet<LayerListenerType> listenersToFire = EnumSet.of(LayerListenerType.ACTIVE_LAYER_CHANGE);
946        if (setEditLayer) {
947            listenersToFire.addAll(setEditLayer(layers));
948        }
949
950        return listenersToFire;
951    }
952
953    /**
954     * Replies the currently active layer
955     *
956     * @return the currently active layer (may be null)
957     */
958    public Layer getActiveLayer() {
959        synchronized (layers) {
960            return activeLayer;
961        }
962    }
963
964    private enum LayerListenerType {
965        ACTIVE_LAYER_CHANGE,
966        EDIT_LAYER_CHANGE
967    }
968
969    /**
970     * This is called whenever one of active layer/edit layer or both may have been changed,
971     * @param oldActive The old active layer
972     * @param oldEdit The old edit layer.
973     * @param listenersToFire A mask of listeners to fire using {@link LayerListenerType}s
974     */
975    private void onActiveEditLayerChanged(final Layer oldActive, final OsmDataLayer oldEdit, EnumSet<LayerListenerType> listenersToFire) {
976        if (listenersToFire.contains(LayerListenerType.EDIT_LAYER_CHANGE)) {
977            onEditLayerChanged(oldEdit);
978        }
979        if (listenersToFire.contains(LayerListenerType.ACTIVE_LAYER_CHANGE)) {
980            onActiveLayerChanged(oldActive);
981        }
982    }
983
984    private void onActiveLayerChanged(final Layer old) {
985        fireActiveLayerChanged(old, activeLayer);
986
987        /* This only makes the buttons look disabled. Disabling the actions as well requires
988         * the user to re-select the tool after i.e. moving a layer. While testing I found
989         * that I switch layers and actions at the same time and it was annoying to mind the
990         * order. This way it works as visual clue for new users */
991        for (final AbstractButton b: Main.map.allMapModeButtons) {
992            MapMode mode = (MapMode) b.getAction();
993            final boolean activeLayerSupported = mode.layerIsSupported(activeLayer);
994            if (activeLayerSupported) {
995                Main.registerActionShortcut(mode, mode.getShortcut()); //fix #6876
996            } else {
997                Main.unregisterShortcut(mode.getShortcut());
998            }
999            GuiHelper.runInEDTAndWait(new Runnable() {
1000                @Override public void run() {
1001                    b.setEnabled(activeLayerSupported);
1002                }
1003            });
1004        }
1005        AudioPlayer.reset();
1006        repaint();
1007    }
1008
1009    /**
1010     * Replies the current edit layer, if any
1011     *
1012     * @return the current edit layer. May be null.
1013     */
1014    public OsmDataLayer getEditLayer() {
1015        synchronized (layers) {
1016            return editLayer;
1017        }
1018    }
1019
1020    /**
1021     * replies true if the list of layers managed by this map view contain layer
1022     *
1023     * @param layer the layer
1024     * @return true if the list of layers managed by this map view contain layer
1025     */
1026    public boolean hasLayer(Layer layer) {
1027        synchronized (layers) {
1028            return layers.contains(layer);
1029        }
1030    }
1031
1032    /**
1033     * Adds a new temporary layer.
1034     * <p>
1035     * A temporary layer is a layer that is painted above all normal layers. Layers are painted in the order they are added.
1036     *
1037     * @param mvp The layer to paint.
1038     * @return <code>true</code> if the layer was added.
1039     */
1040    public boolean addTemporaryLayer(MapViewPaintable mvp) {
1041        synchronized (temporaryLayers) {
1042            return temporaryLayers.add(mvp);
1043        }
1044    }
1045
1046    /**
1047     * Removes a layer previously added as temporary layer.
1048     * @param mvp The layer to remove.
1049     * @return <code>true</code> if that layer was removed.
1050     */
1051    public boolean removeTemporaryLayer(MapViewPaintable mvp) {
1052        synchronized (temporaryLayers) {
1053            return temporaryLayers.remove(mvp);
1054        }
1055    }
1056
1057    /**
1058     * Gets a list of temporary layers.
1059     * @return The layers in the order they are added.
1060     */
1061    public List<MapViewPaintable> getTemporaryLayers() {
1062        synchronized (temporaryLayers) {
1063            return Collections.unmodifiableList(new ArrayList<>(temporaryLayers));
1064        }
1065    }
1066
1067    @Override
1068    public void propertyChange(PropertyChangeEvent evt) {
1069        if (evt.getPropertyName().equals(Layer.VISIBLE_PROP)) {
1070            repaint();
1071        } else if (evt.getPropertyName().equals(Layer.OPACITY_PROP) ||
1072                evt.getPropertyName().equals(Layer.FILTER_STATE_PROP)) {
1073            Layer l = (Layer) evt.getSource();
1074            if (l.isVisible()) {
1075                changedLayer = l;
1076                repaint();
1077            }
1078        } else if (evt.getPropertyName().equals(OsmDataLayer.REQUIRES_SAVE_TO_DISK_PROP)
1079                || evt.getPropertyName().equals(OsmDataLayer.REQUIRES_UPLOAD_TO_SERVER_PROP)) {
1080            OsmDataLayer layer = (OsmDataLayer) evt.getSource();
1081            if (layer == getEditLayer()) {
1082                refreshTitle();
1083            }
1084        }
1085    }
1086
1087    /**
1088     * Sets the title of the JOSM main window, adding a star if there are dirty layers.
1089     * @see Main#parent
1090     */
1091    protected void refreshTitle() {
1092        if (Main.parent != null) {
1093            synchronized (layers) {
1094                boolean dirty = editLayer != null &&
1095                        (editLayer.requiresSaveToFile() || (editLayer.requiresUploadToServer() && !editLayer.isUploadDiscouraged()));
1096                ((JFrame) Main.parent).setTitle((dirty ? "* " : "") + tr("Java OpenStreetMap Editor"));
1097                ((JFrame) Main.parent).getRootPane().putClientProperty("Window.documentModified", dirty);
1098            }
1099        }
1100    }
1101
1102    @Override
1103    public void preferenceChanged(PreferenceChangeEvent e) {
1104        synchronized (this) {
1105            paintPreferencesChanged = true;
1106        }
1107    }
1108
1109    private transient SelectionChangedListener repaintSelectionChangedListener = new SelectionChangedListener() {
1110        @Override
1111        public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
1112            repaint();
1113        }
1114    };
1115
1116    public void destroy() {
1117        Main.pref.removePreferenceChangeListener(this);
1118        DataSet.removeSelectionListener(repaintSelectionChangedListener);
1119        MultipolygonCache.getInstance().clear(this);
1120        if (mapMover != null) {
1121            mapMover.destroy();
1122        }
1123        synchronized (layers) {
1124            activeLayer = null;
1125            changedLayer = null;
1126            editLayer = null;
1127            layers.clear();
1128            nonChangedLayers.clear();
1129        }
1130        synchronized (temporaryLayers) {
1131            temporaryLayers.clear();
1132        }
1133    }
1134
1135    @Override
1136    public void uploadDiscouragedChanged(OsmDataLayer layer, boolean newValue) {
1137        if (layer == getEditLayer()) {
1138            refreshTitle();
1139        }
1140    }
1141
1142    /**
1143     * Get a string representation of all layers suitable for the {@code source} changeset tag.
1144     * @return A String of sources separated by ';'
1145     */
1146    public String getLayerInformationForSourceTag() {
1147        final Collection<String> layerInfo = new ArrayList<>();
1148        if (!getLayersOfType(GpxLayer.class).isEmpty()) {
1149            // no i18n for international values
1150            layerInfo.add("survey");
1151        }
1152        for (final GeoImageLayer i : getLayersOfType(GeoImageLayer.class)) {
1153            layerInfo.add(i.getName());
1154        }
1155        for (final ImageryLayer i : getLayersOfType(ImageryLayer.class)) {
1156            layerInfo.add(ImageryInfo.ImageryType.BING.equals(i.getInfo().getImageryType()) ? "Bing" : i.getName());
1157        }
1158        return Utils.join("; ", layerInfo);
1159    }
1160
1161    /**
1162     * This is a listener that gets informed whenever repaint is called for this MapView.
1163     * <p>
1164     * This is the only safe method to find changes to the map view, since many components call MapView.repaint() directly.
1165     * @author Michael Zangl
1166     */
1167    public interface RepaintListener {
1168        /**
1169         * Called when any repaint method is called (using default arguments if required).
1170         * @param tm see {@link JComponent#repaint(long, int, int, int, int)}
1171         * @param x see {@link JComponent#repaint(long, int, int, int, int)}
1172         * @param y see {@link JComponent#repaint(long, int, int, int, int)}
1173         * @param width see {@link JComponent#repaint(long, int, int, int, int)}
1174         * @param height see {@link JComponent#repaint(long, int, int, int, int)}
1175         */
1176        void repaint(long tm, int x, int y, int width, int height);
1177    }
1178
1179    private final CopyOnWriteArrayList<RepaintListener> repaintListeners = new CopyOnWriteArrayList<>();
1180
1181    /**
1182     * Adds a listener that gets informed whenever repaint() is called for this class.
1183     * @param l The listener.
1184     */
1185    public void addRepaintListener(RepaintListener l) {
1186        repaintListeners.add(l);
1187    }
1188
1189    /**
1190     * Removes a registered repaint listener.
1191     * @param l The listener.
1192     */
1193    public void removeRepaintListener(RepaintListener l) {
1194        repaintListeners.remove(l);
1195    }
1196
1197    @Override
1198    public void repaint(long tm, int x, int y, int width, int height) {
1199        // This is the main repaint method, all other methods are convenience methods and simply call this method.
1200        // This is just an observation, not a must, but seems to be true for all implementations I found so far.
1201        if (repaintListeners != null) {
1202            // Might get called early in super constructor
1203            for (RepaintListener l : repaintListeners) {
1204                l.repaint(tm, x, y, width, height);
1205            }
1206        }
1207        super.repaint(tm, x, y, width, height);
1208    }
1209}