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

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

applied #11632 - Synchronized access to temporary layers and added a getter. (based on patch by michael2402)

  • Property svn:eol-style set to native
File size: 42.5 KB
Line 
1// License: GPL. For details, see LICENSE file.
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.LinkedHashSet;
29import java.util.List;
30import java.util.Set;
31import java.util.concurrent.CopyOnWriteArrayList;
32import java.util.concurrent.locks.ReentrantReadWriteLock;
33
34import javax.swing.AbstractButton;
35import javax.swing.ActionMap;
36import javax.swing.InputMap;
37import javax.swing.JComponent;
38import javax.swing.JFrame;
39import javax.swing.JPanel;
40
41import org.openstreetmap.josm.Main;
42import org.openstreetmap.josm.actions.mapmode.MapMode;
43import org.openstreetmap.josm.data.Bounds;
44import org.openstreetmap.josm.data.Preferences.PreferenceChangeEvent;
45import org.openstreetmap.josm.data.Preferences.PreferenceChangedListener;
46import org.openstreetmap.josm.data.SelectionChangedListener;
47import org.openstreetmap.josm.data.ViewportData;
48import org.openstreetmap.josm.data.coor.EastNorth;
49import org.openstreetmap.josm.data.coor.LatLon;
50import org.openstreetmap.josm.data.imagery.ImageryInfo;
51import org.openstreetmap.josm.data.osm.DataSet;
52import org.openstreetmap.josm.data.osm.OsmPrimitive;
53import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
54import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache;
55import org.openstreetmap.josm.gui.layer.GpxLayer;
56import org.openstreetmap.josm.gui.layer.ImageryLayer;
57import org.openstreetmap.josm.gui.layer.Layer;
58import org.openstreetmap.josm.gui.layer.MapViewPaintable;
59import org.openstreetmap.josm.gui.layer.OsmDataLayer;
60import org.openstreetmap.josm.gui.layer.geoimage.GeoImageLayer;
61import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
62import org.openstreetmap.josm.gui.layer.markerlayer.PlayHeadMarker;
63import org.openstreetmap.josm.gui.util.GuiHelper;
64import org.openstreetmap.josm.tools.AudioPlayer;
65import org.openstreetmap.josm.tools.BugReportExceptionHandler;
66import org.openstreetmap.josm.tools.Shortcut;
67import org.openstreetmap.josm.tools.Utils;
68
69/**
70 * This is a component used in the {@link MapFrame} for browsing the map. It use is to
71 * provide the MapMode's enough capabilities to operate.<br><br>
72 *
73 * {@code MapView} holds meta-data about the data set currently displayed, as scale level,
74 * center point viewed, what scrolling mode or editing mode is selected or with
75 * what projection the map is viewed etc..<br><br>
76 *
77 * {@code MapView} is able to administrate several layers.
78 *
79 * @author imi
80 */
81public class MapView extends NavigatableComponent
82implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.LayerStateChangeListener {
83
84 /**
85 * Interface to notify listeners of a layer change.
86 * @author imi
87 */
88 public interface LayerChangeListener {
89
90 /**
91 * Notifies this listener that the active layer has changed.
92 * @param oldLayer The previous active layer
93 * @param newLayer The new activer layer
94 */
95 void activeLayerChange(Layer oldLayer, Layer newLayer);
96
97 /**
98 * Notifies this listener that a layer has been added.
99 * @param newLayer The new added layer
100 */
101 void layerAdded(Layer newLayer);
102
103 /**
104 * Notifies this listener that a layer has been removed.
105 * @param oldLayer The old removed layer
106 */
107 void layerRemoved(Layer oldLayer);
108 }
109
110 /**
111 * An interface that needs to be implemented in order to listen for changes to the active edit layer.
112 */
113 public interface EditLayerChangeListener {
114
115 /**
116 * Called after the active edit layer was changed.
117 * @param oldLayer The old edit layer
118 * @param newLayer The current (new) edit layer
119 */
120 void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer);
121 }
122
123 public boolean viewportFollowing = false;
124
125 /**
126 * the layer listeners
127 */
128 private static final CopyOnWriteArrayList<LayerChangeListener> layerChangeListeners = new CopyOnWriteArrayList<>();
129 private static final CopyOnWriteArrayList<EditLayerChangeListener> editLayerChangeListeners = new CopyOnWriteArrayList<>();
130
131 /**
132 * Removes a layer change listener
133 *
134 * @param listener the listener. Ignored if null or already registered.
135 */
136 public static void removeLayerChangeListener(LayerChangeListener listener) {
137 layerChangeListeners.remove(listener);
138 }
139
140 public static void removeEditLayerChangeListener(EditLayerChangeListener listener) {
141 editLayerChangeListeners.remove(listener);
142 }
143
144 /**
145 * Adds a layer change listener
146 *
147 * @param listener the listener. Ignored if null or already registered.
148 */
149 public static void addLayerChangeListener(LayerChangeListener listener) {
150 if (listener != null) {
151 layerChangeListeners.addIfAbsent(listener);
152 }
153 }
154
155 /**
156 * Adds a layer change listener
157 *
158 * @param listener the listener. Ignored if null or already registered.
159 * @param initialFire fire an active-layer-changed-event right after adding
160 * the listener in case there is a layer present (should be)
161 */
162 public static void addLayerChangeListener(LayerChangeListener listener, boolean initialFire) {
163 addLayerChangeListener(listener);
164 if (initialFire && Main.isDisplayingMapView()) {
165 listener.activeLayerChange(null, Main.map.mapView.getActiveLayer());
166 }
167 }
168
169 /**
170 * Adds an edit layer change listener
171 *
172 * @param listener the listener. Ignored if null or already registered.
173 * @param initialFire fire an edit-layer-changed-event right after adding
174 * the listener in case there is an edit layer present
175 */
176 public static void addEditLayerChangeListener(EditLayerChangeListener listener, boolean initialFire) {
177 addEditLayerChangeListener(listener);
178 if (initialFire && Main.isDisplayingMapView() && Main.map.mapView.getEditLayer() != null) {
179 listener.editLayerChanged(null, Main.map.mapView.getEditLayer());
180 }
181 }
182
183 /**
184 * Adds an edit layer change listener
185 *
186 * @param listener the listener. Ignored if null or already registered.
187 */
188 public static void addEditLayerChangeListener(EditLayerChangeListener listener) {
189 if (listener != null) {
190 editLayerChangeListeners.addIfAbsent(listener);
191 }
192 }
193
194 /**
195 * Calls the {@link LayerChangeListener#activeLayerChange(Layer, Layer)} method of all listeners.
196 *
197 * @param oldLayer The old layer
198 * @param newLayer The new active layer.
199 */
200 protected void fireActiveLayerChanged(Layer oldLayer, Layer newLayer) {
201 checkLayerLockNotHeld();
202 for (LayerChangeListener l : layerChangeListeners) {
203 l.activeLayerChange(oldLayer, newLayer);
204 }
205 }
206
207 protected void fireLayerAdded(Layer newLayer) {
208 checkLayerLockNotHeld();
209 for (MapView.LayerChangeListener l : MapView.layerChangeListeners) {
210 l.layerAdded(newLayer);
211 }
212 }
213
214 protected void fireLayerRemoved(Layer layer) {
215 checkLayerLockNotHeld();
216 for (MapView.LayerChangeListener l : MapView.layerChangeListeners) {
217 l.layerRemoved(layer);
218 }
219 }
220
221 protected void fireEditLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
222 checkLayerLockNotHeld();
223 for (EditLayerChangeListener l : editLayerChangeListeners) {
224 l.editLayerChanged(oldLayer, newLayer);
225 }
226 }
227
228 /**
229 * This is a simple invariant check that tests if the {@link #layerLock} is not write locked.
230 * This should be the case whenever a layer listener is invoked.
231 */
232 private void checkLayerLockNotHeld() {
233 if (layerLock.isWriteLockedByCurrentThread()) {
234 Main.warn("layerLock is write-held while a listener was called.");
235 }
236 }
237
238 /**
239 * A list of all layers currently loaded. Locked by {@link #layerLock}.
240 */
241 private final transient List<Layer> layers = new ArrayList<>();
242
243 /**
244 * This lock manages concurrent access to {@link #layers},
245 * {@link #editLayer} and {@link #activeLayer}.
246 * <p>
247 * The read lock is always held while those fields are read or while layer change listeners are fired.
248 */
249 private final ReentrantReadWriteLock layerLock = new ReentrantReadWriteLock();
250
251 /**
252 * The play head marker: there is only one of these so it isn't in any specific layer
253 */
254 public transient PlayHeadMarker playHeadMarker = null;
255
256 /**
257 * The layer from the layers list that is currently active. Locked by {@link #layerLock}.
258 */
259 private transient Layer activeLayer;
260
261 /**
262 * The edit layer is the current active data layer. Locked by {@link #layerLock}.
263 */
264 private transient OsmDataLayer editLayer;
265
266 /**
267 * The last event performed by mouse.
268 */
269 public MouseEvent lastMEvent = new MouseEvent(this, 0, 0, 0, 0, 0, 0, false); // In case somebody reads it before first mouse move
270
271 /**
272 * Temporary layers (selection rectangle, etc.) that are never cached and
273 * drawn on top of regular layers.
274 * Access must be synchronized.
275 */
276 private final transient Set<MapViewPaintable> temporaryLayers = new LinkedHashSet<>();
277
278 private transient BufferedImage nonChangedLayersBuffer;
279 private transient BufferedImage offscreenBuffer;
280 // Layers that wasn't changed since last paint
281 private final transient List<Layer> nonChangedLayers = new ArrayList<>();
282 private transient Layer changedLayer;
283 private int lastViewID;
284 private boolean paintPreferencesChanged = true;
285 private Rectangle lastClipBounds = new Rectangle();
286 private transient MapMover mapMover;
287
288 /**
289 * Constructs a new {@code MapView}.
290 * @param contentPane The content pane used to register shortcuts in its
291 * {@link InputMap} and {@link ActionMap}
292 * @param viewportData the initial viewport of the map. Can be null, then
293 * the viewport is derived from the layer data.
294 */
295 public MapView(final JPanel contentPane, final ViewportData viewportData) {
296 initialViewport = viewportData;
297 Main.pref.addPreferenceChangeListener(this);
298 final boolean unregisterTab = Shortcut.findShortcut(KeyEvent.VK_TAB, 0) != null;
299
300 addComponentListener(new ComponentAdapter() {
301 @Override public void componentResized(ComponentEvent e) {
302 removeComponentListener(this);
303
304 MapSlider zoomSlider = new MapSlider(MapView.this);
305 add(zoomSlider);
306 zoomSlider.setBounds(3, 0, 114, 30);
307 zoomSlider.setFocusTraversalKeysEnabled(!unregisterTab);
308
309 MapScaler scaler = new MapScaler(MapView.this);
310 add(scaler);
311 scaler.setLocation(10, 30);
312
313 mapMover = new MapMover(MapView.this, contentPane);
314 }
315 });
316
317 // listend to selection changes to redraw the map
318 DataSet.addSelectionListener(repaintSelectionChangedListener);
319
320 //store the last mouse action
321 this.addMouseMotionListener(new MouseMotionListener() {
322 @Override
323 public void mouseDragged(MouseEvent e) {
324 mouseMoved(e);
325 }
326
327 @Override
328 public void mouseMoved(MouseEvent e) {
329 lastMEvent = e;
330 }
331 });
332 this.addMouseListener(new MouseAdapter() {
333 @Override
334 public void mousePressed(MouseEvent me) {
335 // focus the MapView component when mouse is pressed inside it
336 requestFocus();
337 }
338 });
339
340 if (Shortcut.findShortcut(KeyEvent.VK_TAB, 0) != null) {
341 setFocusTraversalKeysEnabled(false);
342 }
343 }
344
345 // remebered geometry of the component
346 private Dimension oldSize = null;
347 private Point oldLoc = null;
348
349 /*
350 * Call this method to keep map position on screen during next repaint
351 */
352 public void rememberLastPositionOnScreen() {
353 oldSize = getSize();
354 oldLoc = getLocationOnScreen();
355 }
356
357 /**
358 * Adds a GPX layer. A GPX layer is added below the lowest data layer.
359 * <p>
360 * Does not call {@link #fireLayerAdded(Layer)}.
361 *
362 * @param layer the GPX layer
363 */
364 protected void addGpxLayer(GpxLayer layer) {
365 layerLock.writeLock().lock();
366 try {
367 if (layers.isEmpty()) {
368 layers.add(layer);
369 return;
370 }
371 for (int i = layers.size()-1; i >= 0; i--) {
372 if (layers.get(i) instanceof OsmDataLayer) {
373 if (i == layers.size()-1) {
374 layers.add(layer);
375 } else {
376 layers.add(i+1, layer);
377 }
378 return;
379 }
380 }
381 layers.add(0, layer);
382 } finally {
383 layerLock.writeLock().unlock();
384 }
385 }
386
387 /**
388 * Add a layer to the current MapView. The layer will be added at topmost
389 * position.
390 * @param layer The layer to add
391 */
392 public void addLayer(Layer layer) {
393 boolean isOsmDataLayer = layer instanceof OsmDataLayer;
394 layerLock.writeLock().lock();
395 layerLock.readLock().lock();
396 boolean fireSetActiveLayer = false;
397 Layer oldActiveLayer = activeLayer;
398 try {
399 try {
400 if (layer instanceof MarkerLayer && playHeadMarker == null) {
401 playHeadMarker = PlayHeadMarker.create();
402 }
403
404 if (layer instanceof GpxLayer) {
405 addGpxLayer((GpxLayer) layer);
406 } else if (layers.isEmpty()) {
407 layers.add(layer);
408 } else if (layer.isBackgroundLayer()) {
409 int i = 0;
410 for (; i < layers.size(); i++) {
411 if (layers.get(i).isBackgroundLayer()) {
412 break;
413 }
414 }
415 layers.add(i, layer);
416 } else {
417 layers.add(0, layer);
418 }
419
420 if (isOsmDataLayer || oldActiveLayer == null) {
421 // autoselect the new layer
422 fireSetActiveLayer = setActiveLayer(layer, true);
423 }
424 } finally {
425 layerLock.writeLock().unlock();
426 }
427
428 fireLayerAdded(layer);
429 if (isOsmDataLayer) {
430 ((OsmDataLayer) layer).addLayerStateChangeListener(this);
431 }
432 if (fireSetActiveLayer) {
433 onActiveLayerChanged(oldActiveLayer);
434 }
435 layer.addPropertyChangeListener(this);
436 Main.addProjectionChangeListener(layer);
437 AudioPlayer.reset();
438 } finally {
439 layerLock.readLock().unlock();
440 }
441 if (!fireSetActiveLayer) {
442 repaint();
443 }
444 }
445
446 @Override
447 protected DataSet getCurrentDataSet() {
448 layerLock.readLock().lock();
449 try {
450 if (editLayer != null)
451 return editLayer.data;
452 else
453 return null;
454 } finally {
455 layerLock.readLock().unlock();
456 }
457 }
458
459 /**
460 * Replies true if the active data layer (edit layer) is drawable.
461 *
462 * @return true if the active data layer (edit layer) is drawable, false otherwise
463 */
464 public boolean isActiveLayerDrawable() {
465 layerLock.readLock().lock();
466 try {
467 return editLayer != null;
468 } finally {
469 layerLock.readLock().unlock();
470 }
471 }
472
473 /**
474 * Replies true if the active data layer (edit layer) is visible.
475 *
476 * @return true if the active data layer (edit layer) is visible, false otherwise
477 */
478 public boolean isActiveLayerVisible() {
479 layerLock.readLock().lock();
480 try {
481 return isActiveLayerDrawable() && editLayer.isVisible();
482 } finally {
483 layerLock.readLock().unlock();
484 }
485 }
486
487 /**
488 * Determines the next active data layer according to the following
489 * rules:
490 * <ul>
491 * <li>if there is at least one {@link OsmDataLayer} the first one
492 * becomes active</li>
493 * <li>otherwise, the top most layer of any type becomes active</li>
494 * </ul>
495 *
496 * @return the next active data layer
497 */
498 protected Layer determineNextActiveLayer(List<Layer> layersList) {
499 // First look for data layer
500 for (Layer layer:layersList) {
501 if (layer instanceof OsmDataLayer)
502 return layer;
503 }
504
505 // Then any layer
506 if (!layersList.isEmpty())
507 return layersList.get(0);
508
509 // and then give up
510 return null;
511
512 }
513
514 /**
515 * Remove the layer from the mapview. If the layer was in the list before,
516 * an LayerChange event is fired.
517 * @param layer The layer to remove
518 */
519 public void removeLayer(Layer layer) {
520 boolean fireEditLayerChanged;
521 boolean fireSetActiveLayer = false;
522 layerLock.writeLock().lock();
523 layerLock.readLock().lock();
524
525 OsmDataLayer oldEditLayer = editLayer;
526 Layer oldActiveLayer = activeLayer;
527
528 try {
529 try {
530 List<Layer> layersList = new ArrayList<>(layers);
531
532 if (!layersList.remove(layer))
533 return;
534
535 fireEditLayerChanged = setEditLayer(layersList);
536
537 if (layer == activeLayer) {
538 fireSetActiveLayer = setActiveLayer(determineNextActiveLayer(layersList), false);
539 }
540
541 if (layer instanceof OsmDataLayer) {
542 ((OsmDataLayer) layer).removeLayerPropertyChangeListener(this);
543 }
544
545 layers.remove(layer);
546 Main.removeProjectionChangeListener(layer);
547
548 } finally {
549 layerLock.writeLock().unlock();
550 }
551 if (fireEditLayerChanged) {
552 onEditLayerChanged(oldEditLayer);
553 }
554 if (fireSetActiveLayer) {
555 onActiveLayerChanged(oldActiveLayer);
556 }
557 fireLayerRemoved(layer);
558 layer.removePropertyChangeListener(this);
559 layer.destroy();
560 AudioPlayer.reset();
561 } finally {
562 layerLock.readLock().unlock();
563 }
564 repaint();
565 }
566
567 private void onEditLayerChanged(OsmDataLayer oldEditLayer) {
568 fireEditLayerChanged(oldEditLayer, editLayer);
569 refreshTitle();
570 }
571
572 private boolean virtualNodesEnabled = false;
573
574 public void setVirtualNodesEnabled(boolean enabled) {
575 if (virtualNodesEnabled != enabled) {
576 virtualNodesEnabled = enabled;
577 repaint();
578 }
579 }
580
581 public boolean isVirtualNodesEnabled() {
582 return virtualNodesEnabled;
583 }
584
585 /**
586 * Moves the layer to the given new position. No event is fired, but repaints
587 * according to the new Z-Order of the layers.
588 *
589 * @param layer The layer to move
590 * @param pos The new position of the layer
591 */
592 public void moveLayer(Layer layer, int pos) {
593 layerLock.writeLock().lock();
594 layerLock.readLock().lock();
595 boolean fireEditLayerChanged;
596 OsmDataLayer oldEditLayer = editLayer;
597 try {
598 try {
599 int curLayerPos = layers.indexOf(layer);
600 if (curLayerPos == -1)
601 throw new IllegalArgumentException(tr("Layer not in list."));
602 if (pos == curLayerPos)
603 return; // already in place.
604 layers.remove(curLayerPos);
605 if (pos >= layers.size()) {
606 layers.add(layer);
607 } else {
608 layers.add(pos, layer);
609 }
610 fireEditLayerChanged = setEditLayer(layers);
611 } finally {
612 layerLock.writeLock().unlock();
613 }
614 if (fireEditLayerChanged) {
615 onEditLayerChanged(editLayer);
616 }
617 AudioPlayer.reset();
618 } finally {
619 layerLock.readLock().unlock();
620 }
621 repaint();
622 }
623
624 /**
625 * Gets the index of the layer in the layer list.
626 * @param layer The layer to search for.
627 * @return The index in the list.
628 * @throws IllegalArgumentException if that layer does not belong to this view.
629 */
630 public int getLayerPos(Layer layer) {
631 int curLayerPos;
632 layerLock.readLock().lock();
633 try {
634 curLayerPos = layers.indexOf(layer);
635 } finally {
636 layerLock.readLock().unlock();
637 }
638 if (curLayerPos == -1)
639 throw new IllegalArgumentException(tr("Layer not in list."));
640 return curLayerPos;
641 }
642
643 /**
644 * Creates a list of the visible layers in Z-Order, the layer with the lowest Z-Order
645 * first, layer with the highest Z-Order last.
646 *
647 * @return a list of the visible in Z-Order, the layer with the lowest Z-Order
648 * first, layer with the highest Z-Order last.
649 */
650 public List<Layer> getVisibleLayersInZOrder() {
651 layerLock.readLock().lock();
652 try {
653 List<Layer> ret = new ArrayList<>();
654 for (Layer l: layers) {
655 if (l.isVisible()) {
656 ret.add(l);
657 }
658 }
659 // sort according to position in the list of layers, with one exception:
660 // an active data layer always becomes a higher Z-Order than all other data layers
661 Collections.sort(
662 ret,
663 new Comparator<Layer>() {
664 @Override
665 public int compare(Layer l1, Layer l2) {
666 if (l1 instanceof OsmDataLayer && l2 instanceof OsmDataLayer) {
667 if (l1 == getActiveLayer()) return -1;
668 if (l2 == getActiveLayer()) return 1;
669 return Integer.compare(layers.indexOf(l1), layers.indexOf(l2));
670 } else
671 return Integer.compare(layers.indexOf(l1), layers.indexOf(l2));
672 }
673 }
674 );
675 Collections.reverse(ret);
676 return ret;
677 } finally {
678 layerLock.readLock().unlock();
679 }
680 }
681
682 private void paintLayer(Layer layer, Graphics2D g, Bounds box) {
683 if (layer.getOpacity() < 1) {
684 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) layer.getOpacity()));
685 }
686 layer.paint(g, this, box);
687 g.setPaintMode();
688 }
689
690 /**
691 * Draw the component.
692 */
693 @Override
694 public void paint(Graphics g) {
695 if (initialViewport != null) {
696 zoomTo(initialViewport);
697 initialViewport = null;
698 }
699 if (BugReportExceptionHandler.exceptionHandlingInProgress())
700 return;
701
702 if (center == null)
703 return; // no data loaded yet.
704
705 // if the position was remembered, we need to adjust center once before repainting
706 if (oldLoc != null && oldSize != null) {
707 Point l1 = getLocationOnScreen();
708 final EastNorth newCenter = new EastNorth(
709 center.getX()+ (l1.x-oldLoc.x - (oldSize.width-getWidth())/2.0)*getScale(),
710 center.getY()+ (oldLoc.y-l1.y + (oldSize.height-getHeight())/2.0)*getScale()
711 );
712 oldLoc = null; oldSize = null;
713 zoomTo(newCenter);
714 }
715
716 List<Layer> visibleLayers = getVisibleLayersInZOrder();
717
718 int nonChangedLayersCount = 0;
719 for (Layer l: visibleLayers) {
720 if (l.isChanged() || l == changedLayer) {
721 break;
722 } else {
723 nonChangedLayersCount++;
724 }
725 }
726
727 boolean canUseBuffer;
728
729 synchronized (this) {
730 canUseBuffer = !paintPreferencesChanged;
731 paintPreferencesChanged = false;
732 }
733 canUseBuffer = canUseBuffer && nonChangedLayers.size() <= nonChangedLayersCount &&
734 lastViewID == getViewID() && lastClipBounds.contains(g.getClipBounds());
735 if (canUseBuffer) {
736 for (int i = 0; i < nonChangedLayers.size(); i++) {
737 if (visibleLayers.get(i) != nonChangedLayers.get(i)) {
738 canUseBuffer = false;
739 break;
740 }
741 }
742 }
743
744 if (null == offscreenBuffer || offscreenBuffer.getWidth() != getWidth() || offscreenBuffer.getHeight() != getHeight()) {
745 offscreenBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_3BYTE_BGR);
746 }
747
748 Graphics2D tempG = offscreenBuffer.createGraphics();
749 tempG.setClip(g.getClip());
750 Bounds box = getLatLonBounds(g.getClipBounds());
751
752 if (!canUseBuffer || nonChangedLayersBuffer == null) {
753 if (null == nonChangedLayersBuffer
754 || nonChangedLayersBuffer.getWidth() != getWidth() || nonChangedLayersBuffer.getHeight() != getHeight()) {
755 nonChangedLayersBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_3BYTE_BGR);
756 }
757 Graphics2D g2 = nonChangedLayersBuffer.createGraphics();
758 g2.setClip(g.getClip());
759 g2.setColor(PaintColors.getBackgroundColor());
760 g2.fillRect(0, 0, getWidth(), getHeight());
761
762 for (int i = 0; i < nonChangedLayersCount; i++) {
763 paintLayer(visibleLayers.get(i), g2, box);
764 }
765 } else {
766 // Maybe there were more unchanged layers then last time - draw them to buffer
767 if (nonChangedLayers.size() != nonChangedLayersCount) {
768 Graphics2D g2 = nonChangedLayersBuffer.createGraphics();
769 g2.setClip(g.getClip());
770 for (int i = nonChangedLayers.size(); i < nonChangedLayersCount; i++) {
771 paintLayer(visibleLayers.get(i), g2, box);
772 }
773 }
774 }
775
776 nonChangedLayers.clear();
777 changedLayer = null;
778 for (int i = 0; i < nonChangedLayersCount; i++) {
779 nonChangedLayers.add(visibleLayers.get(i));
780 }
781 lastViewID = getViewID();
782 lastClipBounds = g.getClipBounds();
783
784 tempG.drawImage(nonChangedLayersBuffer, 0, 0, null);
785
786 for (int i = nonChangedLayersCount; i < visibleLayers.size(); i++) {
787 paintLayer(visibleLayers.get(i), tempG, box);
788 }
789
790 synchronized (temporaryLayers) {
791 for (MapViewPaintable mvp : temporaryLayers) {
792 mvp.paint(tempG, this, box);
793 }
794 }
795
796 // draw world borders
797 tempG.setColor(Color.WHITE);
798 Bounds b = getProjection().getWorldBoundsLatLon();
799 double lat = b.getMinLat();
800 double lon = b.getMinLon();
801
802 Point p = getPoint(b.getMin());
803
804 GeneralPath path = new GeneralPath();
805
806 path.moveTo(p.x, p.y);
807 double max = b.getMax().lat();
808 for (; lat <= max; lat += 1.0) {
809 p = getPoint(new LatLon(lat >= max ? max : lat, lon));
810 path.lineTo(p.x, p.y);
811 }
812 lat = max; max = b.getMax().lon();
813 for (; lon <= max; lon += 1.0) {
814 p = getPoint(new LatLon(lat, lon >= max ? max : lon));
815 path.lineTo(p.x, p.y);
816 }
817 lon = max; max = b.getMinLat();
818 for (; lat >= max; lat -= 1.0) {
819 p = getPoint(new LatLon(lat <= max ? max : lat, lon));
820 path.lineTo(p.x, p.y);
821 }
822 lat = max; max = b.getMinLon();
823 for (; lon >= max; lon -= 1.0) {
824 p = getPoint(new LatLon(lat, lon <= max ? max : lon));
825 path.lineTo(p.x, p.y);
826 }
827
828 int w = getWidth();
829 int h = getHeight();
830
831 // Work around OpenJDK having problems when drawing out of bounds
832 final Area border = new Area(path);
833 // Make the viewport 1px larger in every direction to prevent an
834 // additional 1px border when zooming in
835 final Area viewport = new Area(new Rectangle(-1, -1, w + 2, h + 2));
836 border.intersect(viewport);
837 tempG.draw(border);
838
839 if (Main.isDisplayingMapView() && Main.map.filterDialog != null) {
840 Main.map.filterDialog.drawOSDText(tempG);
841 }
842
843 if (playHeadMarker != null) {
844 playHeadMarker.paint(tempG, this);
845 }
846
847 g.drawImage(offscreenBuffer, 0, 0, null);
848 super.paint(g);
849 }
850
851 /**
852 * @return An unmodifiable collection of all layers
853 */
854 public Collection<Layer> getAllLayers() {
855 layerLock.readLock().lock();
856 try {
857 return Collections.unmodifiableCollection(new ArrayList<>(layers));
858 } finally {
859 layerLock.readLock().unlock();
860 }
861 }
862
863 /**
864 * @return An unmodifiable ordered list of all layers
865 */
866 public List<Layer> getAllLayersAsList() {
867 layerLock.readLock().lock();
868 try {
869 return Collections.unmodifiableList(new ArrayList<>(layers));
870 } finally {
871 layerLock.readLock().unlock();
872 }
873 }
874
875 /**
876 * Replies an unmodifiable list of layers of a certain type.
877 *
878 * Example:
879 * <pre>
880 * List&lt;WMSLayer&gt; wmsLayers = getLayersOfType(WMSLayer.class);
881 * </pre>
882 *
883 * @param ofType The layer type.
884 * @return an unmodifiable list of layers of a certain type.
885 */
886 public <T extends Layer> List<T> getLayersOfType(Class<T> ofType) {
887 return new ArrayList<>(Utils.filteredCollection(getAllLayers(), ofType));
888 }
889
890 /**
891 * Replies the number of layers managed by this map view
892 *
893 * @return the number of layers managed by this map view
894 */
895 public int getNumLayers() {
896 layerLock.readLock().lock();
897 try {
898 return layers.size();
899 } finally {
900 layerLock.readLock().unlock();
901 }
902 }
903
904 /**
905 * Replies true if there is at least one layer in this map view
906 *
907 * @return true if there is at least one layer in this map view
908 */
909 public boolean hasLayers() {
910 return getNumLayers() > 0;
911 }
912
913 /**
914 * Sets the active edit layer.
915 * <p>
916 * You must own a write {@link #layerLock} when calling this method.
917 * @param layersList A list to select that layer from.
918 * @return <code>true</code> if the edit layer was really changed and the listeners should be informed.
919 */
920 private boolean setEditLayer(List<Layer> layersList) {
921 final OsmDataLayer newEditLayer = findNewEditLayer(layersList);
922
923 // Set new edit layer
924 if (newEditLayer != editLayer) {
925 if (newEditLayer == null) {
926 // Note: Unsafe to call while layer write lock is held.
927 getCurrentDataSet().setSelected();
928 }
929
930 editLayer = newEditLayer;
931 return true;
932 } else {
933 return false;
934 }
935
936 }
937
938 private OsmDataLayer findNewEditLayer(List<Layer> layersList) {
939 OsmDataLayer newEditLayer = layersList.contains(editLayer)?editLayer:null;
940 // Find new edit layer
941 if (activeLayer != editLayer || !layersList.contains(editLayer)) {
942 if (activeLayer instanceof OsmDataLayer && layersList.contains(activeLayer)) {
943 newEditLayer = (OsmDataLayer) activeLayer;
944 } else {
945 for (Layer layer:layersList) {
946 if (layer instanceof OsmDataLayer) {
947 newEditLayer = (OsmDataLayer) layer;
948 break;
949 }
950 }
951 }
952 }
953 return newEditLayer;
954 }
955
956 /**
957 * Sets the active layer to <code>layer</code>. If <code>layer</code> is an instance
958 * of {@link OsmDataLayer} also sets {@link #editLayer} to <code>layer</code>.
959 *
960 * @param layer the layer to be activate; must be one of the layers in the list of layers
961 * @throws IllegalArgumentException if layer is not in the lis of layers
962 */
963 public void setActiveLayer(Layer layer) {
964 layerLock.writeLock().lock();
965 layerLock.readLock().lock();
966 Layer oldActiveLayer = activeLayer;
967 try {
968 boolean fireSetActiveLayer;
969 try {
970 fireSetActiveLayer = setActiveLayer(layer, true);
971 } finally {
972 layerLock.writeLock().unlock();
973 }
974 if (fireSetActiveLayer) {
975 onActiveLayerChanged(oldActiveLayer);
976 }
977 } finally {
978 layerLock.readLock().unlock();
979 }
980 repaint();
981 }
982
983 /**
984 * Sets the active layer. Propagates this change to all map buttons.
985 * @param layer The layer to be active.
986 * @param setEditLayer if this is <code>true</code>, the edit layer is also set.
987 * @return
988 */
989 private boolean setActiveLayer(final Layer layer, boolean setEditLayer) {
990 if (layer != null && !layers.contains(layer))
991 throw new IllegalArgumentException(tr("Layer ''{0}'' must be in list of layers", layer.toString()));
992
993 if (layer == activeLayer)
994 return false;
995
996 Layer old = activeLayer;
997 activeLayer = layer;
998 if (setEditLayer) {
999 setEditLayer(layers);
1000 }
1001
1002 return true;
1003 }
1004
1005 /**
1006 * Replies the currently active layer
1007 *
1008 * @return the currently active layer (may be null)
1009 */
1010 public Layer getActiveLayer() {
1011 layerLock.readLock().lock();
1012 try {
1013 return activeLayer;
1014 } finally {
1015 layerLock.readLock().unlock();
1016 }
1017 }
1018
1019 private void onActiveLayerChanged(final Layer old) {
1020 fireActiveLayerChanged(old, activeLayer);
1021
1022 /* This only makes the buttons look disabled. Disabling the actions as well requires
1023 * the user to re-select the tool after i.e. moving a layer. While testing I found
1024 * that I switch layers and actions at the same time and it was annoying to mind the
1025 * order. This way it works as visual clue for new users */
1026 for (final AbstractButton b: Main.map.allMapModeButtons) {
1027 MapMode mode = (MapMode)b.getAction();
1028 final boolean activeLayerSupported = mode.layerIsSupported(activeLayer);
1029 if (activeLayerSupported) {
1030 Main.registerActionShortcut(mode, mode.getShortcut()); //fix #6876
1031 } else {
1032 Main.unregisterShortcut(mode.getShortcut());
1033 }
1034 GuiHelper.runInEDTAndWait(new Runnable() {
1035 @Override public void run() {
1036 b.setEnabled(activeLayerSupported);
1037 }
1038 });
1039 }
1040 AudioPlayer.reset();
1041 repaint();
1042 }
1043
1044 /**
1045 * Replies the current edit layer, if any
1046 *
1047 * @return the current edit layer. May be null.
1048 */
1049 public OsmDataLayer getEditLayer() {
1050 layerLock.readLock().lock();
1051 try {
1052 return editLayer;
1053 } finally {
1054 layerLock.readLock().unlock();
1055 }
1056 }
1057
1058 /**
1059 * replies true if the list of layers managed by this map view contain layer
1060 *
1061 * @param layer the layer
1062 * @return true if the list of layers managed by this map view contain layer
1063 */
1064 public boolean hasLayer(Layer layer) {
1065 layerLock.readLock().lock();
1066 try {
1067 return layers.contains(layer);
1068 } finally {
1069 layerLock.readLock().unlock();
1070 }
1071 }
1072
1073 /**
1074 * Adds a new temporary layer.
1075 * <p>
1076 * A temporary layer is a layer that is painted above all normal layers. Layers are painted in the order they are added.
1077 *
1078 * @param mvp The layer to paint.
1079 * @return <code>true</code> if the layer was added.
1080 */
1081 public boolean addTemporaryLayer(MapViewPaintable mvp) {
1082 synchronized (temporaryLayers) {
1083 return temporaryLayers.add(mvp);
1084 }
1085 }
1086
1087 /**
1088 * Removes a layer previously added as temporary layer.
1089 * @param mvp The layer to remove.
1090 * @return <code>true</code> if that layer was removed.
1091 */
1092 public boolean removeTemporaryLayer(MapViewPaintable mvp) {
1093 synchronized (temporaryLayers) {
1094 return temporaryLayers.remove(mvp);
1095 }
1096 }
1097
1098 /**
1099 * Gets a list of temporary layers.
1100 * @return The layers in the order they are added.
1101 */
1102 public List<MapViewPaintable> getTemporaryLayers() {
1103 synchronized (temporaryLayers) {
1104 return Collections.unmodifiableList(new ArrayList<>(temporaryLayers));
1105 }
1106 }
1107
1108 @Override
1109 public void propertyChange(PropertyChangeEvent evt) {
1110 if (evt.getPropertyName().equals(Layer.VISIBLE_PROP)) {
1111 repaint();
1112 } else if (evt.getPropertyName().equals(Layer.OPACITY_PROP)) {
1113 Layer l = (Layer) evt.getSource();
1114 if (l.isVisible()) {
1115 changedLayer = l;
1116 repaint();
1117 }
1118 } else if (evt.getPropertyName().equals(OsmDataLayer.REQUIRES_SAVE_TO_DISK_PROP)
1119 || evt.getPropertyName().equals(OsmDataLayer.REQUIRES_UPLOAD_TO_SERVER_PROP)) {
1120 OsmDataLayer layer = (OsmDataLayer) evt.getSource();
1121 if (layer == getEditLayer()) {
1122 refreshTitle();
1123 }
1124 }
1125 }
1126
1127 /**
1128 * Sets the title of the JOSM main window, adding a star if there are dirty layers.
1129 * @see Main#parent
1130 */
1131 protected void refreshTitle() {
1132 if (Main.parent != null) {
1133 layerLock.readLock().lock();
1134 try {
1135 boolean dirty = editLayer != null &&
1136 (editLayer.requiresSaveToFile() || (editLayer.requiresUploadToServer() && !editLayer.isUploadDiscouraged()));
1137 ((JFrame) Main.parent).setTitle((dirty ? "* " : "") + tr("Java OpenStreetMap Editor"));
1138 ((JFrame) Main.parent).getRootPane().putClientProperty("Window.documentModified", dirty);
1139 } finally {
1140 layerLock.readLock().unlock();
1141 }
1142 }
1143 }
1144
1145 @Override
1146 public void preferenceChanged(PreferenceChangeEvent e) {
1147 synchronized (this) {
1148 paintPreferencesChanged = true;
1149 }
1150 }
1151
1152 private transient SelectionChangedListener repaintSelectionChangedListener = new SelectionChangedListener() {
1153 @Override
1154 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
1155 repaint();
1156 }
1157 };
1158
1159 public void destroy() {
1160 Main.pref.removePreferenceChangeListener(this);
1161 DataSet.removeSelectionListener(repaintSelectionChangedListener);
1162 MultipolygonCache.getInstance().clear(this);
1163 if (mapMover != null) {
1164 mapMover.destroy();
1165 }
1166 layerLock.writeLock().lock();
1167 try {
1168 activeLayer = null;
1169 changedLayer = null;
1170 editLayer = null;
1171 layers.clear();
1172 } finally {
1173 layerLock.writeLock().unlock();
1174 }
1175 nonChangedLayers.clear();
1176 synchronized (temporaryLayers) {
1177 temporaryLayers.clear();
1178 }
1179 }
1180
1181 @Override
1182 public void uploadDiscouragedChanged(OsmDataLayer layer, boolean newValue) {
1183 if (layer == getEditLayer()) {
1184 refreshTitle();
1185 }
1186 }
1187
1188 /**
1189 * Get a string representation of all layers suitable for the {@code source} changeset tag.
1190 * @return A String of sources separated by ';'
1191 */
1192 public String getLayerInformationForSourceTag() {
1193 final Collection<String> layerInfo = new ArrayList<>();
1194 if (!getLayersOfType(GpxLayer.class).isEmpty()) {
1195 // no i18n for international values
1196 layerInfo.add("survey");
1197 }
1198 for (final GeoImageLayer i : getLayersOfType(GeoImageLayer.class)) {
1199 layerInfo.add(i.getName());
1200 }
1201 for (final ImageryLayer i : getLayersOfType(ImageryLayer.class)) {
1202 layerInfo.add(ImageryInfo.ImageryType.BING.equals(i.getInfo().getImageryType()) ? "Bing" : i.getName());
1203 }
1204 return Utils.join("; ", layerInfo);
1205 }
1206
1207 /**
1208 * This is a listener that gets informed whenever repaint is called for this MapView.
1209 * <p>
1210 * This is the only safe method to find changes to the map view, since many components call MapView.repaint() directly.
1211 * @author Michael Zangl
1212 */
1213 public interface RepaintListener {
1214 /**
1215 * Called when any repaint method is called (using default arguments if required).
1216 * @param tm see {@link JComponent#repaint(long, int, int, int, int)}
1217 * @param x see {@link JComponent#repaint(long, int, int, int, int)}
1218 * @param y see {@link JComponent#repaint(long, int, int, int, int)}
1219 * @param width see {@link JComponent#repaint(long, int, int, int, int)}
1220 * @param height see {@link JComponent#repaint(long, int, int, int, int)}
1221 */
1222 void repaint(long tm, int x, int y, int width, int height);
1223 }
1224
1225 private final CopyOnWriteArrayList<RepaintListener> repaintListeners = new CopyOnWriteArrayList<>();
1226
1227 /**
1228 * Adds a listener that gets informed whenever repaint() is called for this class.
1229 * @param l The listener.
1230 */
1231 public void addRepaintListener(RepaintListener l) {
1232 repaintListeners.add(l);
1233 }
1234
1235 /**
1236 * Removes a registered repaint listener.
1237 * @param l The listener.
1238 */
1239 public void removeRepaintListener(RepaintListener l) {
1240 repaintListeners.remove(l);
1241 }
1242
1243 @Override
1244 public void repaint(long tm, int x, int y, int width, int height) {
1245 // This is the main repaint method, all other methods are convenience methods and simply call this method. This is just an observation, not a must, but seems to be true for all implementations I found so far.
1246 if (repaintListeners != null) {
1247 // Might get called early in super constructor
1248 for (RepaintListener l : repaintListeners) {
1249 l.repaint(tm, x, y, width, height);
1250 }
1251 }
1252 super.repaint(tm, x, y, width, height);
1253 }
1254}
Note: See TracBrowser for help on using the repository browser.