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 { 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 callSetActiveLayer = layer instanceof OsmDataLayer || activeLayer == null; 315 if (callSetActiveLayer) { 316 // autoselect the new layer 317 setActiveLayer(layer); // also repaints this MapView 318 } 319 layer.addPropertyChangeListener(this); 320 Main.addProjectionChangeListener(layer); 321 AudioPlayer.reset(); 322 if (!callSetActiveLayer) { 323 repaint(); 324 } 325 } 326 327 @Override 328 protected DataSet getCurrentDataSet() { 329 if (editLayer != null) 330 return editLayer.data; 331 else 332 return null; 333 } 334 335 /** 336 * Replies true if the active layer is drawable. 337 * 338 * @return true if the active layer is drawable, false otherwise 339 */ 340 public boolean isActiveLayerDrawable() { 341 return editLayer != null; 342 } 343 344 /** 345 * Replies true if the active layer is visible. 346 * 347 * @return true if the active layer is visible, false otherwise 348 */ 349 public boolean isActiveLayerVisible() { 350 return isActiveLayerDrawable() && editLayer.isVisible(); 351 } 352 353 /** 354 * Determines the next active data layer according to the following 355 * rules: 356 * <ul> 357 * <li>if there is at least one {@link OsmDataLayer} the first one 358 * becomes active</li> 359 * <li>otherwise, the top most layer of any type becomes active</li> 360 * </ul> 361 * 362 * @return the next active data layer 363 */ 364 protected Layer determineNextActiveLayer(List<Layer> layersList) { 365 // First look for data layer 366 for (Layer layer:layersList) { 367 if (layer instanceof OsmDataLayer) 368 return layer; 369 } 370 371 // Then any layer 372 if (!layersList.isEmpty()) 373 return layersList.get(0); 374 375 // and then give up 376 return null; 377 378 } 379 380 /** 381 * Remove the layer from the mapview. If the layer was in the list before, 382 * an LayerChange event is fired. 383 * @param layer The layer to remove 384 */ 385 public void removeLayer(Layer layer) { 386 List<Layer> layersList = new ArrayList<Layer>(layers); 387 388 if (!layersList.remove(layer)) 389 return; 390 391 setEditLayer(layersList); 392 393 if (layer == activeLayer) { 394 setActiveLayer(determineNextActiveLayer(layersList), false); 395 } 396 397 layers.remove(layer); 398 Main.removeProjectionChangeListener(layer); 399 fireLayerRemoved(layer); 400 layer.removePropertyChangeListener(this); 401 layer.destroy(); 402 AudioPlayer.reset(); 403 repaint(); 404 } 405 406 private boolean virtualNodesEnabled = false; 407 408 public void setVirtualNodesEnabled(boolean enabled) { 409 if(virtualNodesEnabled != enabled) { 410 virtualNodesEnabled = enabled; 411 repaint(); 412 } 413 } 414 public boolean isVirtualNodesEnabled() { 415 return virtualNodesEnabled; 416 } 417 418 /** 419 * Moves the layer to the given new position. No event is fired, but repaints 420 * according to the new Z-Order of the layers. 421 * 422 * @param layer The layer to move 423 * @param pos The new position of the layer 424 */ 425 public void moveLayer(Layer layer, int pos) { 426 int curLayerPos = layers.indexOf(layer); 427 if (curLayerPos == -1) 428 throw new IllegalArgumentException(tr("Layer not in list.")); 429 if (pos == curLayerPos) 430 return; // already in place. 431 layers.remove(curLayerPos); 432 if (pos >= layers.size()) { 433 layers.add(layer); 434 } else { 435 layers.add(pos, layer); 436 } 437 setEditLayer(layers); 438 AudioPlayer.reset(); 439 repaint(); 440 } 441 442 public int getLayerPos(Layer layer) { 443 int curLayerPos = layers.indexOf(layer); 444 if (curLayerPos == -1) 445 throw new IllegalArgumentException(tr("Layer not in list.")); 446 return curLayerPos; 447 } 448 449 /** 450 * Creates a list of the visible layers in Z-Order, the layer with the lowest Z-Order 451 * first, layer with the highest Z-Order last. 452 * 453 * @return a list of the visible in Z-Order, the layer with the lowest Z-Order 454 * first, layer with the highest Z-Order last. 455 */ 456 protected List<Layer> getVisibleLayersInZOrder() { 457 ArrayList<Layer> ret = new ArrayList<Layer>(); 458 for (Layer l: layers) { 459 if (l.isVisible()) { 460 ret.add(l); 461 } 462 } 463 // sort according to position in the list of layers, with one exception: 464 // an active data layer always becomes a higher Z-Order than all other 465 // data layers 466 // 467 Collections.sort( 468 ret, 469 new Comparator<Layer>() { 470 public int compare(Layer l1, Layer l2) { 471 if (l1 instanceof OsmDataLayer && l2 instanceof OsmDataLayer) { 472 if (l1 == getActiveLayer()) return -1; 473 if (l2 == getActiveLayer()) return 1; 474 return Integer.valueOf(layers.indexOf(l1)).compareTo(layers.indexOf(l2)); 475 } else 476 return Integer.valueOf(layers.indexOf(l1)).compareTo(layers.indexOf(l2)); 477 } 478 } 479 ); 480 Collections.reverse(ret); 481 return ret; 482 } 483 484 private void paintLayer(Layer layer, Graphics2D g, Bounds box) { 485 if (layer.getOpacity() < 1) { 486 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,(float)layer.getOpacity())); 487 } 488 layer.paint(g, this, box); 489 g.setPaintMode(); 490 } 491 492 /** 493 * Draw the component. 494 */ 495 @Override public void paint(Graphics g) { 496 if (BugReportExceptionHandler.exceptionHandlingInProgress()) 497 return; 498 499 if (center == null) 500 return; // no data loaded yet. 501 502 List<Layer> visibleLayers = getVisibleLayersInZOrder(); 503 504 int nonChangedLayersCount = 0; 505 for (Layer l: visibleLayers) { 506 if (l.isChanged() || l == changedLayer) { 507 break; 508 } else { 509 nonChangedLayersCount++; 510 } 511 } 512 513 boolean canUseBuffer; 514 515 synchronized (this) { 516 canUseBuffer = !paintPreferencesChanged; 517 paintPreferencesChanged = false; 518 } 519 canUseBuffer = canUseBuffer && nonChangedLayers.size() <= nonChangedLayersCount && 520 lastViewID == getViewID() && lastClipBounds.contains(g.getClipBounds()); 521 if (canUseBuffer) { 522 for (int i=0; i<nonChangedLayers.size(); i++) { 523 if (visibleLayers.get(i) != nonChangedLayers.get(i)) { 524 canUseBuffer = false; 525 break; 526 } 527 } 528 } 529 530 if (null == offscreenBuffer || offscreenBuffer.getWidth() != getWidth() || offscreenBuffer.getHeight() != getHeight()) { 531 offscreenBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_3BYTE_BGR); 532 } 533 534 Graphics2D tempG = offscreenBuffer.createGraphics(); 535 tempG.setClip(g.getClip()); 536 Bounds box = getLatLonBounds(g.getClipBounds()); 537 538 if (!canUseBuffer || nonChangedLayersBuffer == null) { 539 if (null == nonChangedLayersBuffer || nonChangedLayersBuffer.getWidth() != getWidth() || nonChangedLayersBuffer.getHeight() != getHeight()) { 540 nonChangedLayersBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_3BYTE_BGR); 541 } 542 Graphics2D g2 = nonChangedLayersBuffer.createGraphics(); 543 g2.setClip(g.getClip()); 544 g2.setColor(PaintColors.getBackgroundColor()); 545 g2.fillRect(0, 0, getWidth(), getHeight()); 546 547 for (int i=0; i<nonChangedLayersCount; i++) { 548 paintLayer(visibleLayers.get(i),g2, box); 549 } 550 } else { 551 // Maybe there were more unchanged layers then last time - draw them to buffer 552 if (nonChangedLayers.size() != nonChangedLayersCount) { 553 Graphics2D g2 = nonChangedLayersBuffer.createGraphics(); 554 g2.setClip(g.getClip()); 555 for (int i=nonChangedLayers.size(); i<nonChangedLayersCount; i++) { 556 paintLayer(visibleLayers.get(i),g2, box); 557 } 558 } 559 } 560 561 nonChangedLayers.clear(); 562 changedLayer = null; 563 for (int i=0; i<nonChangedLayersCount; i++) { 564 nonChangedLayers.add(visibleLayers.get(i)); 565 } 566 lastViewID = getViewID(); 567 lastClipBounds = g.getClipBounds(); 568 569 tempG.drawImage(nonChangedLayersBuffer, 0, 0, null); 570 571 for (int i=nonChangedLayersCount; i<visibleLayers.size(); i++) { 572 paintLayer(visibleLayers.get(i),tempG, box); 573 } 574 575 for (MapViewPaintable mvp : temporaryLayers) { 576 mvp.paint(tempG, this, box); 577 } 578 579 // draw world borders 580 tempG.setColor(Color.WHITE); 581 Bounds b = getProjection().getWorldBoundsLatLon(); 582 double lat = b.getMin().lat(); 583 double lon = b.getMin().lon(); 584 585 Point p = getPoint(b.getMin()); 586 587 GeneralPath path = new GeneralPath(); 588 589 path.moveTo(p.x, p.y); 590 double max = b.getMax().lat(); 591 for(; lat <= max; lat += 1.0) 592 { 593 p = getPoint(new LatLon(lat >= max ? max : lat, lon)); 594 path.lineTo(p.x, p.y); 595 } 596 lat = max; max = b.getMax().lon(); 597 for(; lon <= max; lon += 1.0) 598 { 599 p = getPoint(new LatLon(lat, lon >= max ? max : lon)); 600 path.lineTo(p.x, p.y); 601 } 602 lon = max; max = b.getMin().lat(); 603 for(; lat >= max; lat -= 1.0) 604 { 605 p = getPoint(new LatLon(lat <= max ? max : lat, lon)); 606 path.lineTo(p.x, p.y); 607 } 608 lat = max; max = b.getMin().lon(); 609 for(; lon >= max; lon -= 1.0) 610 { 611 p = getPoint(new LatLon(lat, lon <= max ? max : lon)); 612 path.lineTo(p.x, p.y); 613 } 614 615 int w = getWidth(); 616 int h = getHeight(); 617 618 // Work around OpenJDK having problems when drawing out of bounds 619 final Area border = new Area(path); 620 // Make the viewport 1px larger in every direction to prevent an 621 // additional 1px border when zooming in 622 final Area viewport = new Area(new Rectangle(-1, -1, w + 2, h + 2)); 623 border.intersect(viewport); 624 tempG.draw(border); 625 626 if (Main.isDisplayingMapView() && Main.map.filterDialog != null) { 627 Main.map.filterDialog.drawOSDText(tempG); 628 } 629 630 if (playHeadMarker != null) { 631 playHeadMarker.paint(tempG, this); 632 } 633 634 g.drawImage(offscreenBuffer, 0, 0, null); 635 super.paint(g); 636 } 637 638 /** 639 * Set the new dimension to the view. 640 */ 641 public void recalculateCenterScale(BoundingXYVisitor box) { 642 if (box == null) { 643 box = new BoundingXYVisitor(); 644 } 645 if (box.getBounds() == null) { 646 box.visit(getProjection().getWorldBoundsLatLon()); 647 } 648 if (!box.hasExtend()) { 649 box.enlargeBoundingBox(); 650 } 651 652 zoomTo(box.getBounds()); 653 } 654 655 /** 656 * @return An unmodifiable collection of all layers 657 */ 658 public Collection<Layer> getAllLayers() { 659 return Collections.unmodifiableCollection(layers); 660 } 661 662 /** 663 * @return An unmodifiable ordered list of all layers 664 */ 665 public List<Layer> getAllLayersAsList() { 666 return Collections.unmodifiableList(layers); 667 } 668 669 /** 670 * Replies an unmodifiable list of layers of a certain type. 671 * 672 * Example: 673 * <pre> 674 * List<WMSLayer> wmsLayers = getLayersOfType(WMSLayer.class); 675 * </pre> 676 * 677 * @return an unmodifiable list of layers of a certain type. 678 */ 679 public <T> List<T> getLayersOfType(Class<T> ofType) { 680 ArrayList<T> ret = new ArrayList<T>(); 681 for (Layer layer : getAllLayersAsList()) { 682 if (ofType.isInstance(layer)) { 683 ret.add(ofType.cast(layer)); 684 } 685 } 686 return ret; 687 } 688 689 /** 690 * Replies the number of layers managed by this mav view 691 * 692 * @return the number of layers managed by this mav view 693 */ 694 public int getNumLayers() { 695 return layers.size(); 696 } 697 698 /** 699 * Replies true if there is at least one layer in this map view 700 * 701 * @return true if there is at least one layer in this map view 702 */ 703 public boolean hasLayers() { 704 return getNumLayers() > 0; 705 } 706 707 private void setEditLayer(List<Layer> layersList) { 708 OsmDataLayer newEditLayer = layersList.contains(editLayer)?editLayer:null; 709 OsmDataLayer oldEditLayer = editLayer; 710 711 // Find new edit layer 712 if (activeLayer != editLayer || !layersList.contains(editLayer)) { 713 if (activeLayer instanceof OsmDataLayer && layersList.contains(activeLayer)) { 714 newEditLayer = (OsmDataLayer) activeLayer; 715 } else { 716 for (Layer layer:layersList) { 717 if (layer instanceof OsmDataLayer) { 718 newEditLayer = (OsmDataLayer) layer; 719 break; 720 } 721 } 722 } 723 } 724 725 // Set new edit layer 726 if (newEditLayer != editLayer) { 727 if (newEditLayer == null) { 728 getCurrentDataSet().setSelected(); 729 } 730 731 editLayer = newEditLayer; 732 fireEditLayerChanged(oldEditLayer, newEditLayer); 733 refreshTitle(); 734 } 735 736 } 737 738 /** 739 * Sets the active layer to <code>layer</code>. If <code>layer</code> is an instance 740 * of {@link OsmDataLayer} also sets {@link #editLayer} to <code>layer</code>. 741 * 742 * @param layer the layer to be activate; must be one of the layers in the list of layers 743 * @exception IllegalArgumentException thrown if layer is not in the lis of layers 744 */ 745 public void setActiveLayer(Layer layer) { 746 setActiveLayer(layer, true); 747 } 748 749 private void setActiveLayer(Layer layer, boolean setEditLayer) { 750 if (layer != null && !layers.contains(layer)) 751 throw new IllegalArgumentException(tr("Layer ''{0}'' must be in list of layers", layer.toString())); 752 753 if (layer == activeLayer) 754 return; 755 756 Layer old = activeLayer; 757 activeLayer = layer; 758 if (setEditLayer) { 759 setEditLayer(layers); 760 } 761 fireActiveLayerChanged(old, layer); 762 763 /* This only makes the buttons look disabled. Disabling the actions as well requires 764 * the user to re-select the tool after i.e. moving a layer. While testing I found 765 * that I switch layers and actions at the same time and it was annoying to mind the 766 * order. This way it works as visual clue for new users */ 767 for (Enumeration<AbstractButton> e = Main.map.toolGroup.getElements() ; e.hasMoreElements() ;) { 768 AbstractButton button = e.nextElement(); 769 MapMode mode = (MapMode)button.getAction(); 770 boolean isLayerSupported = mode.layerIsSupported(layer); 771 button.setEnabled(isLayerSupported); 772 // Also update associated shortcut (fix #6876) 773 if (isLayerSupported) { 774 Main.registerActionShortcut(mode, mode.getShortcut()); 775 } else { 776 Main.unregisterShortcut(mode.getShortcut()); 777 } 778 } 779 AudioPlayer.reset(); 780 repaint(); 781 } 782 783 /** 784 * Replies the currently active layer 785 * 786 * @return the currently active layer (may be null) 787 */ 788 public Layer getActiveLayer() { 789 return activeLayer; 790 } 791 792 /** 793 * Replies the current edit layer, if any 794 * 795 * @return the current edit layer. May be null. 796 */ 797 public OsmDataLayer getEditLayer() { 798 return editLayer; 799 } 800 801 /** 802 * replies true if the list of layers managed by this map view contain layer 803 * 804 * @param layer the layer 805 * @return true if the list of layers managed by this map view contain layer 806 */ 807 public boolean hasLayer(Layer layer) { 808 return layers.contains(layer); 809 } 810 811 /** 812 * Tries to zoom to the download boundingbox[es] of the current edit layer 813 * (aka {@link OsmDataLayer}). If the edit layer has multiple download bounding 814 * boxes it zooms to a large virtual bounding box containing all smaller ones. 815 * 816 * @return <code>true</code> if a zoom operation has been performed 817 */ 818 public boolean zoomToDataSetBoundingBox(DataSet ds) { 819 // In case we already have an existing data layer ... 820 OsmDataLayer layer= getEditLayer(); 821 if (layer == null) 822 return false; 823 Collection<DataSource> dataSources = ds.dataSources; 824 // ... with bounding box[es] of data loaded from OSM or a file... 825 BoundingXYVisitor bbox = new BoundingXYVisitor(); 826 for (DataSource source : dataSources) { 827 bbox.visit(source.bounds); 828 } 829 if (bbox.hasExtend()) { 830 // ... we zoom to it's bounding box 831 recalculateCenterScale(bbox); 832 return true; 833 } 834 return false; 835 } 836 837 public boolean addTemporaryLayer(MapViewPaintable mvp) { 838 if (temporaryLayers.contains(mvp)) return false; 839 return temporaryLayers.add(mvp); 840 } 841 842 public boolean removeTemporaryLayer(MapViewPaintable mvp) { 843 return temporaryLayers.remove(mvp); 844 } 845 846 public void propertyChange(PropertyChangeEvent evt) { 847 if (evt.getPropertyName().equals(Layer.VISIBLE_PROP)) { 848 repaint(); 849 } else if (evt.getPropertyName().equals(Layer.OPACITY_PROP)) { 850 Layer l = (Layer)evt.getSource(); 851 if (l.isVisible()) { 852 changedLayer = l; 853 repaint(); 854 } 855 } else if (evt.getPropertyName().equals(OsmDataLayer.REQUIRES_SAVE_TO_DISK_PROP) 856 || evt.getPropertyName().equals(OsmDataLayer.REQUIRES_UPLOAD_TO_SERVER_PROP)) { 857 OsmDataLayer layer = (OsmDataLayer)evt.getSource(); 858 if (layer == getEditLayer()) { 859 refreshTitle(); 860 } 861 } 862 } 863 864 protected void refreshTitle() { 865 boolean dirty = editLayer != null && (editLayer.requiresSaveToFile() || editLayer.requiresUploadToServer()); 866 if (dirty) { 867 JOptionPane.getFrameForComponent(Main.parent).setTitle("* " + tr("Java OpenStreetMap Editor")); 868 } else { 869 JOptionPane.getFrameForComponent(Main.parent).setTitle(tr("Java OpenStreetMap Editor")); 870 } 871 } 872 873 @Override 874 public void preferenceChanged(PreferenceChangeEvent e) { 875 synchronized (this) { 876 paintPreferencesChanged = true; 877 } 878 } 879 880 private SelectionChangedListener repaintSelectionChangedListener = new SelectionChangedListener(){ 881 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) { 882 repaint(); 883 } 884 }; 885 886 public void destroy() { 887 Main.pref.removePreferenceChangeListener(this); 888 DataSet.removeSelectionListener(repaintSelectionChangedListener); 889 MultipolygonCache.getInstance().clear(this); 890 if (mapMover != null) { 891 mapMover.destroy(); 892 } 893 } 894 895 }