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<WMSLayer> 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}