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