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

Last change on this file since 3837 was 3837, checked in by framm, 13 years ago

new viewport following function; moves the viewport so that the last placed
node is in the center. the idea is to greatly improve tracing of long, linear
features from imagery or tracks (no need to right-click and pan all the time),
however the execution still leaves to be desired; problem is that after you
have placed a node you have to wait for the movement to finish before you can
place the next node. possible enhancements: only scroll if placed node is on
the outer rim of viewport?

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