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

Last change on this file since 10364 was 10364, checked in by stoecker, 8 years ago

gsoc-core - patch by Michael Zangl - see #12953 - remove deprecation usage

  • Property svn:eol-style set to native
File size: 44.9 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 addLayerChangeListener(listener, false);
378 }
379
380 /**
381 * Adds a layer change listener
382 * <p>
383 * To be removed: end of 2016.
384 *
385 * @param listener the listener. Ignored if null or already registered.
386 * @param initialFire fire an active-layer-changed-event right after adding
387 * the listener in case there is a layer present (should be)
388 * @deprecated You should register the listener on {@link Main#getLayerManager()} instead.
389 */
390 @Deprecated
391 public static void addLayerChangeListener(LayerChangeListener listener, boolean initialFire) {
392 if (listener != null) {
393 initialFire = initialFire && Main.isDisplayingMapView();
394
395 LayerChangeAdapter adapter = new LayerChangeAdapter(listener, initialFire);
396 Main.getLayerManager().addLayerChangeListener(adapter, false);
397 if (initialFire) {
398 Main.getLayerManager().addAndFireActiveLayerChangeListener(adapter);
399 } else {
400 Main.getLayerManager().addActiveLayerChangeListener(adapter);
401 }
402 adapter.receiveOneInitialFire = false;
403 }
404 }
405
406 /**
407 * Adds an edit layer change listener
408 * <p>
409 * To be removed: end of 2016.
410 *
411 * @param listener the listener. Ignored if null or already registered.
412 * @param initialFire fire an edit-layer-changed-event right after adding
413 * the listener in case there is an edit layer present
414 * @deprecated You should register the listener on {@link Main#getLayerManager()} instead.
415 */
416 @Deprecated
417 public static void addEditLayerChangeListener(EditLayerChangeListener listener, boolean initialFire) {
418 if (listener != null) {
419 boolean doFire = initialFire && Main.isDisplayingMapView() && Main.getLayerManager().getEditLayer() != null;
420 if (doFire) {
421 Main.getLayerManager().addAndFireActiveLayerChangeListener(new EditLayerChangeAdapter(listener));
422 } else {
423 Main.getLayerManager().addActiveLayerChangeListener(new EditLayerChangeAdapter(listener));
424 }
425 }
426 }
427
428 /**
429 * Adds an edit layer change listener
430 * <p>
431 * To be removed: end of 2016.
432 *
433 * @param listener the listener. Ignored if null or already registered.
434 * @deprecated You should register the listener on {@link Main#getLayerManager()} instead.
435 */
436 @Deprecated
437 public static void addEditLayerChangeListener(EditLayerChangeListener listener) {
438 addEditLayerChangeListener(listener, false);
439 }
440
441 public boolean viewportFollowing;
442
443 /**
444 * A list of all layers currently loaded. If we support multiple map views, this list may be different for each of them.
445 */
446 private final MainLayerManager layerManager;
447
448 /**
449 * The play head marker: there is only one of these so it isn't in any specific layer
450 */
451 public transient PlayHeadMarker playHeadMarker;
452
453 /**
454 * The last event performed by mouse.
455 */
456 public MouseEvent lastMEvent = new MouseEvent(this, 0, 0, 0, 0, 0, 0, false); // In case somebody reads it before first mouse move
457
458 /**
459 * Temporary layers (selection rectangle, etc.) that are never cached and
460 * drawn on top of regular layers.
461 * Access must be synchronized.
462 */
463 private final transient Set<MapViewPaintable> temporaryLayers = new LinkedHashSet<>();
464
465 private transient BufferedImage nonChangedLayersBuffer;
466 private transient BufferedImage offscreenBuffer;
467 // Layers that wasn't changed since last paint
468 private final transient List<Layer> nonChangedLayers = new ArrayList<>();
469 private transient Layer changedLayer;
470 private int lastViewID;
471 private boolean paintPreferencesChanged = true;
472 private Rectangle lastClipBounds = new Rectangle();
473 private transient MapMover mapMover;
474
475 /**
476 * The listener that listens to invalidations of all layers.
477 */
478 private final LayerInvalidatedListener invalidatedListener = new LayerInvalidatedListener();
479
480 /**
481 * Constructs a new {@code MapView}.
482 * @param layerManager The layers to display.
483 * @param contentPane The content pane used to register shortcuts in its
484 * {@link InputMap} and {@link ActionMap}
485 * @param viewportData the initial viewport of the map. Can be null, then
486 * the viewport is derived from the layer data.
487 * @since 10279
488 */
489 public MapView(MainLayerManager layerManager, final JPanel contentPane, final ViewportData viewportData) {
490 this.layerManager = layerManager;
491 initialViewport = viewportData;
492 layerManager.addLayerChangeListener(this);
493 layerManager.addActiveLayerChangeListener(this);
494 Main.pref.addPreferenceChangeListener(this);
495
496 addComponentListener(new ComponentAdapter() {
497 @Override
498 public void componentResized(ComponentEvent e) {
499 removeComponentListener(this);
500
501 mapMover = new MapMover(MapView.this, contentPane);
502 }
503 });
504
505 // listend to selection changes to redraw the map
506 DataSet.addSelectionListener(repaintSelectionChangedListener);
507
508 //store the last mouse action
509 this.addMouseMotionListener(new MouseMotionListener() {
510 @Override
511 public void mouseDragged(MouseEvent e) {
512 mouseMoved(e);
513 }
514
515 @Override
516 public void mouseMoved(MouseEvent e) {
517 lastMEvent = e;
518 }
519 });
520 this.addMouseListener(new MouseAdapter() {
521 @Override
522 public void mousePressed(MouseEvent me) {
523 // focus the MapView component when mouse is pressed inside it
524 requestFocus();
525 }
526 });
527
528 if (Shortcut.findShortcut(KeyEvent.VK_TAB, 0) != null) {
529 setFocusTraversalKeysEnabled(false);
530 }
531
532 for (JComponent c : getMapNavigationComponents(MapView.this)) {
533 add(c);
534 }
535 }
536
537 /**
538 * Adds the map navigation components to a
539 * @param forMapView The map view to get the components for.
540 * @return A list containing the correctly positioned map navigation components.
541 */
542 public static List<? extends JComponent> getMapNavigationComponents(MapView forMapView) {
543 MapSlider zoomSlider = new MapSlider(forMapView);
544 Dimension size = zoomSlider.getPreferredSize();
545 zoomSlider.setSize(size);
546 zoomSlider.setLocation(3, 0);
547 zoomSlider.setFocusTraversalKeysEnabled(Shortcut.findShortcut(KeyEvent.VK_TAB, 0) == null);
548
549 MapScaler scaler = new MapScaler(forMapView);
550 scaler.setPreferredLineLength(size.width - 10);
551 scaler.setSize(scaler.getPreferredSize());
552 scaler.setLocation(3, size.height);
553
554 return Arrays.asList(zoomSlider, scaler);
555 }
556
557 // remebered geometry of the component
558 private Dimension oldSize;
559 private Point oldLoc;
560
561 /**
562 * Call this method to keep map position on screen during next repaint
563 */
564 public void rememberLastPositionOnScreen() {
565 oldSize = getSize();
566 oldLoc = getLocationOnScreen();
567 }
568
569 /**
570 * Add a layer to the current MapView.
571 * <p>
572 * To be removed: end of 2016.
573 * @param layer The layer to add
574 * @deprecated Use {@link Main#getLayerManager()}.addLayer() instead.
575 */
576 @Deprecated
577 public void addLayer(Layer layer) {
578 layerManager.addLayer(layer);
579 }
580
581 @Override
582 public void layerAdded(LayerAddEvent e) {
583 Layer layer = e.getAddedLayer();
584 if (layer instanceof MarkerLayer && playHeadMarker == null) {
585 playHeadMarker = PlayHeadMarker.create();
586 }
587
588 layer.addPropertyChangeListener(this);
589 Main.addProjectionChangeListener(layer);
590 invalidatedListener.addTo(layer);
591 AudioPlayer.reset();
592
593 repaint();
594 }
595
596 /**
597 * Returns current data set. To be removed: end of 2016.
598 * @deprecated Use {@link Main#getLayerManager()} instead.
599 */
600 @Override
601 @Deprecated
602 protected DataSet getCurrentDataSet() {
603 return layerManager.getEditDataSet();
604 }
605
606 /**
607 * Replies true if the active data layer (edit layer) is drawable.
608 *
609 * @return true if the active data layer (edit layer) is drawable, false otherwise
610 */
611 public boolean isActiveLayerDrawable() {
612 return getEditLayer() != null;
613 }
614
615 /**
616 * Replies true if the active data layer (edit layer) is visible.
617 *
618 * @return true if the active data layer (edit layer) is visible, false otherwise
619 */
620 public boolean isActiveLayerVisible() {
621 OsmDataLayer e = getEditLayer();
622 return e != null && e.isVisible();
623 }
624
625 /**
626 * Determines the next active data layer according to the following rules:
627 * <ul>
628 * <li>if there is at least one {@link OsmDataLayer} the first one
629 * becomes active</li>
630 * <li>otherwise, the top most layer of any type becomes active</li>
631 * </ul>
632 * To be removed: end of 2016.
633 * @param layersList lit of layers
634 *
635 * @return the next active data layer
636 * @deprecated now handled by {@link MainLayerManager}
637 */
638 @Deprecated
639 protected Layer determineNextActiveLayer(List<Layer> layersList) {
640 // First look for data layer
641 for (Layer layer:layersList) {
642 if (layer instanceof OsmDataLayer)
643 return layer;
644 }
645
646 // Then any layer
647 if (!layersList.isEmpty())
648 return layersList.get(0);
649
650 // and then give up
651 return null;
652 }
653
654 /**
655 * Remove the layer from the mapview. If the layer was in the list before,
656 * an LayerChange event is fired.
657 * <p>
658 * To be removed: end of 2016.
659 * @param layer The layer to remove
660 * @deprecated Use {@link Main#getLayerManager()}.removeLayer() instead.
661 */
662 @Deprecated
663 public void removeLayer(Layer layer) {
664 layerManager.removeLayer(layer);
665 }
666
667 @Override
668 public void layerRemoving(LayerRemoveEvent e) {
669 Layer layer = e.getRemovedLayer();
670
671 Main.removeProjectionChangeListener(layer);
672 layer.removePropertyChangeListener(this);
673 invalidatedListener.removeFrom(layer);
674 layer.destroy();
675 AudioPlayer.reset();
676
677 repaint();
678 }
679
680 private boolean virtualNodesEnabled;
681
682 public void setVirtualNodesEnabled(boolean enabled) {
683 if (virtualNodesEnabled != enabled) {
684 virtualNodesEnabled = enabled;
685 repaint();
686 }
687 }
688
689 /**
690 * Checks if virtual nodes should be drawn. Default is <code>false</code>
691 * @return The virtual nodes property.
692 * @see Rendering#render(DataSet, boolean, Bounds)
693 */
694 public boolean isVirtualNodesEnabled() {
695 return virtualNodesEnabled;
696 }
697
698 /**
699 * Moves the layer to the given new position. No event is fired, but repaints
700 * according to the new Z-Order of the layers.
701 *
702 * @param layer The layer to move
703 * @param pos The new position of the layer
704 */
705 public void moveLayer(Layer layer, int pos) {
706 layerManager.moveLayer(layer, pos);
707 }
708
709 @Override
710 public void layerOrderChanged(LayerOrderChangeEvent e) {
711 AudioPlayer.reset();
712 repaint();
713 }
714
715 /**
716 * Gets the index of the layer in the layer list.
717 * <p>
718 * To be removed: end of 2016.
719 * @param layer The layer to search for.
720 * @return The index in the list.
721 * @throws IllegalArgumentException if that layer does not belong to this view.
722 * @deprecated Access the layer list using {@link Main#getLayerManager()} instead.
723 */
724 @Deprecated
725 public int getLayerPos(Layer layer) {
726 int curLayerPos = layerManager.getLayers().indexOf(layer);
727 if (curLayerPos == -1)
728 throw new IllegalArgumentException(tr("Layer not in list."));
729 return curLayerPos;
730 }
731
732 /**
733 * Creates a list of the visible layers in Z-Order, the layer with the lowest Z-Order
734 * first, layer with the highest Z-Order last.
735 * <p>
736 * The active data layer is pulled above all adjacent data layers.
737 * <p>
738 * To be removed: end of 2016.
739 *
740 * @return a list of the visible in Z-Order, the layer with the lowest Z-Order
741 * first, layer with the highest Z-Order last.
742 * @deprecated Access the layer list using {@link Main#getLayerManager()} instead.
743 */
744 @Deprecated
745 public List<Layer> getVisibleLayersInZOrder() {
746 return layerManager.getVisibleLayersInZOrder();
747 }
748
749 private void paintLayer(Layer layer, Graphics2D g, Bounds box) {
750 try {
751 if (layer.getOpacity() < 1) {
752 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, (float) layer.getOpacity()));
753 }
754 layer.paint(g, this, box);
755 g.setPaintMode();
756 } catch (RuntimeException t) {
757 throw BugReport.intercept(t).put("layer", layer).put("bounds", box);
758 }
759 }
760
761 /**
762 * Draw the component.
763 */
764 @Override
765 public void paint(Graphics g) {
766 if (!prepareToDraw()) {
767 return;
768 }
769
770 List<Layer> visibleLayers = layerManager.getVisibleLayersInZOrder();
771
772 int nonChangedLayersCount = 0;
773 for (Layer l: visibleLayers) {
774 if (l.isChanged() || l == changedLayer) {
775 break;
776 } else {
777 nonChangedLayersCount++;
778 }
779 }
780
781 boolean canUseBuffer;
782
783 synchronized (this) {
784 canUseBuffer = !paintPreferencesChanged;
785 paintPreferencesChanged = false;
786 }
787 canUseBuffer = canUseBuffer && nonChangedLayers.size() <= nonChangedLayersCount &&
788 lastViewID == getViewID() && lastClipBounds.contains(g.getClipBounds());
789 if (canUseBuffer) {
790 for (int i = 0; i < nonChangedLayers.size(); i++) {
791 if (visibleLayers.get(i) != nonChangedLayers.get(i)) {
792 canUseBuffer = false;
793 break;
794 }
795 }
796 }
797
798 if (null == offscreenBuffer || offscreenBuffer.getWidth() != getWidth() || offscreenBuffer.getHeight() != getHeight()) {
799 offscreenBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_3BYTE_BGR);
800 }
801
802 Graphics2D tempG = offscreenBuffer.createGraphics();
803 tempG.setClip(g.getClip());
804 Bounds box = getLatLonBounds(g.getClipBounds());
805
806 if (!canUseBuffer || nonChangedLayersBuffer == null) {
807 if (null == nonChangedLayersBuffer
808 || nonChangedLayersBuffer.getWidth() != getWidth() || nonChangedLayersBuffer.getHeight() != getHeight()) {
809 nonChangedLayersBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_3BYTE_BGR);
810 }
811 Graphics2D g2 = nonChangedLayersBuffer.createGraphics();
812 g2.setClip(g.getClip());
813 g2.setColor(PaintColors.getBackgroundColor());
814 g2.fillRect(0, 0, getWidth(), getHeight());
815
816 for (int i = 0; i < nonChangedLayersCount; i++) {
817 paintLayer(visibleLayers.get(i), g2, box);
818 }
819 } else {
820 // Maybe there were more unchanged layers then last time - draw them to buffer
821 if (nonChangedLayers.size() != nonChangedLayersCount) {
822 Graphics2D g2 = nonChangedLayersBuffer.createGraphics();
823 g2.setClip(g.getClip());
824 for (int i = nonChangedLayers.size(); i < nonChangedLayersCount; i++) {
825 paintLayer(visibleLayers.get(i), g2, box);
826 }
827 }
828 }
829
830 nonChangedLayers.clear();
831 changedLayer = null;
832 for (int i = 0; i < nonChangedLayersCount; i++) {
833 nonChangedLayers.add(visibleLayers.get(i));
834 }
835 lastViewID = getViewID();
836 lastClipBounds = g.getClipBounds();
837
838 tempG.drawImage(nonChangedLayersBuffer, 0, 0, null);
839
840 for (int i = nonChangedLayersCount; i < visibleLayers.size(); i++) {
841 paintLayer(visibleLayers.get(i), tempG, box);
842 }
843
844 synchronized (temporaryLayers) {
845 for (MapViewPaintable mvp : temporaryLayers) {
846 mvp.paint(tempG, this, box);
847 }
848 }
849
850 // draw world borders
851 tempG.setColor(Color.WHITE);
852 Bounds b = getProjection().getWorldBoundsLatLon();
853 double lat = b.getMinLat();
854 double lon = b.getMinLon();
855
856 Point p = getPoint(b.getMin());
857
858 GeneralPath path = new GeneralPath();
859
860 double d = 1.0;
861 path.moveTo(p.x, p.y);
862 double max = b.getMax().lat();
863 for (; lat <= max; lat += d) {
864 p = getPoint(new LatLon(lat >= max ? max : lat, lon));
865 path.lineTo(p.x, p.y);
866 }
867 lat = max; max = b.getMax().lon();
868 for (; lon <= max; lon += d) {
869 p = getPoint(new LatLon(lat, lon >= max ? max : lon));
870 path.lineTo(p.x, p.y);
871 }
872 lon = max; max = b.getMinLat();
873 for (; lat >= max; lat -= d) {
874 p = getPoint(new LatLon(lat <= max ? max : lat, lon));
875 path.lineTo(p.x, p.y);
876 }
877 lat = max; max = b.getMinLon();
878 for (; lon >= max; lon -= d) {
879 p = getPoint(new LatLon(lat, lon <= max ? max : lon));
880 path.lineTo(p.x, p.y);
881 }
882
883 int w = getWidth();
884 int h = getHeight();
885
886 // Work around OpenJDK having problems when drawing out of bounds
887 final Area border = new Area(path);
888 // Make the viewport 1px larger in every direction to prevent an
889 // additional 1px border when zooming in
890 final Area viewport = new Area(new Rectangle(-1, -1, w + 2, h + 2));
891 border.intersect(viewport);
892 tempG.draw(border);
893
894 if (Main.isDisplayingMapView() && Main.map.filterDialog != null) {
895 Main.map.filterDialog.drawOSDText(tempG);
896 }
897
898 if (playHeadMarker != null) {
899 playHeadMarker.paint(tempG, this);
900 }
901
902 try {
903 g.drawImage(offscreenBuffer, 0, 0, null);
904 } catch (ClassCastException e) {
905 // See #11002 and duplicate tickets. On Linux with Java >= 8 Many users face this error here:
906 //
907 // java.lang.ClassCastException: sun.awt.image.BufImgSurfaceData cannot be cast to sun.java2d.xr.XRSurfaceData
908 // at sun.java2d.xr.XRPMBlitLoops.cacheToTmpSurface(XRPMBlitLoops.java:145)
909 // at sun.java2d.xr.XrSwToPMBlit.Blit(XRPMBlitLoops.java:353)
910 // at sun.java2d.pipe.DrawImage.blitSurfaceData(DrawImage.java:959)
911 // at sun.java2d.pipe.DrawImage.renderImageCopy(DrawImage.java:577)
912 // at sun.java2d.pipe.DrawImage.copyImage(DrawImage.java:67)
913 // at sun.java2d.pipe.DrawImage.copyImage(DrawImage.java:1014)
914 // at sun.java2d.pipe.ValidatePipe.copyImage(ValidatePipe.java:186)
915 // at sun.java2d.SunGraphics2D.drawImage(SunGraphics2D.java:3318)
916 // at sun.java2d.SunGraphics2D.drawImage(SunGraphics2D.java:3296)
917 // at org.openstreetmap.josm.gui.MapView.paint(MapView.java:834)
918 //
919 // It seems to be this JDK bug, but Oracle does not seem to be fixing it:
920 // https://bugs.openjdk.java.net/browse/JDK-7172749
921 //
922 // According to bug reports it can happen for a variety of reasons such as:
923 // - long period of time
924 // - change of screen resolution
925 // - addition/removal of a secondary monitor
926 //
927 // But the application seems to work fine after, so let's just log the error
928 Main.error(e);
929 }
930 super.paint(g);
931 }
932
933 /**
934 * Sets up the viewport to prepare for drawing the view.
935 * @return <code>true</code> if the view can be drawn, <code>false</code> otherwise.
936 */
937 public boolean prepareToDraw() {
938 if (initialViewport != null) {
939 zoomTo(initialViewport);
940 initialViewport = null;
941 }
942 if (BugReportExceptionHandler.exceptionHandlingInProgress())
943 return false;
944
945 if (getCenter() == null)
946 return false; // no data loaded yet.
947
948 // if the position was remembered, we need to adjust center once before repainting
949 if (oldLoc != null && oldSize != null) {
950 Point l1 = getLocationOnScreen();
951 final EastNorth newCenter = new EastNorth(
952 getCenter().getX()+ (l1.x-oldLoc.x - (oldSize.width-getWidth())/2.0)*getScale(),
953 getCenter().getY()+ (oldLoc.y-l1.y + (oldSize.height-getHeight())/2.0)*getScale()
954 );
955 oldLoc = null; oldSize = null;
956 zoomTo(newCenter);
957 }
958
959 return true;
960 }
961
962 /**
963 * Returns all layers. To be removed: end of 2016.
964 *
965 * @return An unmodifiable collection of all layers
966 * @deprecated Use {@link LayerManager#getLayers()} instead.
967 */
968 @Deprecated
969 public Collection<Layer> getAllLayers() {
970 return layerManager.getLayers();
971 }
972
973 /**
974 * Returns all layers as list. To be removed: end of 2016.
975 *
976 * @return An unmodifiable ordered list of all layers
977 * @deprecated Use {@link LayerManager#getLayers()} instead.
978 */
979 @Deprecated
980 public List<Layer> getAllLayersAsList() {
981 return layerManager.getLayers();
982 }
983
984 /**
985 * Replies an unmodifiable list of layers of a certain type. To be removed: end of 2016.
986 *
987 * Example:
988 * <pre>
989 * List&lt;WMSLayer&gt; wmsLayers = getLayersOfType(WMSLayer.class);
990 * </pre>
991 *
992 * @param <T> layer type
993 *
994 * @param ofType The layer type.
995 * @return an unmodifiable list of layers of a certain type.
996 * @deprecated Use {@link LayerManager#getLayersOfType(Class)} instead.
997 */
998 @Deprecated
999 public <T extends Layer> List<T> getLayersOfType(Class<T> ofType) {
1000 return layerManager.getLayersOfType(ofType);
1001 }
1002
1003 /**
1004 * Replies the number of layers managed by this map view. To be removed: end of 2016.
1005 * <p>
1006 *
1007 * @return the number of layers managed by this map view
1008 * @deprecated Use {@link Main#getLayerManager()}.getLayers().size() instead.
1009 */
1010 @Deprecated
1011 public int getNumLayers() {
1012 return getAllLayers().size();
1013 }
1014
1015 /**
1016 * Replies true if there is at least one layer in this map view
1017 * <p>
1018 *
1019 * @return true if there is at least one layer in this map view
1020 * @deprecated Use !{@link Main#getLayerManager()}.getLayers().isEmpty() instead.
1021 */
1022 @Deprecated
1023 public boolean hasLayers() {
1024 return getNumLayers() > 0;
1025 }
1026
1027 /**
1028 * Sets the active layer to <code>layer</code>. If <code>layer</code> is an instance
1029 * of {@link OsmDataLayer} also sets editLayer to <code>layer</code>.
1030 * <p>
1031 *
1032 * @param layer the layer to be activate; must be one of the layers in the list of layers
1033 * @throws IllegalArgumentException if layer is not in the list of layers
1034 * @deprecated Use !{@link Main#getLayerManager()}.setActiveLayer() instead.
1035 */
1036 @Deprecated
1037 public void setActiveLayer(Layer layer) {
1038 layerManager.setActiveLayer(layer);
1039 }
1040
1041 /**
1042 * Replies the currently active layer
1043 * <p>
1044 *
1045 * @return the currently active layer (may be null)
1046 * @deprecated Use !{@link Main#getLayerManager()}.getActiveLayer() instead.
1047 */
1048 @Deprecated
1049 public Layer getActiveLayer() {
1050 return layerManager.getActiveLayer();
1051 }
1052
1053 @Override
1054 public void activeOrEditLayerChanged(ActiveLayerChangeEvent e) {
1055 /* This only makes the buttons look disabled. Disabling the actions as well requires
1056 * the user to re-select the tool after i.e. moving a layer. While testing I found
1057 * that I switch layers and actions at the same time and it was annoying to mind the
1058 * order. This way it works as visual clue for new users */
1059 for (final AbstractButton b: Main.map.allMapModeButtons) {
1060 MapMode mode = (MapMode) b.getAction();
1061 final boolean activeLayerSupported = mode.layerIsSupported(layerManager.getActiveLayer());
1062 if (activeLayerSupported) {
1063 Main.registerActionShortcut(mode, mode.getShortcut()); //fix #6876
1064 } else {
1065 Main.unregisterShortcut(mode.getShortcut());
1066 }
1067 GuiHelper.runInEDTAndWait(new Runnable() {
1068 @Override public void run() {
1069 b.setEnabled(activeLayerSupported);
1070 }
1071 });
1072 }
1073 AudioPlayer.reset();
1074 repaint();
1075 }
1076
1077 /**
1078 * Replies the current edit layer, if any
1079 * <p>
1080 *
1081 * @return the current edit layer. May be null.
1082 * @deprecated Use !{@link Main#getLayerManager()}.getEditLayer() instead. To be made private: end of 2016.
1083 */
1084 @Deprecated
1085 public OsmDataLayer getEditLayer() {
1086 return layerManager.getEditLayer();
1087 }
1088
1089 /**
1090 * replies true if the list of layers managed by this map view contain layer
1091 * <p>
1092 *
1093 * @param layer the layer
1094 * @return true if the list of layers managed by this map view contain layer
1095 * @deprecated Use !{@link Main#getLayerManager()}.containsLayer() instead.
1096 */
1097 @Deprecated
1098 public boolean hasLayer(Layer layer) {
1099 return layerManager.containsLayer(layer);
1100 }
1101
1102 /**
1103 * Adds a new temporary layer.
1104 * <p>
1105 * A temporary layer is a layer that is painted above all normal layers. Layers are painted in the order they are added.
1106 *
1107 * @param mvp The layer to paint.
1108 * @return <code>true</code> if the layer was added.
1109 */
1110 public boolean addTemporaryLayer(MapViewPaintable mvp) {
1111 synchronized (temporaryLayers) {
1112 boolean added = temporaryLayers.add(mvp);
1113 if (added) {
1114 invalidatedListener.addTo(mvp);
1115 }
1116 return added;
1117 }
1118 }
1119
1120 /**
1121 * Removes a layer previously added as temporary layer.
1122 * @param mvp The layer to remove.
1123 * @return <code>true</code> if that layer was removed.
1124 */
1125 public boolean removeTemporaryLayer(MapViewPaintable mvp) {
1126 synchronized (temporaryLayers) {
1127 boolean removed = temporaryLayers.remove(mvp);
1128 if (removed) {
1129 invalidatedListener.removeFrom(mvp);
1130 }
1131 return removed;
1132 }
1133 }
1134
1135 /**
1136 * Gets a list of temporary layers.
1137 * @return The layers in the order they are added.
1138 */
1139 public List<MapViewPaintable> getTemporaryLayers() {
1140 synchronized (temporaryLayers) {
1141 return Collections.unmodifiableList(new ArrayList<>(temporaryLayers));
1142 }
1143 }
1144
1145 @Override
1146 public void propertyChange(PropertyChangeEvent evt) {
1147 if (evt.getPropertyName().equals(Layer.VISIBLE_PROP)) {
1148 repaint();
1149 } else if (evt.getPropertyName().equals(Layer.OPACITY_PROP) ||
1150 evt.getPropertyName().equals(Layer.FILTER_STATE_PROP)) {
1151 Layer l = (Layer) evt.getSource();
1152 if (l.isVisible()) {
1153 changedLayer = l;
1154 repaint();
1155 }
1156 }
1157 }
1158
1159 /**
1160 * Sets the title of the JOSM main window, adding a star if there are dirty layers.
1161 * @see Main#parent
1162 * @deprecated Replaced by {@link MainFrame#refreshTitle()}. The {@link MainFrame} should handle this by itself.
1163 */
1164 @Deprecated
1165 protected void refreshTitle() {
1166 if (Main.parent != null) {
1167 ((MainFrame) Main.parent).refreshTitle();
1168 }
1169 }
1170
1171 @Override
1172 public void preferenceChanged(PreferenceChangeEvent e) {
1173 synchronized (this) {
1174 paintPreferencesChanged = true;
1175 }
1176 }
1177
1178 private final transient SelectionChangedListener repaintSelectionChangedListener = new SelectionChangedListener() {
1179 @Override
1180 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
1181 repaint();
1182 }
1183 };
1184
1185 public void destroy() {
1186 layerManager.removeLayerChangeListener(this);
1187 layerManager.removeActiveLayerChangeListener(this);
1188 Main.pref.removePreferenceChangeListener(this);
1189 DataSet.removeSelectionListener(repaintSelectionChangedListener);
1190 MultipolygonCache.getInstance().clear(this);
1191 if (mapMover != null) {
1192 mapMover.destroy();
1193 }
1194 nonChangedLayers.clear();
1195 synchronized (temporaryLayers) {
1196 temporaryLayers.clear();
1197 }
1198 }
1199
1200 /**
1201 * Get a string representation of all layers suitable for the {@code source} changeset tag.
1202 * @return A String of sources separated by ';'
1203 */
1204 public String getLayerInformationForSourceTag() {
1205 final Collection<String> layerInfo = new ArrayList<>();
1206 if (!getLayersOfType(GpxLayer.class).isEmpty()) {
1207 // no i18n for international values
1208 layerInfo.add("survey");
1209 }
1210 for (final GeoImageLayer i : getLayersOfType(GeoImageLayer.class)) {
1211 if (i.isVisible()) {
1212 layerInfo.add(i.getName());
1213 }
1214 }
1215 for (final ImageryLayer i : getLayersOfType(ImageryLayer.class)) {
1216 if (i.isVisible()) {
1217 layerInfo.add(ImageryInfo.ImageryType.BING.equals(i.getInfo().getImageryType()) ? "Bing" : i.getName());
1218 }
1219 }
1220 return Utils.join("; ", layerInfo);
1221 }
1222
1223 /**
1224 * This is a listener that gets informed whenever repaint is called for this MapView.
1225 * <p>
1226 * This is the only safe method to find changes to the map view, since many components call MapView.repaint() directly.
1227 * @author Michael Zangl
1228 */
1229 public interface RepaintListener {
1230 /**
1231 * Called when any repaint method is called (using default arguments if required).
1232 * @param tm see {@link JComponent#repaint(long, int, int, int, int)}
1233 * @param x see {@link JComponent#repaint(long, int, int, int, int)}
1234 * @param y see {@link JComponent#repaint(long, int, int, int, int)}
1235 * @param width see {@link JComponent#repaint(long, int, int, int, int)}
1236 * @param height see {@link JComponent#repaint(long, int, int, int, int)}
1237 */
1238 void repaint(long tm, int x, int y, int width, int height);
1239 }
1240
1241 private final transient CopyOnWriteArrayList<RepaintListener> repaintListeners = new CopyOnWriteArrayList<>();
1242
1243 /**
1244 * Adds a listener that gets informed whenever repaint() is called for this class.
1245 * @param l The listener.
1246 */
1247 public void addRepaintListener(RepaintListener l) {
1248 repaintListeners.add(l);
1249 }
1250
1251 /**
1252 * Removes a registered repaint listener.
1253 * @param l The listener.
1254 */
1255 public void removeRepaintListener(RepaintListener l) {
1256 repaintListeners.remove(l);
1257 }
1258
1259 @Override
1260 public void repaint(long tm, int x, int y, int width, int height) {
1261 // This is the main repaint method, all other methods are convenience methods and simply call this method.
1262 // This is just an observation, not a must, but seems to be true for all implementations I found so far.
1263 if (repaintListeners != null) {
1264 // Might get called early in super constructor
1265 for (RepaintListener l : repaintListeners) {
1266 l.repaint(tm, x, y, width, height);
1267 }
1268 }
1269 super.repaint(tm, x, y, width, height);
1270 }
1271
1272 @Override
1273 public void repaint() {
1274 if (Main.isTraceEnabled()) {
1275 invalidatedListener.traceRandomRepaint();
1276 }
1277 super.repaint();
1278 }
1279
1280 /**
1281 * Returns the layer manager.
1282 * @return the layer manager
1283 * @since 10282
1284 */
1285 public final MainLayerManager getLayerManager() {
1286 return layerManager;
1287 }
1288}
Note: See TracBrowser for help on using the repository browser.