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

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

fix #13303 - Fixes for hatched texture (modified patch by michael2402) - gsoc-core

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