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

Last change on this file since 8372 was 8365, checked in by Don-vip, 9 years ago

fix Findbugs performance issues

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