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

Last change on this file since 6070 was 6070, checked in by stoecker, 11 years ago

see #8853 remove tabs, trailing spaces, windows line ends, strange characters

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