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

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

fix #12981 - mimic the old behavior of the layer listeners (patch by michael2402) - gsoc-core

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