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

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

javadoc fixes for jdk8 compatibility

  • Property svn:eol-style set to native
File size: 33.5 KB
Line 
1// License: GPL. See LICENSE file for details.
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.Collection;
26import java.util.Collections;
27import java.util.Comparator;
28import java.util.LinkedList;
29import java.util.List;
30import java.util.concurrent.CopyOnWriteArrayList;
31
32import javax.swing.AbstractButton;
33import javax.swing.ActionMap;
34import javax.swing.InputMap;
35import javax.swing.JOptionPane;
36import javax.swing.JPanel;
37
38import org.openstreetmap.josm.Main;
39import org.openstreetmap.josm.actions.AutoScaleAction;
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.coor.EastNorth;
46import org.openstreetmap.josm.data.coor.LatLon;
47import org.openstreetmap.josm.data.imagery.ImageryInfo;
48import org.openstreetmap.josm.data.osm.DataSet;
49import org.openstreetmap.josm.data.osm.DataSource;
50import org.openstreetmap.josm.data.osm.OsmPrimitive;
51import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
52import org.openstreetmap.josm.data.osm.visitor.paint.PaintColors;
53import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache;
54import org.openstreetmap.josm.gui.layer.GpxLayer;
55import org.openstreetmap.josm.gui.layer.ImageryLayer;
56import org.openstreetmap.josm.gui.layer.Layer;
57import org.openstreetmap.josm.gui.layer.MapViewPaintable;
58import org.openstreetmap.josm.gui.layer.OsmDataLayer;
59import org.openstreetmap.josm.gui.layer.geoimage.GeoImageLayer;
60import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
61import org.openstreetmap.josm.gui.layer.markerlayer.PlayHeadMarker;
62import org.openstreetmap.josm.gui.util.GuiHelper;
63import org.openstreetmap.josm.tools.AudioPlayer;
64import org.openstreetmap.josm.tools.BugReportExceptionHandler;
65import org.openstreetmap.josm.tools.Shortcut;
66import org.openstreetmap.josm.tools.Utils;
67
68/**
69 * This is a component used in the {@link MapFrame} for browsing the map. It use is to
70 * provide the MapMode's enough capabilities to operate.<br><br>
71 *
72 * {@code MapView} holds meta-data about the data set currently displayed, as scale level,
73 * center point viewed, what scrolling mode or editing mode is selected or with
74 * what projection the map is viewed etc..<br><br>
75 *
76 * {@code MapView} is able to administrate several layers.
77 *
78 * @author imi
79 */
80public class MapView extends NavigatableComponent implements PropertyChangeListener, PreferenceChangedListener, OsmDataLayer.LayerStateChangeListener {
81
82 /**
83 * Interface to notify listeners of a layer change.
84 * @author imi
85 */
86 public interface LayerChangeListener {
87
88 /**
89 * Notifies this listener that the active layer has changed.
90 * @param oldLayer The previous active layer
91 * @param newLayer The new activer layer
92 */
93 void activeLayerChange(Layer oldLayer, Layer newLayer);
94
95 /**
96 * Notifies this listener that a layer has been added.
97 * @param newLayer The new added layer
98 */
99 void layerAdded(Layer newLayer);
100
101 /**
102 * Notifies this listener that a layer has been removed.
103 * @param oldLayer The old removed layer
104 */
105 void layerRemoved(Layer oldLayer);
106 }
107
108 public interface EditLayerChangeListener {
109 void editLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer);
110 }
111
112 public boolean viewportFollowing = false;
113
114 /**
115 * the layer listeners
116 */
117 private static final CopyOnWriteArrayList<LayerChangeListener> layerChangeListeners = new CopyOnWriteArrayList<LayerChangeListener>();
118 private static final CopyOnWriteArrayList<EditLayerChangeListener> editLayerChangeListeners = new CopyOnWriteArrayList<EditLayerChangeListener>();
119
120 /**
121 * Removes a layer change listener
122 *
123 * @param listener the listener. Ignored if null or already registered.
124 */
125 public static void removeLayerChangeListener(LayerChangeListener listener) {
126 layerChangeListeners.remove(listener);
127 }
128
129 public static void removeEditLayerChangeListener(EditLayerChangeListener listener) {
130 editLayerChangeListeners.remove(listener);
131 }
132
133 /**
134 * Adds a layer change listener
135 *
136 * @param listener the listener. Ignored if null or already registered.
137 */
138 public static void addLayerChangeListener(LayerChangeListener listener) {
139 if (listener != null) {
140 layerChangeListeners.addIfAbsent(listener);
141 }
142 }
143
144 /**
145 * Adds an edit layer change listener
146 *
147 * @param listener the listener. Ignored if null or already registered.
148 * @param initialFire Fire an edit-layer-changed-event right after adding
149 * the listener in case there is an edit layer present
150 */
151 public static void addEditLayerChangeListener(EditLayerChangeListener listener, boolean initialFire) {
152 addEditLayerChangeListener(listener);
153 if (initialFire) {
154 if (Main.isDisplayingMapView() && Main.map.mapView.getEditLayer() != null) {
155 fireEditLayerChanged(null, Main.map.mapView.getEditLayer());
156 }
157 }
158 }
159
160 /**
161 * Adds an edit layer change listener
162 *
163 * @param listener the listener. Ignored if null or already registered.
164 */
165 public static void addEditLayerChangeListener(EditLayerChangeListener listener) {
166 if (listener != null) {
167 editLayerChangeListeners.addIfAbsent(listener);
168 }
169 }
170
171 protected static void fireActiveLayerChanged(Layer oldLayer, Layer newLayer) {
172 for (LayerChangeListener l : layerChangeListeners) {
173 l.activeLayerChange(oldLayer, newLayer);
174 }
175 }
176
177 protected static void fireLayerAdded(Layer newLayer) {
178 for (MapView.LayerChangeListener l : MapView.layerChangeListeners) {
179 l.layerAdded(newLayer);
180 }
181 }
182
183 protected static void fireLayerRemoved(Layer layer) {
184 for (MapView.LayerChangeListener l : MapView.layerChangeListeners) {
185 l.layerRemoved(layer);
186 }
187 }
188
189 protected static void fireEditLayerChanged(OsmDataLayer oldLayer, OsmDataLayer newLayer) {
190 for (EditLayerChangeListener l : editLayerChangeListeners) {
191 l.editLayerChanged(oldLayer, newLayer);
192 }
193 }
194
195 /**
196 * A list of all layers currently loaded.
197 */
198 private final List<Layer> layers = new ArrayList<Layer>();
199 /**
200 * The play head marker: there is only one of these so it isn't in any specific layer
201 */
202 public PlayHeadMarker playHeadMarker = null;
203
204 /**
205 * The layer from the layers list that is currently active.
206 */
207 private Layer activeLayer;
208
209 private OsmDataLayer editLayer;
210
211 /**
212 * The last event performed by mouse.
213 */
214 public MouseEvent lastMEvent = new MouseEvent(this, 0, 0, 0, 0, 0, 0, false); // In case somebody reads it before first mouse move
215
216 private final List<MapViewPaintable> temporaryLayers = new LinkedList<MapViewPaintable>();
217
218 private BufferedImage nonChangedLayersBuffer;
219 private BufferedImage offscreenBuffer;
220 // Layers that wasn't changed since last paint
221 private final List<Layer> nonChangedLayers = new ArrayList<Layer>();
222 private Layer changedLayer;
223 private int lastViewID;
224 private boolean paintPreferencesChanged = true;
225 private Rectangle lastClipBounds = new Rectangle();
226 private MapMover mapMover;
227
228 /**
229 * Constructs a new {@code MapView}.
230 * @param contentPane The content pane used to register shortcuts in its
231 * {@link InputMap} and {@link ActionMap}
232 * @param viewportData the initial viewport of the map. Can be null, then
233 * the viewport is derived from the layer data.
234 */
235 public MapView(final JPanel contentPane, final ViewportData viewportData) {
236 Main.pref.addPreferenceChangeListener(this);
237 final boolean unregisterTab = Shortcut.findShortcut(KeyEvent.VK_TAB, 0)!=null;
238
239 addComponentListener(new ComponentAdapter(){
240 @Override public void componentResized(ComponentEvent e) {
241 removeComponentListener(this);
242
243 MapSlider zoomSlider = new MapSlider(MapView.this);
244 add(zoomSlider);
245 zoomSlider.setBounds(3, 0, 114, 30);
246 zoomSlider.setFocusTraversalKeysEnabled(!unregisterTab);
247
248 MapScaler scaler = new MapScaler(MapView.this);
249 add(scaler);
250 scaler.setLocation(10,30);
251
252 mapMover = new MapMover(MapView.this, contentPane);
253 if (viewportData != null) {
254 zoomTo(viewportData.getCenter(), viewportData.getScale());
255 } else {
256 OsmDataLayer layer = getEditLayer();
257 if (layer != null) {
258 if (!zoomToDataSetBoundingBox(layer.data)) {
259 // no bounding box defined
260 AutoScaleAction.autoScale("data");
261 }
262 } else {
263 AutoScaleAction.autoScale("layer");
264 }
265 }
266 }
267 });
268
269 // listend to selection changes to redraw the map
270 DataSet.addSelectionListener(repaintSelectionChangedListener);
271
272 //store the last mouse action
273 this.addMouseMotionListener(new MouseMotionListener() {
274 @Override public void mouseDragged(MouseEvent e) {
275 mouseMoved(e);
276 }
277 @Override public void mouseMoved(MouseEvent e) {
278 lastMEvent = e;
279 }
280 });
281 this.addMouseListener(new MouseAdapter() {
282 @Override
283 public void mousePressed(MouseEvent me) {
284 // focus the MapView component when mouse is pressed inside it
285 requestFocus();
286 }
287 });
288
289 if (Shortcut.findShortcut(KeyEvent.VK_TAB, 0)!=null) {
290 setFocusTraversalKeysEnabled(false);
291 }
292 }
293
294 // remebered geometry of the component
295 private Dimension oldSize = null;
296 private Point oldLoc = null;
297
298 /*
299 * Call this method to keep map position on screen during next repaint
300 */
301 public void rememberLastPositionOnScreen() {
302 oldSize = getSize();
303 oldLoc = getLocationOnScreen();
304 }
305
306 /**
307 * Adds a GPX layer. A GPX layer is added below the lowest data layer.
308 *
309 * @param layer the GPX layer
310 */
311 protected void addGpxLayer(GpxLayer layer) {
312 if (layers.isEmpty()) {
313 layers.add(layer);
314 return;
315 }
316 for (int i=layers.size()-1; i>= 0; i--) {
317 if (layers.get(i) instanceof OsmDataLayer) {
318 if (i == layers.size()-1) {
319 layers.add(layer);
320 } else {
321 layers.add(i+1, layer);
322 }
323 return;
324 }
325 }
326 layers.add(0, layer);
327 }
328
329 /**
330 * Add a layer to the current MapView. The layer will be added at topmost
331 * position.
332 * @param layer The layer to add
333 */
334 public void addLayer(Layer layer) {
335 if (layer instanceof MarkerLayer && playHeadMarker == null) {
336 playHeadMarker = PlayHeadMarker.create();
337 }
338
339 if (layer instanceof GpxLayer) {
340 addGpxLayer((GpxLayer)layer);
341 } else if (layers.isEmpty()) {
342 layers.add(layer);
343 } else if (layer.isBackgroundLayer()) {
344 int i = 0;
345 for (; i < layers.size(); i++) {
346 if (layers.get(i).isBackgroundLayer()) {
347 break;
348 }
349 }
350 layers.add(i, layer);
351 } else {
352 layers.add(0, layer);
353 }
354 fireLayerAdded(layer);
355 boolean isOsmDataLayer = layer instanceof OsmDataLayer;
356 if (isOsmDataLayer) {
357 ((OsmDataLayer)layer).addLayerStateChangeListener(this);
358 }
359 boolean callSetActiveLayer = isOsmDataLayer || activeLayer == null;
360 if (callSetActiveLayer) {
361 // autoselect the new layer
362 setActiveLayer(layer); // also repaints this MapView
363 }
364 layer.addPropertyChangeListener(this);
365 Main.addProjectionChangeListener(layer);
366 AudioPlayer.reset();
367 if (!callSetActiveLayer) {
368 repaint();
369 }
370 }
371
372 @Override
373 protected DataSet getCurrentDataSet() {
374 if (editLayer != null)
375 return editLayer.data;
376 else
377 return null;
378 }
379
380 /**
381 * Replies true if the active layer is drawable.
382 *
383 * @return true if the active layer is drawable, false otherwise
384 */
385 public boolean isActiveLayerDrawable() {
386 return editLayer != null;
387 }
388
389 /**
390 * Replies true if the active layer is visible.
391 *
392 * @return true if the active layer is visible, false otherwise
393 */
394 public boolean isActiveLayerVisible() {
395 return isActiveLayerDrawable() && editLayer.isVisible();
396 }
397
398 /**
399 * Determines the next active data layer according to the following
400 * rules:
401 * <ul>
402 * <li>if there is at least one {@link OsmDataLayer} the first one
403 * becomes active</li>
404 * <li>otherwise, the top most layer of any type becomes active</li>
405 * </ul>
406 *
407 * @return the next active data layer
408 */
409 protected Layer determineNextActiveLayer(List<Layer> layersList) {
410 // First look for data layer
411 for (Layer layer:layersList) {
412 if (layer instanceof OsmDataLayer)
413 return layer;
414 }
415
416 // Then any layer
417 if (!layersList.isEmpty())
418 return layersList.get(0);
419
420 // and then give up
421 return null;
422
423 }
424
425 /**
426 * Remove the layer from the mapview. If the layer was in the list before,
427 * an LayerChange event is fired.
428 * @param layer The layer to remove
429 */
430 public void removeLayer(Layer layer) {
431 List<Layer> layersList = new ArrayList<Layer>(layers);
432
433 if (!layersList.remove(layer))
434 return;
435
436 setEditLayer(layersList);
437
438 if (layer == activeLayer) {
439 setActiveLayer(determineNextActiveLayer(layersList), false);
440 }
441
442 if (layer instanceof OsmDataLayer) {
443 ((OsmDataLayer)layer).removeLayerPropertyChangeListener(this);
444 }
445
446 layers.remove(layer);
447 Main.removeProjectionChangeListener(layer);
448 fireLayerRemoved(layer);
449 layer.removePropertyChangeListener(this);
450 layer.destroy();
451 AudioPlayer.reset();
452 repaint();
453 }
454
455 private boolean virtualNodesEnabled = false;
456
457 public void setVirtualNodesEnabled(boolean enabled) {
458 if(virtualNodesEnabled != enabled) {
459 virtualNodesEnabled = enabled;
460 repaint();
461 }
462 }
463 public boolean isVirtualNodesEnabled() {
464 return virtualNodesEnabled;
465 }
466
467 /**
468 * Moves the layer to the given new position. No event is fired, but repaints
469 * according to the new Z-Order of the layers.
470 *
471 * @param layer The layer to move
472 * @param pos The new position of the layer
473 */
474 public void moveLayer(Layer layer, int pos) {
475 int curLayerPos = layers.indexOf(layer);
476 if (curLayerPos == -1)
477 throw new IllegalArgumentException(tr("Layer not in list."));
478 if (pos == curLayerPos)
479 return; // already in place.
480 layers.remove(curLayerPos);
481 if (pos >= layers.size()) {
482 layers.add(layer);
483 } else {
484 layers.add(pos, layer);
485 }
486 setEditLayer(layers);
487 AudioPlayer.reset();
488 repaint();
489 }
490
491 public int getLayerPos(Layer layer) {
492 int curLayerPos = layers.indexOf(layer);
493 if (curLayerPos == -1)
494 throw new IllegalArgumentException(tr("Layer not in list."));
495 return curLayerPos;
496 }
497
498 /**
499 * Creates a list of the visible layers in Z-Order, the layer with the lowest Z-Order
500 * first, layer with the highest Z-Order last.
501 *
502 * @return a list of the visible in Z-Order, the layer with the lowest Z-Order
503 * first, layer with the highest Z-Order last.
504 */
505 protected List<Layer> getVisibleLayersInZOrder() {
506 List<Layer> ret = new ArrayList<Layer>();
507 for (Layer l: layers) {
508 if (l.isVisible()) {
509 ret.add(l);
510 }
511 }
512 // sort according to position in the list of layers, with one exception:
513 // an active data layer always becomes a higher Z-Order than all other
514 // data layers
515 //
516 Collections.sort(
517 ret,
518 new Comparator<Layer>() {
519 @Override public int compare(Layer l1, Layer l2) {
520 if (l1 instanceof OsmDataLayer && l2 instanceof OsmDataLayer) {
521 if (l1 == getActiveLayer()) return -1;
522 if (l2 == getActiveLayer()) return 1;
523 return Integer.valueOf(layers.indexOf(l1)).compareTo(layers.indexOf(l2));
524 } else
525 return Integer.valueOf(layers.indexOf(l1)).compareTo(layers.indexOf(l2));
526 }
527 }
528 );
529 Collections.reverse(ret);
530 return ret;
531 }
532
533 private void paintLayer(Layer layer, Graphics2D g, Bounds box) {
534 if (layer.getOpacity() < 1) {
535 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,(float)layer.getOpacity()));
536 }
537 layer.paint(g, this, box);
538 g.setPaintMode();
539 }
540
541 /**
542 * Draw the component.
543 */
544 @Override public void paint(Graphics g) {
545 if (BugReportExceptionHandler.exceptionHandlingInProgress())
546 return;
547
548 if (center == null)
549 return; // no data loaded yet.
550
551 // if the position was remembered, we need to adjust center once before repainting
552 if (oldLoc != null && oldSize != null) {
553 Point l1 = getLocationOnScreen();
554 final EastNorth newCenter = new EastNorth(
555 center.getX()+ (l1.x-oldLoc.x - (oldSize.width-getWidth())/2.0)*getScale(),
556 center.getY()+ (oldLoc.y-l1.y + (oldSize.height-getHeight())/2.0)*getScale()
557 );
558 oldLoc = null; oldSize = null;
559 zoomTo(newCenter);
560 }
561
562 List<Layer> visibleLayers = getVisibleLayersInZOrder();
563
564 int nonChangedLayersCount = 0;
565 for (Layer l: visibleLayers) {
566 if (l.isChanged() || l == changedLayer) {
567 break;
568 } else {
569 nonChangedLayersCount++;
570 }
571 }
572
573 boolean canUseBuffer;
574
575 synchronized (this) {
576 canUseBuffer = !paintPreferencesChanged;
577 paintPreferencesChanged = false;
578 }
579 canUseBuffer = canUseBuffer && nonChangedLayers.size() <= nonChangedLayersCount &&
580 lastViewID == getViewID() && lastClipBounds.contains(g.getClipBounds());
581 if (canUseBuffer) {
582 for (int i=0; i<nonChangedLayers.size(); i++) {
583 if (visibleLayers.get(i) != nonChangedLayers.get(i)) {
584 canUseBuffer = false;
585 break;
586 }
587 }
588 }
589
590 if (null == offscreenBuffer || offscreenBuffer.getWidth() != getWidth() || offscreenBuffer.getHeight() != getHeight()) {
591 offscreenBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_3BYTE_BGR);
592 }
593
594 Graphics2D tempG = offscreenBuffer.createGraphics();
595 tempG.setClip(g.getClip());
596 Bounds box = getLatLonBounds(g.getClipBounds());
597
598 if (!canUseBuffer || nonChangedLayersBuffer == null) {
599 if (null == nonChangedLayersBuffer || nonChangedLayersBuffer.getWidth() != getWidth() || nonChangedLayersBuffer.getHeight() != getHeight()) {
600 nonChangedLayersBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_3BYTE_BGR);
601 }
602 Graphics2D g2 = nonChangedLayersBuffer.createGraphics();
603 g2.setClip(g.getClip());
604 g2.setColor(PaintColors.getBackgroundColor());
605 g2.fillRect(0, 0, getWidth(), getHeight());
606
607 for (int i=0; i<nonChangedLayersCount; i++) {
608 paintLayer(visibleLayers.get(i),g2, box);
609 }
610 } else {
611 // Maybe there were more unchanged layers then last time - draw them to buffer
612 if (nonChangedLayers.size() != nonChangedLayersCount) {
613 Graphics2D g2 = nonChangedLayersBuffer.createGraphics();
614 g2.setClip(g.getClip());
615 for (int i=nonChangedLayers.size(); i<nonChangedLayersCount; i++) {
616 paintLayer(visibleLayers.get(i),g2, box);
617 }
618 }
619 }
620
621 nonChangedLayers.clear();
622 changedLayer = null;
623 for (int i=0; i<nonChangedLayersCount; i++) {
624 nonChangedLayers.add(visibleLayers.get(i));
625 }
626 lastViewID = getViewID();
627 lastClipBounds = g.getClipBounds();
628
629 tempG.drawImage(nonChangedLayersBuffer, 0, 0, null);
630
631 for (int i=nonChangedLayersCount; i<visibleLayers.size(); i++) {
632 paintLayer(visibleLayers.get(i),tempG, box);
633 }
634
635 for (MapViewPaintable mvp : temporaryLayers) {
636 mvp.paint(tempG, this, box);
637 }
638
639 // draw world borders
640 tempG.setColor(Color.WHITE);
641 Bounds b = getProjection().getWorldBoundsLatLon();
642 double lat = b.getMinLat();
643 double lon = b.getMinLon();
644
645 Point p = getPoint(b.getMin());
646
647 GeneralPath path = new GeneralPath();
648
649 path.moveTo(p.x, p.y);
650 double max = b.getMax().lat();
651 for(; lat <= max; lat += 1.0)
652 {
653 p = getPoint(new LatLon(lat >= max ? max : lat, lon));
654 path.lineTo(p.x, p.y);
655 }
656 lat = max; max = b.getMax().lon();
657 for(; lon <= max; lon += 1.0)
658 {
659 p = getPoint(new LatLon(lat, lon >= max ? max : lon));
660 path.lineTo(p.x, p.y);
661 }
662 lon = max; max = b.getMinLat();
663 for(; lat >= max; lat -= 1.0)
664 {
665 p = getPoint(new LatLon(lat <= max ? max : lat, lon));
666 path.lineTo(p.x, p.y);
667 }
668 lat = max; max = b.getMinLon();
669 for(; lon >= max; lon -= 1.0)
670 {
671 p = getPoint(new LatLon(lat, lon <= max ? max : lon));
672 path.lineTo(p.x, p.y);
673 }
674
675 int w = getWidth();
676 int h = getHeight();
677
678 // Work around OpenJDK having problems when drawing out of bounds
679 final Area border = new Area(path);
680 // Make the viewport 1px larger in every direction to prevent an
681 // additional 1px border when zooming in
682 final Area viewport = new Area(new Rectangle(-1, -1, w + 2, h + 2));
683 border.intersect(viewport);
684 tempG.draw(border);
685
686 if (Main.isDisplayingMapView() && Main.map.filterDialog != null) {
687 Main.map.filterDialog.drawOSDText(tempG);
688 }
689
690 if (playHeadMarker != null) {
691 playHeadMarker.paint(tempG, this);
692 }
693
694 g.drawImage(offscreenBuffer, 0, 0, null);
695 super.paint(g);
696 }
697
698 /**
699 * Set the new dimension to the view.
700 */
701 public void recalculateCenterScale(BoundingXYVisitor box) {
702 if (box == null) {
703 box = new BoundingXYVisitor();
704 }
705 if (box.getBounds() == null) {
706 box.visit(getProjection().getWorldBoundsLatLon());
707 }
708 if (!box.hasExtend()) {
709 box.enlargeBoundingBox();
710 }
711
712 zoomTo(box.getBounds());
713 }
714
715 /**
716 * @return An unmodifiable collection of all layers
717 */
718 public Collection<Layer> getAllLayers() {
719 return Collections.unmodifiableCollection(new ArrayList<Layer>(layers));
720 }
721
722 /**
723 * @return An unmodifiable ordered list of all layers
724 */
725 public List<Layer> getAllLayersAsList() {
726 return Collections.unmodifiableList(new ArrayList<Layer>(layers));
727 }
728
729 /**
730 * Replies an unmodifiable list of layers of a certain type.
731 *
732 * Example:
733 * <pre>
734 * List&lt;WMSLayer&gt; wmsLayers = getLayersOfType(WMSLayer.class);
735 * </pre>
736 *
737 * @return an unmodifiable list of layers of a certain type.
738 */
739 public <T extends Layer> List<T> getLayersOfType(Class<T> ofType) {
740 return new ArrayList<T>(Utils.filteredCollection(getAllLayers(), ofType));
741 }
742
743 /**
744 * Replies the number of layers managed by this mav view
745 *
746 * @return the number of layers managed by this mav view
747 */
748 public int getNumLayers() {
749 return layers.size();
750 }
751
752 /**
753 * Replies true if there is at least one layer in this map view
754 *
755 * @return true if there is at least one layer in this map view
756 */
757 public boolean hasLayers() {
758 return getNumLayers() > 0;
759 }
760
761 private void setEditLayer(List<Layer> layersList) {
762 OsmDataLayer newEditLayer = layersList.contains(editLayer)?editLayer:null;
763 OsmDataLayer oldEditLayer = editLayer;
764
765 // Find new edit layer
766 if (activeLayer != editLayer || !layersList.contains(editLayer)) {
767 if (activeLayer instanceof OsmDataLayer && layersList.contains(activeLayer)) {
768 newEditLayer = (OsmDataLayer) activeLayer;
769 } else {
770 for (Layer layer:layersList) {
771 if (layer instanceof OsmDataLayer) {
772 newEditLayer = (OsmDataLayer) layer;
773 break;
774 }
775 }
776 }
777 }
778
779 // Set new edit layer
780 if (newEditLayer != editLayer) {
781 if (newEditLayer == null) {
782 getCurrentDataSet().setSelected();
783 }
784
785 editLayer = newEditLayer;
786 fireEditLayerChanged(oldEditLayer, newEditLayer);
787 refreshTitle();
788 }
789
790 }
791
792 /**
793 * Sets the active layer to <code>layer</code>. If <code>layer</code> is an instance
794 * of {@link OsmDataLayer} also sets {@link #editLayer} to <code>layer</code>.
795 *
796 * @param layer the layer to be activate; must be one of the layers in the list of layers
797 * @exception IllegalArgumentException thrown if layer is not in the lis of layers
798 */
799 public void setActiveLayer(Layer layer) {
800 setActiveLayer(layer, true);
801 }
802
803 private void setActiveLayer(Layer layer, boolean setEditLayer) {
804 if (layer != null && !layers.contains(layer))
805 throw new IllegalArgumentException(tr("Layer ''{0}'' must be in list of layers", layer.toString()));
806
807 if (layer == activeLayer)
808 return;
809
810 Layer old = activeLayer;
811 activeLayer = layer;
812 if (setEditLayer) {
813 setEditLayer(layers);
814 }
815 fireActiveLayerChanged(old, layer);
816
817 /* This only makes the buttons look disabled. Disabling the actions as well requires
818 * the user to re-select the tool after i.e. moving a layer. While testing I found
819 * that I switch layers and actions at the same time and it was annoying to mind the
820 * order. This way it works as visual clue for new users */
821 for (final AbstractButton b: Main.map.allMapModeButtons) {
822 MapMode mode = (MapMode)b.getAction();
823 if (mode.layerIsSupported(layer)) {
824 Main.registerActionShortcut(mode, mode.getShortcut()); //fix #6876
825 GuiHelper.runInEDTAndWait(new Runnable() {
826 @Override public void run() {
827 b.setEnabled(true);
828 }
829 });
830 } else {
831 Main.unregisterShortcut(mode.getShortcut());
832 GuiHelper.runInEDTAndWait(new Runnable() {
833 @Override public void run() {
834 b.setEnabled(false);
835 }
836 });
837 }
838 }
839 AudioPlayer.reset();
840 repaint();
841 }
842
843 /**
844 * Replies the currently active layer
845 *
846 * @return the currently active layer (may be null)
847 */
848 public Layer getActiveLayer() {
849 return activeLayer;
850 }
851
852 /**
853 * Replies the current edit layer, if any
854 *
855 * @return the current edit layer. May be null.
856 */
857 public OsmDataLayer getEditLayer() {
858 return editLayer;
859 }
860
861 /**
862 * replies true if the list of layers managed by this map view contain layer
863 *
864 * @param layer the layer
865 * @return true if the list of layers managed by this map view contain layer
866 */
867 public boolean hasLayer(Layer layer) {
868 return layers.contains(layer);
869 }
870
871 /**
872 * Tries to zoom to the download boundingbox[es] of the current edit layer
873 * (aka {@link OsmDataLayer}). If the edit layer has multiple download bounding
874 * boxes it zooms to a large virtual bounding box containing all smaller ones.
875 *
876 * @return <code>true</code> if a zoom operation has been performed
877 */
878 public boolean zoomToDataSetBoundingBox(DataSet ds) {
879 // In case we already have an existing data layer ...
880 OsmDataLayer layer= getEditLayer();
881 if (layer == null)
882 return false;
883 Collection<DataSource> dataSources = ds.dataSources;
884 // ... with bounding box[es] of data loaded from OSM or a file...
885 BoundingXYVisitor bbox = new BoundingXYVisitor();
886 for (DataSource source : dataSources) {
887 bbox.visit(source.bounds);
888 }
889 if (bbox.hasExtend()) {
890 // ... we zoom to it's bounding box
891 recalculateCenterScale(bbox);
892 return true;
893 }
894 return false;
895 }
896
897 public boolean addTemporaryLayer(MapViewPaintable mvp) {
898 if (temporaryLayers.contains(mvp)) return false;
899 return temporaryLayers.add(mvp);
900 }
901
902 public boolean removeTemporaryLayer(MapViewPaintable mvp) {
903 return temporaryLayers.remove(mvp);
904 }
905
906 @Override
907 public void propertyChange(PropertyChangeEvent evt) {
908 if (evt.getPropertyName().equals(Layer.VISIBLE_PROP)) {
909 repaint();
910 } else if (evt.getPropertyName().equals(Layer.OPACITY_PROP)) {
911 Layer l = (Layer)evt.getSource();
912 if (l.isVisible()) {
913 changedLayer = l;
914 repaint();
915 }
916 } else if (evt.getPropertyName().equals(OsmDataLayer.REQUIRES_SAVE_TO_DISK_PROP)
917 || evt.getPropertyName().equals(OsmDataLayer.REQUIRES_UPLOAD_TO_SERVER_PROP)) {
918 OsmDataLayer layer = (OsmDataLayer)evt.getSource();
919 if (layer == getEditLayer()) {
920 refreshTitle();
921 }
922 }
923 }
924
925 protected void refreshTitle() {
926 boolean dirty = editLayer != null && (editLayer.requiresSaveToFile() || (editLayer.requiresUploadToServer() && !editLayer.isUploadDiscouraged()));
927 if (dirty) {
928 JOptionPane.getFrameForComponent(Main.parent).setTitle("* " + tr("Java OpenStreetMap Editor"));
929 } else {
930 JOptionPane.getFrameForComponent(Main.parent).setTitle(tr("Java OpenStreetMap Editor"));
931 }
932 }
933
934 @Override
935 public void preferenceChanged(PreferenceChangeEvent e) {
936 synchronized (this) {
937 paintPreferencesChanged = true;
938 }
939 }
940
941 private SelectionChangedListener repaintSelectionChangedListener = new SelectionChangedListener(){
942 @Override public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
943 repaint();
944 }
945 };
946
947 public void destroy() {
948 Main.pref.removePreferenceChangeListener(this);
949 DataSet.removeSelectionListener(repaintSelectionChangedListener);
950 MultipolygonCache.getInstance().clear(this);
951 if (mapMover != null) {
952 mapMover.destroy();
953 }
954 activeLayer = null;
955 changedLayer = null;
956 editLayer = null;
957 layers.clear();
958 nonChangedLayers.clear();
959 temporaryLayers.clear();
960 }
961
962 @Override
963 public void uploadDiscouragedChanged(OsmDataLayer layer, boolean newValue) {
964 if (layer == getEditLayer()) {
965 refreshTitle();
966 }
967 }
968
969 /**
970 * Get a string representation of all layers suitable for the {@code source} changeset tag.
971 */
972 public String getLayerInformationForSourceTag() {
973 final Collection<String> layerInfo = new ArrayList<String>();
974 if (!getLayersOfType(GpxLayer.class).isEmpty()) {
975 // no i18n for international values
976 layerInfo.add("survey");
977 }
978 for (final GeoImageLayer i : getLayersOfType(GeoImageLayer.class)) {
979 layerInfo.add(i.getName());
980 }
981 for (final ImageryLayer i : getLayersOfType(ImageryLayer.class)) {
982 layerInfo.add(ImageryInfo.ImageryType.BING.equals(i.getInfo().getImageryType()) ? "Bing" : i.getName());
983 }
984 return Utils.join("; ", layerInfo);
985 }
986}
Note: See TracBrowser for help on using the repository browser.