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

Last change on this file since 1825 was 1825, checked in by Gubaer, 15 years ago

Changed the way we get an "active data layer" and a "current data set". Now, the "active data layer" is the first OsmDataLayer in the list of layers, or null, if there are no OsmDataLayers. The "current data set" is the dataset of the "active data layer", or nul, if there is no "active data layer".

  • Property svn:eol-style set to native
File size: 16.2 KB
Line 
1// License: GPL. See LICENSE file for details.
2
3package org.openstreetmap.josm.gui;
4
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.awt.Color;
8import java.awt.Graphics;
9import java.awt.Graphics2D;
10import java.awt.Point;
11import java.awt.event.ComponentAdapter;
12import java.awt.event.ComponentEvent;
13import java.awt.event.MouseEvent;
14import java.awt.event.MouseMotionListener;
15import java.awt.geom.GeneralPath;
16import java.awt.image.BufferedImage;
17import java.util.ArrayList;
18import java.util.Collection;
19import java.util.Collections;
20import java.util.Enumeration;
21import java.util.LinkedList;
22
23import javax.swing.AbstractButton;
24import javax.swing.JComponent;
25import javax.swing.JOptionPane;
26
27import org.openstreetmap.josm.Main;
28import org.openstreetmap.josm.actions.AutoScaleAction;
29import org.openstreetmap.josm.actions.JosmAction;
30import org.openstreetmap.josm.actions.MoveAction;
31import org.openstreetmap.josm.actions.mapmode.MapMode;
32import org.openstreetmap.josm.data.Bounds;
33import org.openstreetmap.josm.data.ProjectionBounds;
34import org.openstreetmap.josm.data.SelectionChangedListener;
35import org.openstreetmap.josm.data.osm.DataSet;
36import org.openstreetmap.josm.data.osm.DataSource;
37import org.openstreetmap.josm.data.osm.OsmPrimitive;
38import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
39import org.openstreetmap.josm.data.coor.LatLon;
40import org.openstreetmap.josm.gui.layer.Layer;
41import org.openstreetmap.josm.gui.layer.MapViewPaintable;
42import org.openstreetmap.josm.gui.layer.OsmDataLayer;
43import org.openstreetmap.josm.gui.layer.OsmDataLayer.ModifiedChangedListener;
44import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
45import org.openstreetmap.josm.gui.layer.markerlayer.PlayHeadMarker;
46import org.openstreetmap.josm.tools.AudioPlayer;
47
48/**
49 * This is a component used in the MapFrame for browsing the map. It use is to
50 * provide the MapMode's enough capabilities to operate.
51 *
52 * MapView hold meta-data about the data set currently displayed, as scale level,
53 * center point viewed, what scrolling mode or editing mode is selected or with
54 * what projection the map is viewed etc..
55 *
56 * MapView is able to administrate several layers.
57 *
58 * @author imi
59 */
60public class MapView extends NavigatableComponent {
61
62
63 /**
64 * A list of all layers currently loaded.
65 */
66 private ArrayList<Layer> layers = new ArrayList<Layer>();
67 /**
68 * The play head marker: there is only one of these so it isn't in any specific layer
69 */
70 public PlayHeadMarker playHeadMarker = null;
71
72 /**
73 * The layer from the layers list that is currently active.
74 */
75 private Layer activeLayer;
76
77 /**
78 * The last event performed by mouse.
79 */
80 public MouseEvent lastMEvent;
81
82 private LinkedList<MapViewPaintable> temporaryLayers = new LinkedList<MapViewPaintable>();
83
84 private BufferedImage offscreenBuffer;
85
86 public MapView() {
87 addComponentListener(new ComponentAdapter(){
88 @Override public void componentResized(ComponentEvent e) {
89 removeComponentListener(this);
90
91 MapSlider zoomSlider = new MapSlider(MapView.this);
92 add(zoomSlider);
93 zoomSlider.setBounds(3, 0, 114, 30);
94
95 MapScaler scaler = new MapScaler(MapView.this);
96 add(scaler);
97 scaler.setLocation(10,30);
98
99 if (!zoomToEditLayerBoundingBox()) {
100 new AutoScaleAction("data").actionPerformed(null);
101 }
102
103 new MapMover(MapView.this, Main.contentPane);
104 JosmAction mv;
105 mv = new MoveAction(MoveAction.Direction.UP);
106 if (mv.getShortcut() != null) {
107 Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(mv.getShortcut().getKeyStroke(), "UP");
108 Main.contentPane.getActionMap().put("UP", mv);
109 }
110 mv = new MoveAction(MoveAction.Direction.DOWN);
111 if (mv.getShortcut() != null) {
112 Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(mv.getShortcut().getKeyStroke(), "DOWN");
113 Main.contentPane.getActionMap().put("DOWN", mv);
114 }
115 mv = new MoveAction(MoveAction.Direction.LEFT);
116 if (mv.getShortcut() != null) {
117 Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(mv.getShortcut().getKeyStroke(), "LEFT");
118 Main.contentPane.getActionMap().put("LEFT", mv);
119 }
120 mv = new MoveAction(MoveAction.Direction.RIGHT);
121 if (mv.getShortcut() != null) {
122 Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(mv.getShortcut().getKeyStroke(), "RIGHT");
123 Main.contentPane.getActionMap().put("RIGHT", mv);
124 }
125 }
126 });
127
128 // listend to selection changes to redraw the map
129 DataSet.selListeners.add(new SelectionChangedListener(){
130 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
131 repaint();
132 }
133 });
134
135 //store the last mouse action
136 this.addMouseMotionListener(new MouseMotionListener() {
137 public void mouseDragged(MouseEvent e) {
138 mouseMoved(e);
139 }
140 public void mouseMoved(MouseEvent e) {
141 lastMEvent = e;
142 }
143 });
144 }
145
146 /**
147 * Add a layer to the current MapView. The layer will be added at topmost
148 * position.
149 */
150 public void addLayer(Layer layer) {
151 if (layer instanceof OsmDataLayer) {
152 OsmDataLayer editLayer = (OsmDataLayer)layer;
153 editLayer.listenerModified.add(new ModifiedChangedListener(){
154 public void modifiedChanged(boolean value, OsmDataLayer source) {
155 JOptionPane.getFrameForComponent(Main.parent).setTitle((value?"*":"")
156 +tr("Java OpenStreetMap Editor"));
157 }
158 });
159 }
160 if (layer instanceof MarkerLayer && playHeadMarker == null) {
161 playHeadMarker = PlayHeadMarker.create();
162 }
163 int pos = layers.size();
164 while(pos > 0 && layers.get(pos-1).background) {
165 --pos;
166 }
167 layers.add(pos, layer);
168
169 for (Layer.LayerChangeListener l : Layer.listeners) {
170 l.layerAdded(layer);
171 }
172 if (layer instanceof OsmDataLayer || activeLayer == null) {
173 // autoselect the new layer
174 Layer old = activeLayer;
175 setActiveLayer(layer);
176 for (Layer.LayerChangeListener l : Layer.listeners) {
177 l.activeLayerChange(old, layer);
178 }
179 }
180 AudioPlayer.reset();
181 repaint();
182 }
183
184 @Override
185 protected DataSet getCurrentDataSet() {
186 if(activeLayer != null && activeLayer instanceof OsmDataLayer)
187 return ((OsmDataLayer)activeLayer).data;
188 return null;
189 }
190
191 /**
192 * Replies true if the active layer is drawable.
193 *
194 * @return true if the active layer is drawable, false otherwise
195 */
196 public boolean isActiveLayerDrawable() {
197 return activeLayer != null && activeLayer instanceof OsmDataLayer;
198 }
199
200 /**
201 * Replies true if the active layer is visible.
202 *
203 * @return true if the active layer is visible, false otherwise
204 */
205 public boolean isActiveLayerVisible() {
206 return isActiveLayerDrawable() && activeLayer.visible;
207 }
208
209 /**
210 * Remove the layer from the mapview. If the layer was in the list before,
211 * an LayerChange event is fired.
212 */
213 public void removeLayer(Layer layer) {
214 if (layer == activeLayer) {
215 activeLayer = null;
216 }
217 if (layers.remove(layer)) {
218 for (Layer.LayerChangeListener l : Layer.listeners) {
219 l.layerRemoved(layer);
220 }
221 }
222 layer.destroy();
223 AudioPlayer.reset();
224 }
225
226 private boolean virtualNodesEnabled = false;
227 public void setVirtualNodesEnabled(boolean enabled) {
228 if(virtualNodesEnabled != enabled) {
229 virtualNodesEnabled = enabled;
230 repaint();
231 }
232 }
233 public boolean isVirtualNodesEnabled() {
234 return virtualNodesEnabled;
235 }
236
237 /**
238 * Moves the layer to the given new position. No event is fired.
239 * @param layer The layer to move
240 * @param pos The new position of the layer
241 */
242 public void moveLayer(Layer layer, int pos) {
243 int curLayerPos = layers.indexOf(layer);
244 if (curLayerPos == -1)
245 throw new IllegalArgumentException(tr("layer not in list."));
246 if (pos == curLayerPos)
247 return; // already in place.
248 layers.remove(curLayerPos);
249 if (pos >= layers.size()) {
250 layers.add(layer);
251 } else {
252 layers.add(pos, layer);
253 }
254 AudioPlayer.reset();
255 }
256
257
258 public int getLayerPos(Layer layer) {
259 int curLayerPos = layers.indexOf(layer);
260 if (curLayerPos == -1)
261 throw new IllegalArgumentException(tr("layer not in list."));
262 return curLayerPos;
263 }
264
265 /**
266 * Draw the component.
267 */
268 @Override public void paint(Graphics g) {
269 if (center == null)
270 return; // no data loaded yet.
271
272 // re-create offscreen-buffer if we've been resized, otherwise
273 // just re-use it.
274 if (null == offscreenBuffer || offscreenBuffer.getWidth() != getWidth()
275 || offscreenBuffer.getHeight() != getHeight()) {
276 offscreenBuffer = new BufferedImage(getWidth(), getHeight(),
277 BufferedImage.TYPE_INT_ARGB);
278 }
279
280 Graphics2D tempG = offscreenBuffer.createGraphics();
281 tempG.setColor(Main.pref.getColor("background", Color.BLACK));
282 tempG.fillRect(0, 0, getWidth(), getHeight());
283
284 for (int i = layers.size()-1; i >= 0; --i) {
285 Layer l = layers.get(i);
286 if (l.visible/* && l != getActiveLayer()*/) {
287 l.paint(tempG, this);
288 }
289 }
290
291 /*if (getActiveLayer() != null && getActiveLayer().visible)
292 getActiveLayer().paint(tempG, this);*/
293
294 for (MapViewPaintable mvp : temporaryLayers) {
295 mvp.paint(tempG, this);
296 }
297
298 // draw world borders
299 tempG.setColor(Color.WHITE);
300 GeneralPath path = new GeneralPath();
301 Bounds b = getProjection().getWorldBoundsLatLon();
302 double lat = b.min.lat();
303 double lon = b.min.lon();
304
305 Point p = getPoint(b.min);
306 path.moveTo(p.x, p.y);
307
308 double max = b.max.lat();
309 for(; lat <= max; lat += 1.0)
310 {
311 p = getPoint(new LatLon(lat >= max ? max : lat, lon));
312 path.lineTo(p.x, p.y);
313 }
314 lat = max; max = b.max.lon();
315 for(; lon <= max; lon += 1.0)
316 {
317 p = getPoint(new LatLon(lat, lon >= max ? max : lon));
318 path.lineTo(p.x, p.y);
319 }
320 lon = max; max = b.min.lat();
321 for(; lat >= max; lat -= 1.0)
322 {
323 p = getPoint(new LatLon(lat <= max ? max : lat, lon));
324 path.lineTo(p.x, p.y);
325 }
326 lat = max; max = b.min.lon();
327 for(; lon >= max; lon -= 1.0)
328 {
329 p = getPoint(new LatLon(lat, lon <= max ? max : lon));
330 path.lineTo(p.x, p.y);
331 }
332
333 if (playHeadMarker != null) {
334 playHeadMarker.paint(tempG, this);
335 }
336 tempG.draw(path);
337
338 g.drawImage(offscreenBuffer, 0, 0, null);
339 super.paint(g);
340 }
341
342 /**
343 * Set the new dimension to the view.
344 */
345 public void recalculateCenterScale(BoundingXYVisitor box) {
346 if (box == null) {
347 box = new BoundingXYVisitor();
348 }
349 if (box.getBounds() == null) {
350 box.visit(getProjection().getWorldBoundsLatLon());
351 }
352 if (!box.hasExtend()) {
353 box.enlargeBoundingBox();
354 }
355
356 zoomTo(box.getBounds());
357 }
358
359 /**
360 * @return An unmodifiable collection of all layers
361 */
362 public Collection<Layer> getAllLayers() {
363 return Collections.unmodifiableCollection(layers);
364 }
365
366 /**
367 * Sets the active layer to <code>layer</code>. If <code>layer</code> is an instance
368 * of {@see OsmDataLayer} also sets {@see #editLayer} to <code>layer</code>.
369 *
370 * @param layer the layer to be activate; must be one of the layers in the list of layers
371 * @exception IllegalArgumentException thrown if layer is not in the lis of layers
372 */
373 public void setActiveLayer(Layer layer) {
374 if (!layers.contains(layer))
375 throw new IllegalArgumentException(tr("Layer ''{0}'' must be in list of layers", layer.toString()));
376 if (! (layer instanceof OsmDataLayer)) {
377 if (getCurrentDataSet() != null) {
378 getCurrentDataSet().setSelected();
379 DataSet.fireSelectionChanged(getCurrentDataSet().getSelected());
380 }
381 }
382 Layer old = activeLayer;
383 activeLayer = layer;
384 if (old != layer) {
385 for (Layer.LayerChangeListener l : Layer.listeners) {
386 l.activeLayerChange(old, layer);
387 }
388 }
389
390 /* This only makes the buttons look disabled. Disabling the actions as well requires
391 * the user to re-select the tool after i.e. moving a layer. While testing I found
392 * that I switch layers and actions at the same time and it was annoying to mind the
393 * order. This way it works as visual clue for new users */
394 for (Enumeration<AbstractButton> e = Main.map.toolGroup.getElements() ; e.hasMoreElements() ;) {
395 AbstractButton x=e.nextElement();
396 x.setEnabled(((MapMode)x.getAction()).layerIsSupported(layer));
397 }
398 AudioPlayer.reset();
399 repaint();
400 }
401
402 /**
403 * Replies the currently active layer
404 *
405 * @return the currently active layer (may be null)
406 */
407 public Layer getActiveLayer() {
408 return activeLayer;
409 }
410
411 /**
412 * Replies the current edit layer, if any
413 *
414 * @return the current edit layer. May be null.
415 */
416 public OsmDataLayer getEditLayer() {
417 if (activeLayer instanceof OsmDataLayer)
418 return (OsmDataLayer)activeLayer;
419
420 // the first OsmDataLayer is the edit layer
421 //
422 for (Layer layer : layers) {
423 if (layer instanceof OsmDataLayer)
424 return (OsmDataLayer)layer;
425 }
426 return null;
427 }
428
429 /**
430 * replies true if the list of layers managed by this map view contain layer
431 *
432 * @param layer the layer
433 * @return true if the list of layers managed by this map view contain layer
434 */
435 public boolean hasLayer(Layer layer) {
436 return layers.contains(layer);
437 }
438
439 /**
440 * Tries to zoom to the download boundingbox[es] of the current edit layer
441 * (aka {@link OsmDataLayer}). If the edit layer has multiple download bounding
442 * boxes it zooms to a large virtual bounding box containing all smaller ones.
443 * This implementation can be used for resolving ticket #1461.
444 *
445 * @return <code>true</code> if a zoom operation has been performed
446 */
447 public boolean zoomToEditLayerBoundingBox() {
448 // workaround for #1461 (zoom to download bounding box instead of all data)
449 // In case we already have an existing data layer ...
450 OsmDataLayer layer= getEditLayer();
451 if (layer == null)
452 return false;
453 Collection<DataSource> dataSources = layer.data.dataSources;
454 // ... with bounding box[es] of data loaded from OSM or a file...
455 BoundingXYVisitor bbox = new BoundingXYVisitor();
456 for (DataSource ds : dataSources) {
457 bbox.visit(ds.bounds);
458 if (bbox.hasExtend()) {
459 // ... we zoom to it's bounding box
460 recalculateCenterScale(bbox);
461 return true;
462 }
463 }
464 return false;
465 }
466
467 public boolean addTemporaryLayer(MapViewPaintable mvp) {
468 if (temporaryLayers.contains(mvp)) return false;
469 return temporaryLayers.add(mvp);
470 }
471
472 public boolean removeTemporaryLayer(MapViewPaintable mvp) {
473 return temporaryLayers.remove(mvp);
474 }
475}
Note: See TracBrowser for help on using the repository browser.