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