source: josm/trunk/src/org/openstreetmap/josm/gui/MapView.java@ 4623

Last change on this file since 4623 was 4623, checked in by Don-vip, 12 years ago

see #6987 - multipolygon cache aware of events requiring cache refresh (data change, zoom, layer removing)

  • Property svn:eol-style set to native
File size: 28.7 KB
RevLine 
[608]1// License: GPL. See LICENSE file for details.
[283]2package org.openstreetmap.josm.gui;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
[3705]6import java.awt.AlphaComposite;
[283]7import java.awt.Color;
8import java.awt.Graphics;
[635]9import java.awt.Graphics2D;
[283]10import java.awt.Point;
[2107]11import java.awt.Rectangle;
[1750]12import java.awt.event.ComponentAdapter;
13import java.awt.event.ComponentEvent;
[811]14import java.awt.event.MouseEvent;
15import java.awt.event.MouseMotionListener;
[2107]16import java.awt.geom.Area;
[1823]17import java.awt.geom.GeneralPath;
[635]18import java.awt.image.BufferedImage;
[1890]19import java.beans.PropertyChangeEvent;
20import java.beans.PropertyChangeListener;
[283]21import java.util.ArrayList;
22import java.util.Collection;
23import java.util.Collections;
[1943]24import java.util.Comparator;
[1750]25import java.util.Enumeration;
[283]26import java.util.LinkedList;
[1895]27import java.util.List;
[2621]28import java.util.concurrent.CopyOnWriteArrayList;
[283]29
[1379]30import javax.swing.AbstractButton;
[283]31import javax.swing.JOptionPane;
[3252]32import javax.swing.JPanel;
[283]33
34import org.openstreetmap.josm.Main;
35import org.openstreetmap.josm.actions.AutoScaleAction;
[1379]36import org.openstreetmap.josm.actions.mapmode.MapMode;
[1823]37import org.openstreetmap.josm.data.Bounds;
[3128]38import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent;
39import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
[3594]40import org.openstreetmap.josm.data.SelectionChangedListener;
[1854]41import org.openstreetmap.josm.data.coor.LatLon;
[290]42import org.openstreetmap.josm.data.osm.DataSet;
[1022]43import org.openstreetmap.josm.data.osm.DataSource;
[283]44import org.openstreetmap.josm.data.osm.OsmPrimitive;
45import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
[2666]46import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
[4623]47import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache;
[2395]48import org.openstreetmap.josm.gui.layer.GpxLayer;
[283]49import org.openstreetmap.josm.gui.layer.Layer;
[608]50import org.openstreetmap.josm.gui.layer.MapViewPaintable;
[283]51import org.openstreetmap.josm.gui.layer.OsmDataLayer;
[572]52import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
53import org.openstreetmap.josm.gui.layer.markerlayer.PlayHeadMarker;
[1685]54import org.openstreetmap.josm.tools.AudioPlayer;
[3478]55import org.openstreetmap.josm.tools.BugReportExceptionHandler;
[283]56
57/**
58 * This is a component used in the MapFrame for browsing the map. It use is to
59 * provide the MapMode's enough capabilities to operate.
60 *
61 * MapView hold meta-data about the data set currently displayed, as scale level,
62 * center point viewed, what scrolling mode or editing mode is selected or with
63 * what projection the map is viewed etc..
64 *
65 * MapView is able to administrate several layers.
66 *
67 * @author imi
68 */
[3128]69public class MapView extends NavigatableComponent implements PropertyChangeListener, PreferenceChangedListener {
[283]70
[1169]71 /**
[2621]72 * Interface to notify listeners of the change of the active layer.
73 * @author imi
74 */
75 public interface LayerChangeListener {
76 void activeLayerChange(Layer oldLayer, Layer newLayer);
77 void layerAdded(Layer newLayer);
78 void layerRemoved(Layer oldLayer);
79 }
80
[2652]81 public interface EditLayerChangeListener {
82 void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer);
83 }
84
[3837]85 public boolean viewportFollowing = false;
86
[2621]87 /**
88 * the layer listeners
89 */
[2655]90 private static final CopyOnWriteArrayList<MapView.LayerChangeListener> layerChangeListeners = new CopyOnWriteArrayList<MapView.LayerChangeListener>();
91 private static final CopyOnWriteArrayList<EditLayerChangeListener> editLayerChangeListeners = new CopyOnWriteArrayList<EditLayerChangeListener>();
[2621]92
93 /**
94 * Removes a layer change listener
[2711]95 *
[2621]96 * @param listener the listener. Ignored if null or already registered.
97 */
98 public static void removeLayerChangeListener(MapView.LayerChangeListener listener) {
[2655]99 layerChangeListeners.remove(listener);
[2621]100 }
101
[2652]102 public static void removeEditLayerChangeListener(EditLayerChangeListener listener) {
[2655]103 editLayerChangeListeners.remove(listener);
[2652]104 }
105
[2621]106 /**
107 * Adds a layer change listener
[2711]108 *
[2621]109 * @param listener the listener. Ignored if null or already registered.
110 */
111 public static void addLayerChangeListener(MapView.LayerChangeListener listener) {
[2655]112 if (listener != null) {
113 layerChangeListeners.addIfAbsent(listener);
[2621]114 }
115 }
116
[3078]117 /**
[3669]118 * Adds an edit layer change listener
[3078]119 *
120 * @param listener the listener. Ignored if null or already registered.
[4126]121 * @param initialFire Fire an edit-layer-changed-event right after adding
[3669]122 * the listener in case there is an edit layer present
[3078]123 */
124 public static void addEditLayerChangeListener(EditLayerChangeListener listener, boolean initialFire) {
125 addEditLayerChangeListener(listener);
126 if (initialFire) {
127 if (Main.map != null && Main.map.mapView != null && Main.map.mapView.getEditLayer() != null) {
128 fireEditLayerChanged(null, Main.map.mapView.getEditLayer());
129 }
130 }
131 }
132
[2652]133 public static void addEditLayerChangeListener(EditLayerChangeListener listener) {
[2655]134 if (listener != null) {
135 editLayerChangeListeners.addIfAbsent(listener);
[2652]136 }
137 }
138
[2621]139 protected static void fireActiveLayerChanged(Layer oldLayer, Layer newLayer) {
140 for (LayerChangeListener l : layerChangeListeners) {
141 l.activeLayerChange(oldLayer, newLayer);
142 }
143 }
144
145 protected static void fireLayerAdded(Layer newLayer) {
146 for (MapView.LayerChangeListener l : MapView.layerChangeListeners) {
147 l.layerAdded(newLayer);
148 }
149 }
150
151 protected static void fireLayerRemoved(Layer layer) {
152 for (MapView.LayerChangeListener l : MapView.layerChangeListeners) {
153 l.layerRemoved(layer);
154 }
155 }
156
[2652]157 protected static void fireEditLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
158 for (EditLayerChangeListener l : editLayerChangeListeners) {
159 l.editLayerChanged(oldLayer, newLayer);
160 }
161 }
162
[2621]163 /**
[1169]164 * A list of all layers currently loaded.
165 */
[3116]166 private final List<Layer> layers = new ArrayList<Layer>();
[1169]167 /**
168 * The play head marker: there is only one of these so it isn't in any specific layer
169 */
170 public PlayHeadMarker playHeadMarker = null;
[1750]171
[1169]172 /**
173 * The layer from the layers list that is currently active.
174 */
175 private Layer activeLayer;
[1023]176
[2652]177 private OsmDataLayer editLayer;
178
[1169]179 /**
180 * The last event performed by mouse.
181 */
[3451]182 public MouseEvent lastMEvent = new MouseEvent(this, 0, 0, 0, 0, 0, 0, false); // In case somebody reads it before first mouse move
[811]183
[1169]184 private LinkedList<MapViewPaintable> temporaryLayers = new LinkedList<MapViewPaintable>();
[1023]185
[3136]186 private BufferedImage nonChangedLayersBuffer;
[1169]187 private BufferedImage offscreenBuffer;
[3116]188 // Layers that wasn't changed since last paint
189 private final List<Layer> nonChangedLayers = new ArrayList<Layer>();
[3705]190 private Layer changedLayer;
[3118]191 private int lastViewID;
[3128]192 private boolean paintPreferencesChanged = true;
[3144]193 private Rectangle lastClipBounds = new Rectangle();
[1023]194
[3252]195 public MapView(final JPanel contentPane) {
[3128]196 Main.pref.addPreferenceChangeListener(this);
[3327]197
198 // new MoveAction(MoveAction.Direction.UP);
199 // new MoveAction(MoveAction.Direction.DOWN);
200 // new MoveAction(MoveAction.Direction.LEFT);
201 // new MoveAction(MoveAction.Direction.RIGHT);
202
[1169]203 addComponentListener(new ComponentAdapter(){
204 @Override public void componentResized(ComponentEvent e) {
205 removeComponentListener(this);
[1677]206
[1518]207 MapSlider zoomSlider = new MapSlider(MapView.this);
208 add(zoomSlider);
209 zoomSlider.setBounds(3, 0, 114, 30);
[506]210
[1722]211 MapScaler scaler = new MapScaler(MapView.this);
[1518]212 add(scaler);
213 scaler.setLocation(10,30);
214
[4244]215 new MapMover(MapView.this, contentPane);
[3010]216 OsmDataLayer layer = getEditLayer();
217 if (layer != null) {
218 if (!zoomToDataSetBoundingBox(layer.data)) {
219 // no bounding box defined
[3327]220 AutoScaleAction.autoScale("data");
[3010]221 }
222 } else {
[3327]223 AutoScaleAction.autoScale("layer");
[1750]224 }
[1169]225 }
226 });
[283]227
[1169]228 // listend to selection changes to redraw the map
[3443]229 DataSet.addSelectionListener(repaintSelectionChangedListener);
[811]230
[1169]231 //store the last mouse action
232 this.addMouseMotionListener(new MouseMotionListener() {
233 public void mouseDragged(MouseEvent e) {
234 mouseMoved(e);
235 }
236 public void mouseMoved(MouseEvent e) {
237 lastMEvent = e;
238 }
239 });
[4623]240
241 // Add Multipolygon cache to layer and zoom listeners
242 addLayerChangeListener(MultipolygonCache.getInstance());
243 addZoomChangeListener(MultipolygonCache.getInstance());
[1169]244 }
[283]245
[1169]246 /**
[2395]247 * Adds a GPX layer. A GPX layer is added below the lowest data layer.
[2512]248 *
[2395]249 * @param layer the GPX layer
250 */
251 protected void addGpxLayer(GpxLayer layer) {
252 if (layers.isEmpty()) {
253 layers.add(layer);
254 return;
255 }
[2446]256 for (int i=layers.size()-1; i>= 0; i--) {
[2395]257 if (layers.get(i) instanceof OsmDataLayer) {
[2446]258 if (i == layers.size()-1) {
[2395]259 layers.add(layer);
260 } else {
261 layers.add(i+1, layer);
262 }
263 return;
264 }
265 }
[3229]266 layers.add(0, layer);
[2395]267 }
268
269 /**
[1169]270 * Add a layer to the current MapView. The layer will be added at topmost
271 * position.
272 */
273 public void addLayer(Layer layer) {
[1750]274 if (layer instanceof MarkerLayer && playHeadMarker == null) {
[1169]275 playHeadMarker = PlayHeadMarker.create();
[1750]276 }
[2192]277
[2446]278 if (layer instanceof GpxLayer) {
279 addGpxLayer((GpxLayer)layer);
280 } else if (layer.isBackgroundLayer() || layers.isEmpty()) {
[2192]281 layers.add(layer);
282 } else {
283 layers.add(0, layer);
[1750]284 }
[2621]285 fireLayerAdded(layer);
[1169]286 if (layer instanceof OsmDataLayer || activeLayer == null) {
287 // autoselect the new layer
288 setActiveLayer(layer);
289 }
[1890]290 layer.addPropertyChangeListener(this);
[4126]291 Main.addProjectionChangeListener(layer);
[1685]292 AudioPlayer.reset();
[1169]293 repaint();
294 }
[283]295
[1169]296 @Override
[1814]297 protected DataSet getCurrentDataSet() {
[2652]298 if (editLayer != null)
299 return editLayer.data;
300 else
301 return null;
[1169]302 }
[845]303
[1750]304 /**
305 * Replies true if the active layer is drawable.
[2512]306 *
[1750]307 * @return true if the active layer is drawable, false otherwise
308 */
309 public boolean isActiveLayerDrawable() {
[2652]310 return editLayer != null;
[1169]311 }
[1677]312
[1750]313 /**
314 * Replies true if the active layer is visible.
[2512]315 *
[1750]316 * @return true if the active layer is visible, false otherwise
317 */
318 public boolean isActiveLayerVisible() {
[2652]319 return isActiveLayerDrawable() && editLayer.isVisible();
[1418]320 }
[845]321
[1169]322 /**
[1952]323 * Determines the next active data layer according to the following
324 * rules:
325 * <ul>
326 * <li>if there is at least one {@see OsmDataLayer} the first one
327 * becomes active</li>
328 * <li>otherwise, the top most layer of any type becomes active</li>
329 * </ul>
[2512]330 *
[1952]331 * @return the next active data layer
332 */
[2860]333 protected Layer determineNextActiveLayer(List<Layer> layersList) {
[2652]334 // First look for data layer
[2860]335 for (Layer layer:layersList) {
336 if (layer instanceof OsmDataLayer)
[2652]337 return layer;
338 }
[1952]339
[2652]340 // Then any layer
[2860]341 if (!layersList.isEmpty())
342 return layersList.get(0);
[2652]343
344 // and then give up
345 return null;
346
[1952]347 }
348
349 /**
[1169]350 * Remove the layer from the mapview. If the layer was in the list before,
351 * an LayerChange event is fired.
352 */
353 public void removeLayer(Layer layer) {
[2860]354 List<Layer> layersList = new ArrayList<Layer>(layers);
355
356 if (!layersList.remove(layer))
357 return;
358
359 setEditLayer(layersList);
360
[1808]361 if (layer == activeLayer) {
[2860]362 setActiveLayer(determineNextActiveLayer(layersList), false);
[1808]363 }
[2860]364
365 layers.remove(layer);
[4126]366 Main.removeProjectionChangeListener(layer);
[2860]367 fireLayerRemoved(layer);
[1890]368 layer.removePropertyChangeListener(this);
[1169]369 layer.destroy();
[1685]370 AudioPlayer.reset();
[1895]371 repaint();
[1169]372 }
[283]373
[1750]374 private boolean virtualNodesEnabled = false;
[2621]375
[1750]376 public void setVirtualNodesEnabled(boolean enabled) {
377 if(virtualNodesEnabled != enabled) {
378 virtualNodesEnabled = enabled;
[1169]379 repaint();
380 }
381 }
[1750]382 public boolean isVirtualNodesEnabled() {
383 return virtualNodesEnabled;
[1169]384 }
[805]385
[1169]386 /**
[1943]387 * Moves the layer to the given new position. No event is fired, but repaints
388 * according to the new Z-Order of the layers.
[2512]389 *
[1169]390 * @param layer The layer to move
391 * @param pos The new position of the layer
392 */
393 public void moveLayer(Layer layer, int pos) {
394 int curLayerPos = layers.indexOf(layer);
395 if (curLayerPos == -1)
[2181]396 throw new IllegalArgumentException(tr("Layer not in list."));
[1169]397 if (pos == curLayerPos)
398 return; // already in place.
399 layers.remove(curLayerPos);
[1750]400 if (pos >= layers.size()) {
[1169]401 layers.add(layer);
[1750]402 } else {
[1169]403 layers.add(pos, layer);
[1750]404 }
[2860]405 setEditLayer(layers);
[1685]406 AudioPlayer.reset();
[1943]407 repaint();
[1169]408 }
[283]409
[1265]410 public int getLayerPos(Layer layer) {
411 int curLayerPos = layers.indexOf(layer);
412 if (curLayerPos == -1)
[2181]413 throw new IllegalArgumentException(tr("Layer not in list."));
[1265]414 return curLayerPos;
415 }
416
[1169]417 /**
[1943]418 * Creates a list of the visible layers in Z-Order, the layer with the lowest Z-Order
419 * first, layer with the highest Z-Order last.
[2512]420 *
[1943]421 * @return a list of the visible in Z-Order, the layer with the lowest Z-Order
422 * first, layer with the highest Z-Order last.
423 */
424 protected List<Layer> getVisibleLayersInZOrder() {
425 ArrayList<Layer> ret = new ArrayList<Layer>();
426 for (Layer l: layers) {
427 if (l.isVisible()) {
428 ret.add(l);
429 }
430 }
431 // sort according to position in the list of layers, with one exception:
432 // an active data layer always becomes a higher Z-Order than all other
433 // data layers
434 //
435 Collections.sort(
436 ret,
437 new Comparator<Layer>() {
438 public int compare(Layer l1, Layer l2) {
439 if (l1 instanceof OsmDataLayer && l2 instanceof OsmDataLayer) {
440 if (l1 == getActiveLayer()) return -1;
441 if (l2 == getActiveLayer()) return 1;
[2626]442 return Integer.valueOf(layers.indexOf(l1)).compareTo(layers.indexOf(l2));
[1943]443 } else
[2626]444 return Integer.valueOf(layers.indexOf(l1)).compareTo(layers.indexOf(l2));
[1943]445 }
446 }
447 );
448 Collections.reverse(ret);
449 return ret;
450 }
451
[3705]452 private void paintLayer(Layer layer, Graphics2D g, Bounds box) {
453 if (layer.getOpacity() < 1) {
454 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,(float)layer.getOpacity()));
455 }
456 layer.paint(g, this, box);
457 g.setPaintMode();
458 }
459
[1943]460 /**
[1169]461 * Draw the component.
462 */
463 @Override public void paint(Graphics g) {
[3478]464 if (BugReportExceptionHandler.exceptionHandlingInProgress())
465 return;
466
[1169]467 if (center == null)
468 return; // no data loaded yet.
[775]469
[3116]470 List<Layer> visibleLayers = getVisibleLayersInZOrder();
471
472 int nonChangedLayersCount = 0;
473 for (Layer l: visibleLayers) {
[3705]474 if (l.isChanged() || l == changedLayer) {
[3116]475 break;
476 } else {
477 nonChangedLayersCount++;
478 }
[1750]479 }
[775]480
[3144]481 boolean canUseBuffer = !paintPreferencesChanged && nonChangedLayers.size() <= nonChangedLayersCount &&
482 lastViewID == getViewID() && lastClipBounds.contains(g.getClipBounds());
[3116]483 if (canUseBuffer) {
484 for (int i=0; i<nonChangedLayers.size(); i++) {
485 if (visibleLayers.get(i) != nonChangedLayers.get(i)) {
486 canUseBuffer = false;
487 break;
488 }
489 }
490 }
[283]491
[3136]492 if (null == offscreenBuffer || offscreenBuffer.getWidth() != getWidth() || offscreenBuffer.getHeight() != getHeight()) {
493 offscreenBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_3BYTE_BGR);
494 }
495
496 Graphics2D tempG = offscreenBuffer.createGraphics();
497 tempG.setClip(g.getClip());
[2450]498 Bounds box = getLatLonBounds(g.getClipBounds());
499
[3136]500 if (!canUseBuffer || nonChangedLayersBuffer == null) {
501 if (null == nonChangedLayersBuffer || nonChangedLayersBuffer.getWidth() != getWidth() || nonChangedLayersBuffer.getHeight() != getHeight()) {
502 nonChangedLayersBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_3BYTE_BGR);
[3116]503 }
[3136]504 Graphics2D g2 = nonChangedLayersBuffer.createGraphics();
[3125]505 g2.setClip(g.getClip());
[3896]506 g2.setColor(PaintColors.getBackgroundColor());
[3116]507 g2.fillRect(0, 0, getWidth(), getHeight());
508
509 for (int i=0; i<nonChangedLayersCount; i++) {
[3705]510 paintLayer(visibleLayers.get(i),g2, box);
[3116]511 }
512 } else {
513 // Maybe there were more unchanged layers then last time - draw them to buffer
514 if (nonChangedLayers.size() != nonChangedLayersCount) {
[3136]515 Graphics2D g2 = nonChangedLayersBuffer.createGraphics();
[3125]516 g2.setClip(g.getClip());
[3116]517 for (int i=nonChangedLayers.size(); i<nonChangedLayersCount; i++) {
[3705]518 paintLayer(visibleLayers.get(i),g2, box);
[3116]519 }
520 }
[1169]521 }
[3116]522
523 nonChangedLayers.clear();
[3705]524 changedLayer = null;
[3116]525 for (int i=0; i<nonChangedLayersCount; i++) {
526 nonChangedLayers.add(visibleLayers.get(i));
527 }
[3118]528 lastViewID = getViewID();
[3128]529 paintPreferencesChanged = false;
[3144]530 lastClipBounds = g.getClipBounds();
[3116]531
[3136]532 tempG.drawImage(nonChangedLayersBuffer, 0, 0, null);
[3116]533
534 for (int i=nonChangedLayersCount; i<visibleLayers.size(); i++) {
[3705]535 paintLayer(visibleLayers.get(i),tempG, box);
[3116]536 }
537
[1169]538 for (MapViewPaintable mvp : temporaryLayers) {
[2450]539 mvp.paint(tempG, this, box);
[1169]540 }
[1023]541
[1169]542 // draw world borders
543 tempG.setColor(Color.WHITE);
[1823]544 Bounds b = getProjection().getWorldBoundsLatLon();
[2327]545 double lat = b.getMin().lat();
546 double lon = b.getMin().lon();
[1823]547
[2327]548 Point p = getPoint(b.getMin());
[2107]549
550 GeneralPath path = new GeneralPath();
551
[1823]552 path.moveTo(p.x, p.y);
[2327]553 double max = b.getMax().lat();
[1823]554 for(; lat <= max; lat += 1.0)
555 {
556 p = getPoint(new LatLon(lat >= max ? max : lat, lon));
557 path.lineTo(p.x, p.y);
[1750]558 }
[2327]559 lat = max; max = b.getMax().lon();
[1823]560 for(; lon <= max; lon += 1.0)
561 {
562 p = getPoint(new LatLon(lat, lon >= max ? max : lon));
563 path.lineTo(p.x, p.y);
564 }
[2327]565 lon = max; max = b.getMin().lat();
[1823]566 for(; lat >= max; lat -= 1.0)
567 {
568 p = getPoint(new LatLon(lat <= max ? max : lat, lon));
569 path.lineTo(p.x, p.y);
570 }
[2327]571 lat = max; max = b.getMin().lon();
[1823]572 for(; lon >= max; lon -= 1.0)
573 {
574 p = getPoint(new LatLon(lat, lon <= max ? max : lon));
575 path.lineTo(p.x, p.y);
576 }
[1023]577
[3116]578 int w = getWidth();
579 int h = getHeight();
[2107]580
581 // Work around OpenJDK having problems when drawing out of bounds
582 final Area border = new Area(path);
583 // Make the viewport 1px larger in every direction to prevent an
584 // additional 1px border when zooming in
585 final Area viewport = new Area(new Rectangle(-1, -1, w + 2, h + 2));
586 border.intersect(viewport);
587 tempG.draw(border);
588
[4479]589 if (Main.map != null && Main.map.filterDialog != null) {
[3178]590 Main.map.filterDialog.drawOSDText(tempG);
591 }
592
[1750]593 if (playHeadMarker != null) {
[1169]594 playHeadMarker.paint(tempG, this);
[1750]595 }
[572]596
[3136]597 g.drawImage(offscreenBuffer, 0, 0, null);
[1169]598 super.paint(g);
599 }
[283]600
[1169]601 /**
[1722]602 * Set the new dimension to the view.
[1169]603 */
604 public void recalculateCenterScale(BoundingXYVisitor box) {
[1797]605 if (box == null) {
[1722]606 box = new BoundingXYVisitor();
[1750]607 }
[1797]608 if (box.getBounds() == null) {
[1823]609 box.visit(getProjection().getWorldBoundsLatLon());
[1750]610 }
[1797]611 if (!box.hasExtend()) {
[1750]612 box.enlargeBoundingBox();
613 }
[283]614
[1722]615 zoomTo(box.getBounds());
[1169]616 }
[283]617
[1169]618 /**
[1750]619 * @return An unmodifiable collection of all layers
[1169]620 */
621 public Collection<Layer> getAllLayers() {
622 return Collections.unmodifiableCollection(layers);
623 }
[283]624
[1169]625 /**
[1895]626 * @return An unmodifiable ordered list of all layers
627 */
628 public List<Layer> getAllLayersAsList() {
629 return Collections.unmodifiableList(layers);
630 }
631
632 /**
[1914]633 * Replies an unmodifiable list of layers of a certain type.
[2512]634 *
[1914]635 * Example:
636 * <pre>
637 * List<WMSLayer> wmsLayers = getLayersOfType(WMSLayer.class);
638 * </pre>
[2512]639 *
[1914]640 * @return an unmodifiable list of layers of a certain type.
641 */
642 public <T> List<T> getLayersOfType(Class<T> ofType) {
643 ArrayList<T> ret = new ArrayList<T>();
644 for (Layer layer : getAllLayersAsList()) {
645 if (ofType.isInstance(layer)) {
646 ret.add(ofType.cast(layer));
647 }
648 }
649 return ret;
650 }
651
652 /**
[1895]653 * Replies the number of layers managed by this mav view
[2512]654 *
[1895]655 * @return the number of layers managed by this mav view
656 */
657 public int getNumLayers() {
658 return layers.size();
659 }
660
661 /**
662 * Replies true if there is at least one layer in this map view
[2512]663 *
[1895]664 * @return true if there is at least one layer in this map view
665 */
666 public boolean hasLayers() {
667 return getNumLayers() > 0;
668 }
669
[2860]670 private void setEditLayer(List<Layer> layersList) {
671 OsmDataLayer newEditLayer = layersList.contains(editLayer)?editLayer:null;
[2652]672 OsmDataLayer oldEditLayer = editLayer;
673
674 // Find new edit layer
[2860]675 if (activeLayer != editLayer || !layersList.contains(editLayer)) {
676 if (activeLayer instanceof OsmDataLayer && layersList.contains(activeLayer)) {
[2652]677 newEditLayer = (OsmDataLayer) activeLayer;
678 } else {
[2860]679 for (Layer layer:layersList) {
[2652]680 if (layer instanceof OsmDataLayer) {
681 newEditLayer = (OsmDataLayer) layer;
682 break;
683 }
684 }
685 }
686 }
687
688 // Set new edit layer
689 if (newEditLayer != editLayer) {
690 if (newEditLayer == null) {
691 getCurrentDataSet().setSelected();
692 }
693
694 editLayer = newEditLayer;
695 fireEditLayerChanged(oldEditLayer, newEditLayer);
696 refreshTitle();
697 }
698
699 }
700
[1895]701 /**
[1750]702 * Sets the active layer to <code>layer</code>. If <code>layer</code> is an instance
703 * of {@see OsmDataLayer} also sets {@see #editLayer} to <code>layer</code>.
[2512]704 *
[1750]705 * @param layer the layer to be activate; must be one of the layers in the list of layers
706 * @exception IllegalArgumentException thrown if layer is not in the lis of layers
[1169]707 */
708 public void setActiveLayer(Layer layer) {
[2860]709 setActiveLayer(layer, true);
710 }
711
712 private void setActiveLayer(Layer layer, boolean setEditLayer) {
[2653]713 if (layer != null && !layers.contains(layer))
[1814]714 throw new IllegalArgumentException(tr("Layer ''{0}'' must be in list of layers", layer.toString()));
[2652]715
716 if (layer == activeLayer)
717 return;
718
[1169]719 Layer old = activeLayer;
720 activeLayer = layer;
[2860]721 if (setEditLayer) {
722 setEditLayer(layers);
723 }
[2652]724 fireActiveLayerChanged(old, layer);
[1662]725
[1379]726 /* This only makes the buttons look disabled. Disabling the actions as well requires
727 * the user to re-select the tool after i.e. moving a layer. While testing I found
728 * that I switch layers and actions at the same time and it was annoying to mind the
729 * order. This way it works as visual clue for new users */
[1415]730 for (Enumeration<AbstractButton> e = Main.map.toolGroup.getElements() ; e.hasMoreElements() ;) {
[4480]731 AbstractButton button = e.nextElement();
732 MapMode mode = (MapMode)button.getAction();
733 boolean isLayerSupported = mode.layerIsSupported(layer);
734 button.setEnabled(isLayerSupported);
735 // Also update associated shortcut (fix #6876)
736 if (isLayerSupported) {
737 Main.registerActionShortcut(mode, mode.getShortcut());
738 } else {
739 Main.unregisterActionShortcut(mode.getShortcut());
740 }
[1379]741 }
[1685]742 AudioPlayer.reset();
[1169]743 repaint();
744 }
[283]745
[1169]746 /**
[1750]747 * Replies the currently active layer
[2512]748 *
[1750]749 * @return the currently active layer (may be null)
[1169]750 */
751 public Layer getActiveLayer() {
752 return activeLayer;
753 }
[283]754
[1169]755 /**
[1750]756 * Replies the current edit layer, if any
[2512]757 *
[1750]758 * @return the current edit layer. May be null.
759 */
760 public OsmDataLayer getEditLayer() {
[2652]761 return editLayer;
[1750]762 }
763
764 /**
765 * replies true if the list of layers managed by this map view contain layer
[2512]766 *
[1750]767 * @param layer the layer
768 * @return true if the list of layers managed by this map view contain layer
769 */
770 public boolean hasLayer(Layer layer) {
771 return layers.contains(layer);
772 }
773
774 /**
[1169]775 * Tries to zoom to the download boundingbox[es] of the current edit layer
776 * (aka {@link OsmDataLayer}). If the edit layer has multiple download bounding
777 * boxes it zooms to a large virtual bounding box containing all smaller ones.
778 *
779 * @return <code>true</code> if a zoom operation has been performed
780 */
[3010]781 public boolean zoomToDataSetBoundingBox(DataSet ds) {
[1169]782 // In case we already have an existing data layer ...
[1808]783 OsmDataLayer layer= getEditLayer();
784 if (layer == null)
785 return false;
[3010]786 Collection<DataSource> dataSources = ds.dataSources;
[1169]787 // ... with bounding box[es] of data loaded from OSM or a file...
788 BoundingXYVisitor bbox = new BoundingXYVisitor();
[3010]789 for (DataSource source : dataSources) {
790 bbox.visit(source.bounds);
[1169]791 }
[3010]792 if (bbox.hasExtend()) {
793 // ... we zoom to it's bounding box
794 recalculateCenterScale(bbox);
795 return true;
796 }
[1169]797 return false;
798 }
[1023]799
[1169]800 public boolean addTemporaryLayer(MapViewPaintable mvp) {
801 if (temporaryLayers.contains(mvp)) return false;
802 return temporaryLayers.add(mvp);
803 }
[1023]804
[1169]805 public boolean removeTemporaryLayer(MapViewPaintable mvp) {
806 return temporaryLayers.remove(mvp);
807 }
[1890]808
809 public void propertyChange(PropertyChangeEvent evt) {
810 if (evt.getPropertyName().equals(Layer.VISIBLE_PROP)) {
811 repaint();
[3705]812 } else if (evt.getPropertyName().equals(Layer.OPACITY_PROP)) {
813 Layer l = (Layer)evt.getSource();
814 if (l.isVisible()) {
815 changedLayer = l;
816 repaint();
817 }
[2025]818 } else if (evt.getPropertyName().equals(OsmDataLayer.REQUIRES_SAVE_TO_DISK_PROP)
819 || evt.getPropertyName().equals(OsmDataLayer.REQUIRES_UPLOAD_TO_SERVER_PROP)) {
820 OsmDataLayer layer = (OsmDataLayer)evt.getSource();
821 if (layer == getEditLayer()) {
[2652]822 refreshTitle();
[2025]823 }
[1890]824 }
825 }
[2025]826
[2652]827 protected void refreshTitle() {
828 boolean dirty = editLayer != null && (editLayer.requiresSaveToFile() || editLayer.requiresUploadToServer());
[2025]829 if (dirty) {
830 JOptionPane.getFrameForComponent(Main.parent).setTitle("* " + tr("Java OpenStreetMap Editor"));
831 } else {
832 JOptionPane.getFrameForComponent(Main.parent).setTitle(tr("Java OpenStreetMap Editor"));
833 }
834 }
[2621]835
[3128]836 public void preferenceChanged(PreferenceChangeEvent e) {
837 paintPreferencesChanged = true;
838 }
839
[3443]840 private SelectionChangedListener repaintSelectionChangedListener = new SelectionChangedListener(){
841 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
842 repaint();
843 }
844 };
845
846 public void destroy() {
847 Main.pref.removePreferenceChangeListener(this);
848 DataSet.removeSelectionListener(repaintSelectionChangedListener);
[4623]849 MultipolygonCache.getInstance().clear(this);
[3443]850 }
851
[283]852}
Note: See TracBrowser for help on using the repository browser.