001// License: GPL. See LICENSE file for details. 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.Collection; 026import java.util.Collections; 027import java.util.Comparator; 028import java.util.LinkedList; 029import java.util.List; 030import java.util.concurrent.CopyOnWriteArrayList; 031 032import javax.swing.AbstractButton; 033import javax.swing.ActionMap; 034import javax.swing.InputMap; 035import javax.swing.JFrame; 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.SelectionChangedListener; 044import org.openstreetmap.josm.data.ViewportData; 045import org.openstreetmap.josm.data.coor.EastNorth; 046import org.openstreetmap.josm.data.coor.LatLon; 047import org.openstreetmap.josm.data.imagery.ImageryInfo; 048import org.openstreetmap.josm.data.osm.DataSet; 049import org.openstreetmap.josm.data.osm.OsmPrimitive; 050import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor; 051import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors; 052import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache; 053import org.openstreetmap.josm.gui.layer.GpxLayer; 054import org.openstreetmap.josm.gui.layer.ImageryLayer; 055import org.openstreetmap.josm.gui.layer.Layer; 056import org.openstreetmap.josm.gui.layer.MapViewPaintable; 057import org.openstreetmap.josm.gui.layer.OsmDataLayer; 058import org.openstreetmap.josm.gui.layer.geoimage.GeoImageLayer; 059import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer; 060import org.openstreetmap.josm.gui.layer.markerlayer.PlayHeadMarker; 061import org.openstreetmap.josm.gui.util.GuiHelper; 062import org.openstreetmap.josm.tools.AudioPlayer; 063import org.openstreetmap.josm.tools.BugReportExceptionHandler; 064import org.openstreetmap.josm.tools.Shortcut; 065import org.openstreetmap.josm.tools.Utils; 066 067/** 068 * This is a component used in the {@link MapFrame} for browsing the map. It use is to 069 * provide the MapMode's enough capabilities to operate.<br><br> 070 * 071 * {@code MapView} holds meta-data about the data set currently displayed, as scale level, 072 * center point viewed, what scrolling mode or editing mode is selected or with 073 * what projection the map is viewed etc..<br><br> 074 * 075 * {@code MapView} is able to administrate several layers. 076 * 077 * @author imi 078 */ 079public class MapView extends NavigatableComponent implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.LayerStateChangeListener { 080 081 /** 082 * Interface to notify listeners of a layer change. 083 * @author imi 084 */ 085 public interface LayerChangeListener { 086 087 /** 088 * Notifies this listener that the active layer has changed. 089 * @param oldLayer The previous active layer 090 * @param newLayer The new activer layer 091 */ 092 void activeLayerChange(Layer oldLayer, Layer newLayer); 093 094 /** 095 * Notifies this listener that a layer has been added. 096 * @param newLayer The new added layer 097 */ 098 void layerAdded(Layer newLayer); 099 100 /** 101 * Notifies this listener that a layer has been removed. 102 * @param oldLayer The old removed layer 103 */ 104 void layerRemoved(Layer oldLayer); 105 } 106 107 public interface EditLayerChangeListener { 108 void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer); 109 } 110 111 public boolean viewportFollowing = false; 112 113 /** 114 * the layer listeners 115 */ 116 private static final CopyOnWriteArrayList<LayerChangeListener> layerChangeListeners = new CopyOnWriteArrayList<>(); 117 private static final CopyOnWriteArrayList<EditLayerChangeListener> editLayerChangeListeners = new CopyOnWriteArrayList<>(); 118 119 /** 120 * Removes a layer change listener 121 * 122 * @param listener the listener. Ignored if null or already registered. 123 */ 124 public static void removeLayerChangeListener(LayerChangeListener listener) { 125 layerChangeListeners.remove(listener); 126 } 127 128 public static void removeEditLayerChangeListener(EditLayerChangeListener listener) { 129 editLayerChangeListeners.remove(listener); 130 } 131 132 /** 133 * Adds a layer change listener 134 * 135 * @param listener the listener. Ignored if null or already registered. 136 */ 137 public static void addLayerChangeListener(LayerChangeListener listener) { 138 if (listener != null) { 139 layerChangeListeners.addIfAbsent(listener); 140 } 141 } 142 143 /** 144 * Adds an edit layer change listener 145 * 146 * @param listener the listener. Ignored if null or already registered. 147 * @param initialFire Fire an edit-layer-changed-event right after adding 148 * the listener in case there is an edit layer present 149 */ 150 public static void addEditLayerChangeListener(EditLayerChangeListener listener, boolean initialFire) { 151 addEditLayerChangeListener(listener); 152 if (initialFire && Main.isDisplayingMapView() && Main.map.mapView.getEditLayer() != null) { 153 fireEditLayerChanged(null, Main.map.mapView.getEditLayer()); 154 } 155 } 156 157 /** 158 * Adds an edit layer change listener 159 * 160 * @param listener the listener. Ignored if null or already registered. 161 */ 162 public static void addEditLayerChangeListener(EditLayerChangeListener listener) { 163 if (listener != null) { 164 editLayerChangeListeners.addIfAbsent(listener); 165 } 166 } 167 168 protected static void fireActiveLayerChanged(Layer oldLayer, Layer newLayer) { 169 for (LayerChangeListener l : layerChangeListeners) { 170 l.activeLayerChange(oldLayer, newLayer); 171 } 172 } 173 174 protected static void fireLayerAdded(Layer newLayer) { 175 for (MapView.LayerChangeListener l : MapView.layerChangeListeners) { 176 l.layerAdded(newLayer); 177 } 178 } 179 180 protected static void fireLayerRemoved(Layer layer) { 181 for (MapView.LayerChangeListener l : MapView.layerChangeListeners) { 182 l.layerRemoved(layer); 183 } 184 } 185 186 protected static void fireEditLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) { 187 for (EditLayerChangeListener l : editLayerChangeListeners) { 188 l.editLayerChanged(oldLayer, newLayer); 189 } 190 } 191 192 /** 193 * A list of all layers currently loaded. 194 */ 195 private final List<Layer> layers = new ArrayList<>(); 196 /** 197 * The play head marker: there is only one of these so it isn't in any specific layer 198 */ 199 public PlayHeadMarker playHeadMarker = null; 200 201 /** 202 * The layer from the layers list that is currently active. 203 */ 204 private Layer activeLayer; 205 206 private OsmDataLayer editLayer; 207 208 /** 209 * The last event performed by mouse. 210 */ 211 public MouseEvent lastMEvent = new MouseEvent(this, 0, 0, 0, 0, 0, 0, false); // In case somebody reads it before first mouse move 212 213 private final List<MapViewPaintable> temporaryLayers = new LinkedList<>(); 214 215 private BufferedImage nonChangedLayersBuffer; 216 private BufferedImage offscreenBuffer; 217 // Layers that wasn't changed since last paint 218 private final List<Layer> nonChangedLayers = new ArrayList<>(); 219 private Layer changedLayer; 220 private int lastViewID; 221 private boolean paintPreferencesChanged = true; 222 private Rectangle lastClipBounds = new Rectangle(); 223 private MapMover mapMover; 224 225 /** 226 * Constructs a new {@code MapView}. 227 * @param contentPane The content pane used to register shortcuts in its 228 * {@link InputMap} and {@link ActionMap} 229 * @param viewportData the initial viewport of the map. Can be null, then 230 * the viewport is derived from the layer data. 231 */ 232 public MapView(final JPanel contentPane, final ViewportData viewportData) { 233 initialViewport = viewportData; 234 Main.pref.addPreferenceChangeListener(this); 235 final boolean unregisterTab = Shortcut.findShortcut(KeyEvent.VK_TAB, 0)!=null; 236 237 addComponentListener(new ComponentAdapter(){ 238 @Override public void componentResized(ComponentEvent e) { 239 removeComponentListener(this); 240 241 MapSlider zoomSlider = new MapSlider(MapView.this); 242 add(zoomSlider); 243 zoomSlider.setBounds(3, 0, 114, 30); 244 zoomSlider.setFocusTraversalKeysEnabled(!unregisterTab); 245 246 MapScaler scaler = new MapScaler(MapView.this); 247 add(scaler); 248 scaler.setLocation(10,30); 249 250 mapMover = new MapMover(MapView.this, contentPane); 251 } 252 }); 253 254 // listend to selection changes to redraw the map 255 DataSet.addSelectionListener(repaintSelectionChangedListener); 256 257 //store the last mouse action 258 this.addMouseMotionListener(new MouseMotionListener() { 259 @Override public void mouseDragged(MouseEvent e) { 260 mouseMoved(e); 261 } 262 @Override public void mouseMoved(MouseEvent e) { 263 lastMEvent = e; 264 } 265 }); 266 this.addMouseListener(new MouseAdapter() { 267 @Override 268 public void mousePressed(MouseEvent me) { 269 // focus the MapView component when mouse is pressed inside it 270 requestFocus(); 271 } 272 }); 273 274 if (Shortcut.findShortcut(KeyEvent.VK_TAB, 0)!=null) { 275 setFocusTraversalKeysEnabled(false); 276 } 277 } 278 279 // remebered geometry of the component 280 private Dimension oldSize = null; 281 private Point oldLoc = null; 282 283 /* 284 * Call this method to keep map position on screen during next repaint 285 */ 286 public void rememberLastPositionOnScreen() { 287 oldSize = getSize(); 288 oldLoc = getLocationOnScreen(); 289 } 290 291 /** 292 * Adds a GPX layer. A GPX layer is added below the lowest data layer. 293 * 294 * @param layer the GPX layer 295 */ 296 protected void addGpxLayer(GpxLayer layer) { 297 if (layers.isEmpty()) { 298 layers.add(layer); 299 return; 300 } 301 for (int i=layers.size()-1; i>= 0; i--) { 302 if (layers.get(i) instanceof OsmDataLayer) { 303 if (i == layers.size()-1) { 304 layers.add(layer); 305 } else { 306 layers.add(i+1, layer); 307 } 308 return; 309 } 310 } 311 layers.add(0, layer); 312 } 313 314 /** 315 * Add a layer to the current MapView. The layer will be added at topmost 316 * position. 317 * @param layer The layer to add 318 */ 319 public void addLayer(Layer layer) { 320 if (layer instanceof MarkerLayer && playHeadMarker == null) { 321 playHeadMarker = PlayHeadMarker.create(); 322 } 323 324 if (layer instanceof GpxLayer) { 325 addGpxLayer((GpxLayer)layer); 326 } else if (layers.isEmpty()) { 327 layers.add(layer); 328 } else if (layer.isBackgroundLayer()) { 329 int i = 0; 330 for (; i < layers.size(); i++) { 331 if (layers.get(i).isBackgroundLayer()) { 332 break; 333 } 334 } 335 layers.add(i, layer); 336 } else { 337 layers.add(0, layer); 338 } 339 fireLayerAdded(layer); 340 boolean isOsmDataLayer = layer instanceof OsmDataLayer; 341 if (isOsmDataLayer) { 342 ((OsmDataLayer)layer).addLayerStateChangeListener(this); 343 } 344 boolean callSetActiveLayer = isOsmDataLayer || activeLayer == null; 345 if (callSetActiveLayer) { 346 // autoselect the new layer 347 setActiveLayer(layer); // also repaints this MapView 348 } 349 layer.addPropertyChangeListener(this); 350 Main.addProjectionChangeListener(layer); 351 AudioPlayer.reset(); 352 if (!callSetActiveLayer) { 353 repaint(); 354 } 355 } 356 357 @Override 358 protected DataSet getCurrentDataSet() { 359 if (editLayer != null) 360 return editLayer.data; 361 else 362 return null; 363 } 364 365 /** 366 * Replies true if the active layer is drawable. 367 * 368 * @return true if the active layer is drawable, false otherwise 369 */ 370 public boolean isActiveLayerDrawable() { 371 return editLayer != null; 372 } 373 374 /** 375 * Replies true if the active layer is visible. 376 * 377 * @return true if the active layer is visible, false otherwise 378 */ 379 public boolean isActiveLayerVisible() { 380 return isActiveLayerDrawable() && editLayer.isVisible(); 381 } 382 383 /** 384 * Determines the next active data layer according to the following 385 * rules: 386 * <ul> 387 * <li>if there is at least one {@link OsmDataLayer} the first one 388 * becomes active</li> 389 * <li>otherwise, the top most layer of any type becomes active</li> 390 * </ul> 391 * 392 * @return the next active data layer 393 */ 394 protected Layer determineNextActiveLayer(List<Layer> layersList) { 395 // First look for data layer 396 for (Layer layer:layersList) { 397 if (layer instanceof OsmDataLayer) 398 return layer; 399 } 400 401 // Then any layer 402 if (!layersList.isEmpty()) 403 return layersList.get(0); 404 405 // and then give up 406 return null; 407 408 } 409 410 /** 411 * Remove the layer from the mapview. If the layer was in the list before, 412 * an LayerChange event is fired. 413 * @param layer The layer to remove 414 */ 415 public void removeLayer(Layer layer) { 416 List<Layer> layersList = new ArrayList<>(layers); 417 418 if (!layersList.remove(layer)) 419 return; 420 421 setEditLayer(layersList); 422 423 if (layer == activeLayer) { 424 setActiveLayer(determineNextActiveLayer(layersList), false); 425 } 426 427 if (layer instanceof OsmDataLayer) { 428 ((OsmDataLayer)layer).removeLayerPropertyChangeListener(this); 429 } 430 431 layers.remove(layer); 432 Main.removeProjectionChangeListener(layer); 433 fireLayerRemoved(layer); 434 layer.removePropertyChangeListener(this); 435 layer.destroy(); 436 AudioPlayer.reset(); 437 repaint(); 438 } 439 440 private boolean virtualNodesEnabled = false; 441 442 public void setVirtualNodesEnabled(boolean enabled) { 443 if(virtualNodesEnabled != enabled) { 444 virtualNodesEnabled = enabled; 445 repaint(); 446 } 447 } 448 public boolean isVirtualNodesEnabled() { 449 return virtualNodesEnabled; 450 } 451 452 /** 453 * Moves the layer to the given new position. No event is fired, but repaints 454 * according to the new Z-Order of the layers. 455 * 456 * @param layer The layer to move 457 * @param pos The new position of the layer 458 */ 459 public void moveLayer(Layer layer, int pos) { 460 int curLayerPos = layers.indexOf(layer); 461 if (curLayerPos == -1) 462 throw new IllegalArgumentException(tr("Layer not in list.")); 463 if (pos == curLayerPos) 464 return; // already in place. 465 layers.remove(curLayerPos); 466 if (pos >= layers.size()) { 467 layers.add(layer); 468 } else { 469 layers.add(pos, layer); 470 } 471 setEditLayer(layers); 472 AudioPlayer.reset(); 473 repaint(); 474 } 475 476 public int getLayerPos(Layer layer) { 477 int curLayerPos = layers.indexOf(layer); 478 if (curLayerPos == -1) 479 throw new IllegalArgumentException(tr("Layer not in list.")); 480 return curLayerPos; 481 } 482 483 /** 484 * Creates a list of the visible layers in Z-Order, the layer with the lowest Z-Order 485 * first, layer with the highest Z-Order last. 486 * 487 * @return a list of the visible in Z-Order, the layer with the lowest Z-Order 488 * first, layer with the highest Z-Order last. 489 */ 490 protected List<Layer> getVisibleLayersInZOrder() { 491 List<Layer> ret = new ArrayList<>(); 492 for (Layer l: layers) { 493 if (l.isVisible()) { 494 ret.add(l); 495 } 496 } 497 // sort according to position in the list of layers, with one exception: 498 // an active data layer always becomes a higher Z-Order than all other 499 // data layers 500 // 501 Collections.sort( 502 ret, 503 new Comparator<Layer>() { 504 @Override public int compare(Layer l1, Layer l2) { 505 if (l1 instanceof OsmDataLayer && l2 instanceof OsmDataLayer) { 506 if (l1 == getActiveLayer()) return -1; 507 if (l2 == getActiveLayer()) return 1; 508 return Integer.valueOf(layers.indexOf(l1)).compareTo(layers.indexOf(l2)); 509 } else 510 return Integer.valueOf(layers.indexOf(l1)).compareTo(layers.indexOf(l2)); 511 } 512 } 513 ); 514 Collections.reverse(ret); 515 return ret; 516 } 517 518 private void paintLayer(Layer layer, Graphics2D g, Bounds box) { 519 if (layer.getOpacity() < 1) { 520 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,(float)layer.getOpacity())); 521 } 522 layer.paint(g, this, box); 523 g.setPaintMode(); 524 } 525 526 /** 527 * Draw the component. 528 */ 529 @Override public void paint(Graphics g) { 530 if (initialViewport != null) { 531 zoomTo(initialViewport); 532 initialViewport = null; 533 } 534 if (BugReportExceptionHandler.exceptionHandlingInProgress()) 535 return; 536 537 if (center == null) 538 return; // no data loaded yet. 539 540 // if the position was remembered, we need to adjust center once before repainting 541 if (oldLoc != null && oldSize != null) { 542 Point l1 = getLocationOnScreen(); 543 final EastNorth newCenter = new EastNorth( 544 center.getX()+ (l1.x-oldLoc.x - (oldSize.width-getWidth())/2.0)*getScale(), 545 center.getY()+ (oldLoc.y-l1.y + (oldSize.height-getHeight())/2.0)*getScale() 546 ); 547 oldLoc = null; oldSize = null; 548 zoomTo(newCenter); 549 } 550 551 List<Layer> visibleLayers = getVisibleLayersInZOrder(); 552 553 int nonChangedLayersCount = 0; 554 for (Layer l: visibleLayers) { 555 if (l.isChanged() || l == changedLayer) { 556 break; 557 } else { 558 nonChangedLayersCount++; 559 } 560 } 561 562 boolean canUseBuffer; 563 564 synchronized (this) { 565 canUseBuffer = !paintPreferencesChanged; 566 paintPreferencesChanged = false; 567 } 568 canUseBuffer = canUseBuffer && nonChangedLayers.size() <= nonChangedLayersCount && 569 lastViewID == getViewID() && lastClipBounds.contains(g.getClipBounds()); 570 if (canUseBuffer) { 571 for (int i=0; i<nonChangedLayers.size(); i++) { 572 if (visibleLayers.get(i) != nonChangedLayers.get(i)) { 573 canUseBuffer = false; 574 break; 575 } 576 } 577 } 578 579 if (null == offscreenBuffer || offscreenBuffer.getWidth() != getWidth() || offscreenBuffer.getHeight() != getHeight()) { 580 offscreenBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_3BYTE_BGR); 581 } 582 583 Graphics2D tempG = offscreenBuffer.createGraphics(); 584 tempG.setClip(g.getClip()); 585 Bounds box = getLatLonBounds(g.getClipBounds()); 586 587 if (!canUseBuffer || nonChangedLayersBuffer == null) { 588 if (null == nonChangedLayersBuffer || nonChangedLayersBuffer.getWidth() != getWidth() || nonChangedLayersBuffer.getHeight() != getHeight()) { 589 nonChangedLayersBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_3BYTE_BGR); 590 } 591 Graphics2D g2 = nonChangedLayersBuffer.createGraphics(); 592 g2.setClip(g.getClip()); 593 g2.setColor(PaintColors.getBackgroundColor()); 594 g2.fillRect(0, 0, getWidth(), getHeight()); 595 596 for (int i=0; i<nonChangedLayersCount; i++) { 597 paintLayer(visibleLayers.get(i),g2, box); 598 } 599 } else { 600 // Maybe there were more unchanged layers then last time - draw them to buffer 601 if (nonChangedLayers.size() != nonChangedLayersCount) { 602 Graphics2D g2 = nonChangedLayersBuffer.createGraphics(); 603 g2.setClip(g.getClip()); 604 for (int i=nonChangedLayers.size(); i<nonChangedLayersCount; i++) { 605 paintLayer(visibleLayers.get(i),g2, box); 606 } 607 } 608 } 609 610 nonChangedLayers.clear(); 611 changedLayer = null; 612 for (int i=0; i<nonChangedLayersCount; i++) { 613 nonChangedLayers.add(visibleLayers.get(i)); 614 } 615 lastViewID = getViewID(); 616 lastClipBounds = g.getClipBounds(); 617 618 tempG.drawImage(nonChangedLayersBuffer, 0, 0, null); 619 620 for (int i=nonChangedLayersCount; i<visibleLayers.size(); i++) { 621 paintLayer(visibleLayers.get(i),tempG, box); 622 } 623 624 for (MapViewPaintable mvp : temporaryLayers) { 625 mvp.paint(tempG, this, box); 626 } 627 628 // draw world borders 629 tempG.setColor(Color.WHITE); 630 Bounds b = getProjection().getWorldBoundsLatLon(); 631 double lat = b.getMinLat(); 632 double lon = b.getMinLon(); 633 634 Point p = getPoint(b.getMin()); 635 636 GeneralPath path = new GeneralPath(); 637 638 path.moveTo(p.x, p.y); 639 double max = b.getMax().lat(); 640 for(; lat <= max; lat += 1.0) 641 { 642 p = getPoint(new LatLon(lat >= max ? max : lat, lon)); 643 path.lineTo(p.x, p.y); 644 } 645 lat = max; max = b.getMax().lon(); 646 for(; lon <= max; lon += 1.0) 647 { 648 p = getPoint(new LatLon(lat, lon >= max ? max : lon)); 649 path.lineTo(p.x, p.y); 650 } 651 lon = max; max = b.getMinLat(); 652 for(; lat >= max; lat -= 1.0) 653 { 654 p = getPoint(new LatLon(lat <= max ? max : lat, lon)); 655 path.lineTo(p.x, p.y); 656 } 657 lat = max; max = b.getMinLon(); 658 for(; lon >= max; lon -= 1.0) 659 { 660 p = getPoint(new LatLon(lat, lon <= max ? max : lon)); 661 path.lineTo(p.x, p.y); 662 } 663 664 int w = getWidth(); 665 int h = getHeight(); 666 667 // Work around OpenJDK having problems when drawing out of bounds 668 final Area border = new Area(path); 669 // Make the viewport 1px larger in every direction to prevent an 670 // additional 1px border when zooming in 671 final Area viewport = new Area(new Rectangle(-1, -1, w + 2, h + 2)); 672 border.intersect(viewport); 673 tempG.draw(border); 674 675 if (Main.isDisplayingMapView() && Main.map.filterDialog != null) { 676 Main.map.filterDialog.drawOSDText(tempG); 677 } 678 679 if (playHeadMarker != null) { 680 playHeadMarker.paint(tempG, this); 681 } 682 683 g.drawImage(offscreenBuffer, 0, 0, null); 684 super.paint(g); 685 } 686 687 /** 688 * Set the new dimension to the view. 689 * 690 * @deprecated use #zoomTo(BoundingXYVisitor) 691 */ 692 @Deprecated 693 public void recalculateCenterScale(BoundingXYVisitor box) { 694 zoomTo(box); 695 } 696 697 /** 698 * @return An unmodifiable collection of all layers 699 */ 700 public Collection<Layer> getAllLayers() { 701 return Collections.unmodifiableCollection(new ArrayList<>(layers)); 702 } 703 704 /** 705 * @return An unmodifiable ordered list of all layers 706 */ 707 public List<Layer> getAllLayersAsList() { 708 return Collections.unmodifiableList(new ArrayList<>(layers)); 709 } 710 711 /** 712 * Replies an unmodifiable list of layers of a certain type. 713 * 714 * Example: 715 * <pre> 716 * List<WMSLayer> wmsLayers = getLayersOfType(WMSLayer.class); 717 * </pre> 718 * 719 * @return an unmodifiable list of layers of a certain type. 720 */ 721 public <T extends Layer> List<T> getLayersOfType(Class<T> ofType) { 722 return new ArrayList<>(Utils.filteredCollection(getAllLayers(), ofType)); 723 } 724 725 /** 726 * Replies the number of layers managed by this mav view 727 * 728 * @return the number of layers managed by this mav view 729 */ 730 public int getNumLayers() { 731 return layers.size(); 732 } 733 734 /** 735 * Replies true if there is at least one layer in this map view 736 * 737 * @return true if there is at least one layer in this map view 738 */ 739 public boolean hasLayers() { 740 return getNumLayers() > 0; 741 } 742 743 private void setEditLayer(List<Layer> layersList) { 744 OsmDataLayer newEditLayer = layersList.contains(editLayer)?editLayer:null; 745 OsmDataLayer oldEditLayer = editLayer; 746 747 // Find new edit layer 748 if (activeLayer != editLayer || !layersList.contains(editLayer)) { 749 if (activeLayer instanceof OsmDataLayer && layersList.contains(activeLayer)) { 750 newEditLayer = (OsmDataLayer) activeLayer; 751 } else { 752 for (Layer layer:layersList) { 753 if (layer instanceof OsmDataLayer) { 754 newEditLayer = (OsmDataLayer) layer; 755 break; 756 } 757 } 758 } 759 } 760 761 // Set new edit layer 762 if (newEditLayer != editLayer) { 763 if (newEditLayer == null) { 764 getCurrentDataSet().setSelected(); 765 } 766 767 editLayer = newEditLayer; 768 fireEditLayerChanged(oldEditLayer, newEditLayer); 769 refreshTitle(); 770 } 771 772 } 773 774 /** 775 * Sets the active layer to <code>layer</code>. If <code>layer</code> is an instance 776 * of {@link OsmDataLayer} also sets {@link #editLayer} to <code>layer</code>. 777 * 778 * @param layer the layer to be activate; must be one of the layers in the list of layers 779 * @exception IllegalArgumentException thrown if layer is not in the lis of layers 780 */ 781 public void setActiveLayer(Layer layer) { 782 setActiveLayer(layer, true); 783 } 784 785 private void setActiveLayer(Layer layer, boolean setEditLayer) { 786 if (layer != null && !layers.contains(layer)) 787 throw new IllegalArgumentException(tr("Layer ''{0}'' must be in list of layers", layer.toString())); 788 789 if (layer == activeLayer) 790 return; 791 792 Layer old = activeLayer; 793 activeLayer = layer; 794 if (setEditLayer) { 795 setEditLayer(layers); 796 } 797 fireActiveLayerChanged(old, layer); 798 799 /* This only makes the buttons look disabled. Disabling the actions as well requires 800 * the user to re-select the tool after i.e. moving a layer. While testing I found 801 * that I switch layers and actions at the same time and it was annoying to mind the 802 * order. This way it works as visual clue for new users */ 803 for (final AbstractButton b: Main.map.allMapModeButtons) { 804 MapMode mode = (MapMode)b.getAction(); 805 if (mode.layerIsSupported(layer)) { 806 Main.registerActionShortcut(mode, mode.getShortcut()); //fix #6876 807 GuiHelper.runInEDTAndWait(new Runnable() { 808 @Override public void run() { 809 b.setEnabled(true); 810 } 811 }); 812 } else { 813 Main.unregisterShortcut(mode.getShortcut()); 814 GuiHelper.runInEDTAndWait(new Runnable() { 815 @Override public void run() { 816 b.setEnabled(false); 817 } 818 }); 819 } 820 } 821 AudioPlayer.reset(); 822 repaint(); 823 } 824 825 /** 826 * Replies the currently active layer 827 * 828 * @return the currently active layer (may be null) 829 */ 830 public Layer getActiveLayer() { 831 return activeLayer; 832 } 833 834 /** 835 * Replies the current edit layer, if any 836 * 837 * @return the current edit layer. May be null. 838 */ 839 public OsmDataLayer getEditLayer() { 840 return editLayer; 841 } 842 843 /** 844 * replies true if the list of layers managed by this map view contain layer 845 * 846 * @param layer the layer 847 * @return true if the list of layers managed by this map view contain layer 848 */ 849 public boolean hasLayer(Layer layer) { 850 return layers.contains(layer); 851 } 852 853 public boolean addTemporaryLayer(MapViewPaintable mvp) { 854 if (temporaryLayers.contains(mvp)) return false; 855 return temporaryLayers.add(mvp); 856 } 857 858 public boolean removeTemporaryLayer(MapViewPaintable mvp) { 859 return temporaryLayers.remove(mvp); 860 } 861 862 @Override 863 public void propertyChange(PropertyChangeEvent evt) { 864 if (evt.getPropertyName().equals(Layer.VISIBLE_PROP)) { 865 repaint(); 866 } else if (evt.getPropertyName().equals(Layer.OPACITY_PROP)) { 867 Layer l = (Layer)evt.getSource(); 868 if (l.isVisible()) { 869 changedLayer = l; 870 repaint(); 871 } 872 } else if (evt.getPropertyName().equals(OsmDataLayer.REQUIRES_SAVE_TO_DISK_PROP) 873 || evt.getPropertyName().equals(OsmDataLayer.REQUIRES_UPLOAD_TO_SERVER_PROP)) { 874 OsmDataLayer layer = (OsmDataLayer)evt.getSource(); 875 if (layer == getEditLayer()) { 876 refreshTitle(); 877 } 878 } 879 } 880 881 protected void refreshTitle() { 882 if (Main.parent != null) { 883 boolean dirty = editLayer != null && 884 (editLayer.requiresSaveToFile() || (editLayer.requiresUploadToServer() && !editLayer.isUploadDiscouraged())); 885 ((JFrame) Main.parent).setTitle((dirty ? "* " : "") + tr("Java OpenStreetMap Editor")); 886 ((JFrame) Main.parent).getRootPane().putClientProperty("Window.documentModified", dirty); 887 } 888 } 889 890 @Override 891 public void preferenceChanged(PreferenceChangeEvent e) { 892 synchronized (this) { 893 paintPreferencesChanged = true; 894 } 895 } 896 897 private SelectionChangedListener repaintSelectionChangedListener = new SelectionChangedListener(){ 898 @Override public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) { 899 repaint(); 900 } 901 }; 902 903 public void destroy() { 904 Main.pref.removePreferenceChangeListener(this); 905 DataSet.removeSelectionListener(repaintSelectionChangedListener); 906 MultipolygonCache.getInstance().clear(this); 907 if (mapMover != null) { 908 mapMover.destroy(); 909 } 910 activeLayer = null; 911 changedLayer = null; 912 editLayer = null; 913 layers.clear(); 914 nonChangedLayers.clear(); 915 temporaryLayers.clear(); 916 } 917 918 @Override 919 public void uploadDiscouragedChanged(OsmDataLayer layer, boolean newValue) { 920 if (layer == getEditLayer()) { 921 refreshTitle(); 922 } 923 } 924 925 /** 926 * Get a string representation of all layers suitable for the {@code source} changeset tag. 927 */ 928 public String getLayerInformationForSourceTag() { 929 final Collection<String> layerInfo = new ArrayList<>(); 930 if (!getLayersOfType(GpxLayer.class).isEmpty()) { 931 // no i18n for international values 932 layerInfo.add("survey"); 933 } 934 for (final GeoImageLayer i : getLayersOfType(GeoImageLayer.class)) { 935 layerInfo.add(i.getName()); 936 } 937 for (final ImageryLayer i : getLayersOfType(ImageryLayer.class)) { 938 layerInfo.add(ImageryInfo.ImageryType.BING.equals(i.getInfo().getImageryType()) ? "Bing" : i.getName()); 939 } 940 return Utils.join("; ", layerInfo); 941 } 942}