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