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

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

Prevents MapView from being repainted multiple times when adding the first active layer

  • Property svn:eol-style set to native
File size: 30.1 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 {
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<MapView.LayerChangeListener> layerChangeListeners = new CopyOnWriteArrayList<MapView.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(MapView.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(MapView.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.map != null && Main.map.mapView != null && 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
218 /**
219 * Constructs a new {@code MapView}.
220 * @param contentPane The content pane used to register shortcuts in its {@link InputMap} and {@link ActionMap}
221 */
222 public MapView(final JPanel contentPane) {
223 Main.pref.addPreferenceChangeListener(this);
224
225 // new MoveAction(MoveAction.Direction.UP);
226 // new MoveAction(MoveAction.Direction.DOWN);
227 // new MoveAction(MoveAction.Direction.LEFT);
228 // new MoveAction(MoveAction.Direction.RIGHT);
229
230 addComponentListener(new ComponentAdapter(){
231 @Override public void componentResized(ComponentEvent e) {
232 removeComponentListener(this);
233
234 MapSlider zoomSlider = new MapSlider(MapView.this);
235 add(zoomSlider);
236 zoomSlider.setBounds(3, 0, 114, 30);
237
238 MapScaler scaler = new MapScaler(MapView.this);
239 add(scaler);
240 scaler.setLocation(10,30);
241
242 new MapMover(MapView.this, contentPane);
243 OsmDataLayer layer = getEditLayer();
244 if (layer != null) {
245 if (!zoomToDataSetBoundingBox(layer.data)) {
246 // no bounding box defined
247 AutoScaleAction.autoScale("data");
248 }
249 } else {
250 AutoScaleAction.autoScale("layer");
251 }
252 }
253 });
254
255 // listend to selection changes to redraw the map
256 DataSet.addSelectionListener(repaintSelectionChangedListener);
257
258 //store the last mouse action
259 this.addMouseMotionListener(new MouseMotionListener() {
260 public void mouseDragged(MouseEvent e) {
261 mouseMoved(e);
262 }
263 public void mouseMoved(MouseEvent e) {
264 lastMEvent = e;
265 }
266 });
267
268 // Add Multipolygon cache to layer listeners
269 addLayerChangeListener(MultipolygonCache.getInstance());
270 }
271
272 /**
273 * Adds a GPX layer. A GPX layer is added below the lowest data layer.
274 *
275 * @param layer the GPX layer
276 */
277 protected void addGpxLayer(GpxLayer layer) {
278 if (layers.isEmpty()) {
279 layers.add(layer);
280 return;
281 }
282 for (int i=layers.size()-1; i>= 0; i--) {
283 if (layers.get(i) instanceof OsmDataLayer) {
284 if (i == layers.size()-1) {
285 layers.add(layer);
286 } else {
287 layers.add(i+1, layer);
288 }
289 return;
290 }
291 }
292 layers.add(0, layer);
293 }
294
295 /**
296 * Add a layer to the current MapView. The layer will be added at topmost
297 * position.
298 * @param layer The layer to add
299 */
300 public void addLayer(Layer layer) {
301 if (layer instanceof MarkerLayer && playHeadMarker == null) {
302 playHeadMarker = PlayHeadMarker.create();
303 }
304
305 if (layer instanceof GpxLayer) {
306 addGpxLayer((GpxLayer)layer);
307 } else if (layers.isEmpty()) {
308 layers.add(layer);
309 } else if (layer.isBackgroundLayer()) {
310 int i = 0;
311 for (; i < layers.size(); i++) {
312 if (layers.get(i).isBackgroundLayer()) {
313 break;
314 }
315 }
316 layers.add(i, layer);
317 } else {
318 layers.add(0, layer);
319 }
320 fireLayerAdded(layer);
321 boolean callSetActiveLayer = layer instanceof OsmDataLayer || activeLayer == null;
322 if (callSetActiveLayer) {
323 // autoselect the new layer
324 setActiveLayer(layer); // also repaints this MapView
325 }
326 layer.addPropertyChangeListener(this);
327 Main.addProjectionChangeListener(layer);
328 AudioPlayer.reset();
329 if (!callSetActiveLayer) {
330 repaint();
331 }
332 }
333
334 @Override
335 protected DataSet getCurrentDataSet() {
336 if (editLayer != null)
337 return editLayer.data;
338 else
339 return null;
340 }
341
342 /**
343 * Replies true if the active layer is drawable.
344 *
345 * @return true if the active layer is drawable, false otherwise
346 */
347 public boolean isActiveLayerDrawable() {
348 return editLayer != null;
349 }
350
351 /**
352 * Replies true if the active layer is visible.
353 *
354 * @return true if the active layer is visible, false otherwise
355 */
356 public boolean isActiveLayerVisible() {
357 return isActiveLayerDrawable() && editLayer.isVisible();
358 }
359
360 /**
361 * Determines the next active data layer according to the following
362 * rules:
363 * <ul>
364 * <li>if there is at least one {@link OsmDataLayer} the first one
365 * becomes active</li>
366 * <li>otherwise, the top most layer of any type becomes active</li>
367 * </ul>
368 *
369 * @return the next active data layer
370 */
371 protected Layer determineNextActiveLayer(List<Layer> layersList) {
372 // First look for data layer
373 for (Layer layer:layersList) {
374 if (layer instanceof OsmDataLayer)
375 return layer;
376 }
377
378 // Then any layer
379 if (!layersList.isEmpty())
380 return layersList.get(0);
381
382 // and then give up
383 return null;
384
385 }
386
387 /**
388 * Remove the layer from the mapview. If the layer was in the list before,
389 * an LayerChange event is fired.
390 * @param layer The layer to remove
391 */
392 public void removeLayer(Layer layer) {
393 List<Layer> layersList = new ArrayList<Layer>(layers);
394
395 if (!layersList.remove(layer))
396 return;
397
398 setEditLayer(layersList);
399
400 if (layer == activeLayer) {
401 setActiveLayer(determineNextActiveLayer(layersList), false);
402 }
403
404 layers.remove(layer);
405 Main.removeProjectionChangeListener(layer);
406 fireLayerRemoved(layer);
407 layer.removePropertyChangeListener(this);
408 layer.destroy();
409 AudioPlayer.reset();
410 repaint();
411 }
412
413 private boolean virtualNodesEnabled = false;
414
415 public void setVirtualNodesEnabled(boolean enabled) {
416 if(virtualNodesEnabled != enabled) {
417 virtualNodesEnabled = enabled;
418 repaint();
419 }
420 }
421 public boolean isVirtualNodesEnabled() {
422 return virtualNodesEnabled;
423 }
424
425 /**
426 * Moves the layer to the given new position. No event is fired, but repaints
427 * according to the new Z-Order of the layers.
428 *
429 * @param layer The layer to move
430 * @param pos The new position of the layer
431 */
432 public void moveLayer(Layer layer, int pos) {
433 int curLayerPos = layers.indexOf(layer);
434 if (curLayerPos == -1)
435 throw new IllegalArgumentException(tr("Layer not in list."));
436 if (pos == curLayerPos)
437 return; // already in place.
438 layers.remove(curLayerPos);
439 if (pos >= layers.size()) {
440 layers.add(layer);
441 } else {
442 layers.add(pos, layer);
443 }
444 setEditLayer(layers);
445 AudioPlayer.reset();
446 repaint();
447 }
448
449 public int getLayerPos(Layer layer) {
450 int curLayerPos = layers.indexOf(layer);
451 if (curLayerPos == -1)
452 throw new IllegalArgumentException(tr("Layer not in list."));
453 return curLayerPos;
454 }
455
456 /**
457 * Creates a list of the visible layers in Z-Order, the layer with the lowest Z-Order
458 * first, layer with the highest Z-Order last.
459 *
460 * @return a list of the visible in Z-Order, the layer with the lowest Z-Order
461 * first, layer with the highest Z-Order last.
462 */
463 protected List<Layer> getVisibleLayersInZOrder() {
464 ArrayList<Layer> ret = new ArrayList<Layer>();
465 for (Layer l: layers) {
466 if (l.isVisible()) {
467 ret.add(l);
468 }
469 }
470 // sort according to position in the list of layers, with one exception:
471 // an active data layer always becomes a higher Z-Order than all other
472 // data layers
473 //
474 Collections.sort(
475 ret,
476 new Comparator<Layer>() {
477 public int compare(Layer l1, Layer l2) {
478 if (l1 instanceof OsmDataLayer && l2 instanceof OsmDataLayer) {
479 if (l1 == getActiveLayer()) return -1;
480 if (l2 == getActiveLayer()) return 1;
481 return Integer.valueOf(layers.indexOf(l1)).compareTo(layers.indexOf(l2));
482 } else
483 return Integer.valueOf(layers.indexOf(l1)).compareTo(layers.indexOf(l2));
484 }
485 }
486 );
487 Collections.reverse(ret);
488 return ret;
489 }
490
491 private void paintLayer(Layer layer, Graphics2D g, Bounds box) {
492 if (layer.getOpacity() < 1) {
493 g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,(float)layer.getOpacity()));
494 }
495 layer.paint(g, this, box);
496 g.setPaintMode();
497 }
498
499 /**
500 * Draw the component.
501 */
502 @Override public void paint(Graphics g) {
503 if (BugReportExceptionHandler.exceptionHandlingInProgress())
504 return;
505
506 if (center == null)
507 return; // no data loaded yet.
508
509 List<Layer> visibleLayers = getVisibleLayersInZOrder();
510
511 int nonChangedLayersCount = 0;
512 for (Layer l: visibleLayers) {
513 if (l.isChanged() || l == changedLayer) {
514 break;
515 } else {
516 nonChangedLayersCount++;
517 }
518 }
519
520 boolean canUseBuffer;
521
522 synchronized (this) {
523 canUseBuffer = !paintPreferencesChanged;
524 paintPreferencesChanged = false;
525 }
526 canUseBuffer = canUseBuffer && nonChangedLayers.size() <= nonChangedLayersCount &&
527 lastViewID == getViewID() && lastClipBounds.contains(g.getClipBounds());
528 if (canUseBuffer) {
529 for (int i=0; i<nonChangedLayers.size(); i++) {
530 if (visibleLayers.get(i) != nonChangedLayers.get(i)) {
531 canUseBuffer = false;
532 break;
533 }
534 }
535 }
536
537 if (null == offscreenBuffer || offscreenBuffer.getWidth() != getWidth() || offscreenBuffer.getHeight() != getHeight()) {
538 offscreenBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_3BYTE_BGR);
539 }
540
541 Graphics2D tempG = offscreenBuffer.createGraphics();
542 tempG.setClip(g.getClip());
543 Bounds box = getLatLonBounds(g.getClipBounds());
544
545 if (!canUseBuffer || nonChangedLayersBuffer == null) {
546 if (null == nonChangedLayersBuffer || nonChangedLayersBuffer.getWidth() != getWidth() || nonChangedLayersBuffer.getHeight() != getHeight()) {
547 nonChangedLayersBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_3BYTE_BGR);
548 }
549 Graphics2D g2 = nonChangedLayersBuffer.createGraphics();
550 g2.setClip(g.getClip());
551 g2.setColor(PaintColors.getBackgroundColor());
552 g2.fillRect(0, 0, getWidth(), getHeight());
553
554 for (int i=0; i<nonChangedLayersCount; i++) {
555 paintLayer(visibleLayers.get(i),g2, box);
556 }
557 } else {
558 // Maybe there were more unchanged layers then last time - draw them to buffer
559 if (nonChangedLayers.size() != nonChangedLayersCount) {
560 Graphics2D g2 = nonChangedLayersBuffer.createGraphics();
561 g2.setClip(g.getClip());
562 for (int i=nonChangedLayers.size(); i<nonChangedLayersCount; i++) {
563 paintLayer(visibleLayers.get(i),g2, box);
564 }
565 }
566 }
567
568 nonChangedLayers.clear();
569 changedLayer = null;
570 for (int i=0; i<nonChangedLayersCount; i++) {
571 nonChangedLayers.add(visibleLayers.get(i));
572 }
573 lastViewID = getViewID();
574 lastClipBounds = g.getClipBounds();
575
576 tempG.drawImage(nonChangedLayersBuffer, 0, 0, null);
577
578 for (int i=nonChangedLayersCount; i<visibleLayers.size(); i++) {
579 paintLayer(visibleLayers.get(i),tempG, box);
580 }
581
582 for (MapViewPaintable mvp : temporaryLayers) {
583 mvp.paint(tempG, this, box);
584 }
585
586 // draw world borders
587 tempG.setColor(Color.WHITE);
588 Bounds b = getProjection().getWorldBoundsLatLon();
589 double lat = b.getMin().lat();
590 double lon = b.getMin().lon();
591
592 Point p = getPoint(b.getMin());
593
594 GeneralPath path = new GeneralPath();
595
596 path.moveTo(p.x, p.y);
597 double max = b.getMax().lat();
598 for(; lat <= max; lat += 1.0)
599 {
600 p = getPoint(new LatLon(lat >= max ? max : lat, lon));
601 path.lineTo(p.x, p.y);
602 }
603 lat = max; max = b.getMax().lon();
604 for(; lon <= max; lon += 1.0)
605 {
606 p = getPoint(new LatLon(lat, lon >= max ? max : lon));
607 path.lineTo(p.x, p.y);
608 }
609 lon = max; max = b.getMin().lat();
610 for(; lat >= max; lat -= 1.0)
611 {
612 p = getPoint(new LatLon(lat <= max ? max : lat, lon));
613 path.lineTo(p.x, p.y);
614 }
615 lat = max; max = b.getMin().lon();
616 for(; lon >= max; lon -= 1.0)
617 {
618 p = getPoint(new LatLon(lat, lon <= max ? max : lon));
619 path.lineTo(p.x, p.y);
620 }
621
622 int w = getWidth();
623 int h = getHeight();
624
625 // Work around OpenJDK having problems when drawing out of bounds
626 final Area border = new Area(path);
627 // Make the viewport 1px larger in every direction to prevent an
628 // additional 1px border when zooming in
629 final Area viewport = new Area(new Rectangle(-1, -1, w + 2, h + 2));
630 border.intersect(viewport);
631 tempG.draw(border);
632
633 if (Main.map != null && Main.map.filterDialog != null) {
634 Main.map.filterDialog.drawOSDText(tempG);
635 }
636
637 if (playHeadMarker != null) {
638 playHeadMarker.paint(tempG, this);
639 }
640
641 g.drawImage(offscreenBuffer, 0, 0, null);
642 super.paint(g);
643 }
644
645 /**
646 * Set the new dimension to the view.
647 */
648 public void recalculateCenterScale(BoundingXYVisitor box) {
649 if (box == null) {
650 box = new BoundingXYVisitor();
651 }
652 if (box.getBounds() == null) {
653 box.visit(getProjection().getWorldBoundsLatLon());
654 }
655 if (!box.hasExtend()) {
656 box.enlargeBoundingBox();
657 }
658
659 zoomTo(box.getBounds());
660 }
661
662 /**
663 * @return An unmodifiable collection of all layers
664 */
665 public Collection<Layer> getAllLayers() {
666 return Collections.unmodifiableCollection(layers);
667 }
668
669 /**
670 * @return An unmodifiable ordered list of all layers
671 */
672 public List<Layer> getAllLayersAsList() {
673 return Collections.unmodifiableList(layers);
674 }
675
676 /**
677 * Replies an unmodifiable list of layers of a certain type.
678 *
679 * Example:
680 * <pre>
681 * List<WMSLayer> wmsLayers = getLayersOfType(WMSLayer.class);
682 * </pre>
683 *
684 * @return an unmodifiable list of layers of a certain type.
685 */
686 public <T> List<T> getLayersOfType(Class<T> ofType) {
687 ArrayList<T> ret = new ArrayList<T>();
688 for (Layer layer : getAllLayersAsList()) {
689 if (ofType.isInstance(layer)) {
690 ret.add(ofType.cast(layer));
691 }
692 }
693 return ret;
694 }
695
696 /**
697 * Replies the number of layers managed by this mav view
698 *
699 * @return the number of layers managed by this mav view
700 */
701 public int getNumLayers() {
702 return layers.size();
703 }
704
705 /**
706 * Replies true if there is at least one layer in this map view
707 *
708 * @return true if there is at least one layer in this map view
709 */
710 public boolean hasLayers() {
711 return getNumLayers() > 0;
712 }
713
714 private void setEditLayer(List<Layer> layersList) {
715 OsmDataLayer newEditLayer = layersList.contains(editLayer)?editLayer:null;
716 OsmDataLayer oldEditLayer = editLayer;
717
718 // Find new edit layer
719 if (activeLayer != editLayer || !layersList.contains(editLayer)) {
720 if (activeLayer instanceof OsmDataLayer && layersList.contains(activeLayer)) {
721 newEditLayer = (OsmDataLayer) activeLayer;
722 } else {
723 for (Layer layer:layersList) {
724 if (layer instanceof OsmDataLayer) {
725 newEditLayer = (OsmDataLayer) layer;
726 break;
727 }
728 }
729 }
730 }
731
732 // Set new edit layer
733 if (newEditLayer != editLayer) {
734 if (newEditLayer == null) {
735 getCurrentDataSet().setSelected();
736 }
737
738 editLayer = newEditLayer;
739 fireEditLayerChanged(oldEditLayer, newEditLayer);
740 refreshTitle();
741 }
742
743 }
744
745 /**
746 * Sets the active layer to <code>layer</code>. If <code>layer</code> is an instance
747 * of {@link OsmDataLayer} also sets {@link #editLayer} to <code>layer</code>.
748 *
749 * @param layer the layer to be activate; must be one of the layers in the list of layers
750 * @exception IllegalArgumentException thrown if layer is not in the lis of layers
751 */
752 public void setActiveLayer(Layer layer) {
753 setActiveLayer(layer, true);
754 }
755
756 private void setActiveLayer(Layer layer, boolean setEditLayer) {
757 if (layer != null && !layers.contains(layer))
758 throw new IllegalArgumentException(tr("Layer ''{0}'' must be in list of layers", layer.toString()));
759
760 if (layer == activeLayer)
761 return;
762
763 Layer old = activeLayer;
764 activeLayer = layer;
765 if (setEditLayer) {
766 setEditLayer(layers);
767 }
768 fireActiveLayerChanged(old, layer);
769
770 /* This only makes the buttons look disabled. Disabling the actions as well requires
771 * the user to re-select the tool after i.e. moving a layer. While testing I found
772 * that I switch layers and actions at the same time and it was annoying to mind the
773 * order. This way it works as visual clue for new users */
774 for (Enumeration<AbstractButton> e = Main.map.toolGroup.getElements() ; e.hasMoreElements() ;) {
775 AbstractButton button = e.nextElement();
776 MapMode mode = (MapMode)button.getAction();
777 boolean isLayerSupported = mode.layerIsSupported(layer);
778 button.setEnabled(isLayerSupported);
779 // Also update associated shortcut (fix #6876)
780 if (isLayerSupported) {
781 Main.registerActionShortcut(mode, mode.getShortcut());
782 } else {
783 Main.unregisterShortcut(mode.getShortcut());
784 }
785 }
786 AudioPlayer.reset();
787 repaint();
788 }
789
790 /**
791 * Replies the currently active layer
792 *
793 * @return the currently active layer (may be null)
794 */
795 public Layer getActiveLayer() {
796 return activeLayer;
797 }
798
799 /**
800 * Replies the current edit layer, if any
801 *
802 * @return the current edit layer. May be null.
803 */
804 public OsmDataLayer getEditLayer() {
805 return editLayer;
806 }
807
808 /**
809 * replies true if the list of layers managed by this map view contain layer
810 *
811 * @param layer the layer
812 * @return true if the list of layers managed by this map view contain layer
813 */
814 public boolean hasLayer(Layer layer) {
815 return layers.contains(layer);
816 }
817
818 /**
819 * Tries to zoom to the download boundingbox[es] of the current edit layer
820 * (aka {@link OsmDataLayer}). If the edit layer has multiple download bounding
821 * boxes it zooms to a large virtual bounding box containing all smaller ones.
822 *
823 * @return <code>true</code> if a zoom operation has been performed
824 */
825 public boolean zoomToDataSetBoundingBox(DataSet ds) {
826 // In case we already have an existing data layer ...
827 OsmDataLayer layer= getEditLayer();
828 if (layer == null)
829 return false;
830 Collection<DataSource> dataSources = ds.dataSources;
831 // ... with bounding box[es] of data loaded from OSM or a file...
832 BoundingXYVisitor bbox = new BoundingXYVisitor();
833 for (DataSource source : dataSources) {
834 bbox.visit(source.bounds);
835 }
836 if (bbox.hasExtend()) {
837 // ... we zoom to it's bounding box
838 recalculateCenterScale(bbox);
839 return true;
840 }
841 return false;
842 }
843
844 public boolean addTemporaryLayer(MapViewPaintable mvp) {
845 if (temporaryLayers.contains(mvp)) return false;
846 return temporaryLayers.add(mvp);
847 }
848
849 public boolean removeTemporaryLayer(MapViewPaintable mvp) {
850 return temporaryLayers.remove(mvp);
851 }
852
853 public void propertyChange(PropertyChangeEvent evt) {
854 if (evt.getPropertyName().equals(Layer.VISIBLE_PROP)) {
855 repaint();
856 } else if (evt.getPropertyName().equals(Layer.OPACITY_PROP)) {
857 Layer l = (Layer)evt.getSource();
858 if (l.isVisible()) {
859 changedLayer = l;
860 repaint();
861 }
862 } else if (evt.getPropertyName().equals(OsmDataLayer.REQUIRES_SAVE_TO_DISK_PROP)
863 || evt.getPropertyName().equals(OsmDataLayer.REQUIRES_UPLOAD_TO_SERVER_PROP)) {
864 OsmDataLayer layer = (OsmDataLayer)evt.getSource();
865 if (layer == getEditLayer()) {
866 refreshTitle();
867 }
868 }
869 }
870
871 protected void refreshTitle() {
872 boolean dirty = editLayer != null && (editLayer.requiresSaveToFile() || editLayer.requiresUploadToServer());
873 if (dirty) {
874 JOptionPane.getFrameForComponent(Main.parent).setTitle("* " + tr("Java OpenStreetMap Editor"));
875 } else {
876 JOptionPane.getFrameForComponent(Main.parent).setTitle(tr("Java OpenStreetMap Editor"));
877 }
878 }
879
880 @Override
881 public void preferenceChanged(PreferenceChangeEvent e) {
882 synchronized (this) {
883 paintPreferencesChanged = true;
884 }
885 }
886
887 private SelectionChangedListener repaintSelectionChangedListener = new SelectionChangedListener(){
888 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
889 repaint();
890 }
891 };
892
893 public void destroy() {
894 Main.pref.removePreferenceChangeListener(this);
895 DataSet.removeSelectionListener(repaintSelectionChangedListener);
896 MultipolygonCache.getInstance().clear(this);
897 }
898
899}
Note: See TracBrowser for help on using the repository browser.