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

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

sonar - squid:S3052 - Fields should not be initialized to default values

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