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

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

sonar - Immutable Field

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