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

Last change on this file since 7882 was 7817, checked in by bastiK, 9 years ago

fixed #10861 - Zoom to layer when opening new file (see #10860)

  • Property svn:eol-style set to native
File size: 31.7 KB
Line 
1// License: GPL. See LICENSE file for details.
2package org.openstreetmap.josm.gui;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.AlphaComposite;
7import java.awt.Color;
8import java.awt.Dimension;
9import java.awt.Graphics;
10import java.awt.Graphics2D;
11import java.awt.Point;
12import java.awt.Rectangle;
13import java.awt.event.ComponentAdapter;
14import java.awt.event.ComponentEvent;
15import java.awt.event.KeyEvent;
16import java.awt.event.MouseAdapter;
17import java.awt.event.MouseEvent;
18import java.awt.event.MouseMotionListener;
19import java.awt.geom.Area;
20import java.awt.geom.GeneralPath;
21import java.awt.image.BufferedImage;
22import java.beans.PropertyChangeEvent;
23import java.beans.PropertyChangeListener;
24import java.util.ArrayList;
25import java.util.Collection;
26import java.util.Collections;
27import java.util.Comparator;
28import java.util.LinkedList;
29import java.util.List;
30import java.util.concurrent.CopyOnWriteArrayList;
31
32import javax.swing.AbstractButton;
33import javax.swing.ActionMap;
34import javax.swing.InputMap;
35import javax.swing.JFrame;
36import javax.swing.JPanel;
37
38import org.openstreetmap.josm.Main;
39import org.openstreetmap.josm.actions.mapmode.MapMode;
40import org.openstreetmap.josm.data.Bounds;
41import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent;
42import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
43import org.openstreetmap.josm.data.SelectionChangedListener;
44import org.openstreetmap.josm.data.ViewportData;
45import org.openstreetmap.josm.data.coor.EastNorth;
46import org.openstreetmap.josm.data.coor.LatLon;
47import org.openstreetmap.josm.data.imagery.ImageryInfo;
48import org.openstreetmap.josm.data.osm.DataSet;
49import org.openstreetmap.josm.data.osm.OsmPrimitive;
50import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
51import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
52import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache;
53import org.openstreetmap.josm.gui.layer.GpxLayer;
54import org.openstreetmap.josm.gui.layer.ImageryLayer;
55import org.openstreetmap.josm.gui.layer.Layer;
56import org.openstreetmap.josm.gui.layer.MapViewPaintable;
57import org.openstreetmap.josm.gui.layer.OsmDataLayer;
58import org.openstreetmap.josm.gui.layer.geoimage.GeoImageLayer;
59import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
60import org.openstreetmap.josm.gui.layer.markerlayer.PlayHeadMarker;
61import org.openstreetmap.josm.gui.util.GuiHelper;
62import org.openstreetmap.josm.tools.AudioPlayer;
63import org.openstreetmap.josm.tools.BugReportExceptionHandler;
64import org.openstreetmap.josm.tools.Shortcut;
65import org.openstreetmap.josm.tools.Utils;
66
67/**
68 * This is a component used in the {@link MapFrame} for browsing the map. It use is to
69 * provide the MapMode's enough capabilities to operate.<br><br>
70 *
71 * {@code MapView} holds meta-data about the data set currently displayed, as scale level,
72 * center point viewed, what scrolling mode or editing mode is selected or with
73 * what projection the map is viewed etc..<br><br>
74 *
75 * {@code MapView} is able to administrate several layers.
76 *
77 * @author imi
78 */
79public class MapView extends NavigatableComponent implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.LayerStateChangeListener {
80
81 /**
82 * Interface to notify listeners of a layer change.
83 * @author imi
84 */
85 public interface LayerChangeListener {
86
87 /**
88 * Notifies this listener that the active layer has changed.
89 * @param oldLayer The previous active layer
90 * @param newLayer The new activer layer
91 */
92 void activeLayerChange(Layer oldLayer, Layer newLayer);
93
94 /**
95 * Notifies this listener that a layer has been added.
96 * @param newLayer The new added layer
97 */
98 void layerAdded(Layer newLayer);
99
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&lt;WMSLayer&gt; 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}
Note: See TracBrowser for help on using the repository browser.