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