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

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

fix #8090 - Removing saved non-uploadable layer asks for confirmation

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