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