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

Last change on this file since 845 was 845, checked in by stoecker, 16 years ago

Prevent editing inactive layer. Fix layer oder and display order. Closes #371

  • Property svn:eol-style set to native
File size: 12.8 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.Transparency;
12import java.awt.event.MouseEvent;
13import java.awt.event.MouseMotionListener;
14import java.awt.event.ComponentAdapter;
15import java.awt.event.ComponentEvent;
16import java.awt.event.KeyEvent;
17import java.awt.image.BufferedImage;
18import java.util.ArrayList;
19import java.util.Collection;
20import java.util.Collections;
21import java.util.LinkedList;
22
23import javax.swing.JComponent;
24import javax.swing.JOptionPane;
25import javax.swing.KeyStroke;
26
27import org.openstreetmap.josm.Main;
28import org.openstreetmap.josm.actions.AutoScaleAction;
29import org.openstreetmap.josm.actions.MoveAction;
30import org.openstreetmap.josm.data.Bounds;
31import org.openstreetmap.josm.data.Preferences;
32import org.openstreetmap.josm.data.SelectionChangedListener;
33import org.openstreetmap.josm.data.coor.EastNorth;
34import org.openstreetmap.josm.data.coor.LatLon;
35import org.openstreetmap.josm.data.osm.DataSet;
36import org.openstreetmap.josm.data.osm.OsmPrimitive;
37import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
38import org.openstreetmap.josm.data.projection.Projection;
39import org.openstreetmap.josm.gui.layer.Layer;
40import org.openstreetmap.josm.gui.layer.MapViewPaintable;
41import org.openstreetmap.josm.gui.layer.OsmDataLayer;
42import org.openstreetmap.josm.gui.layer.OsmDataLayer.ModifiedChangedListener;
43import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
44import org.openstreetmap.josm.gui.layer.markerlayer.PlayHeadMarker;
45
46/**
47 * This is a component used in the MapFrame for browsing the map. It use is to
48 * provide the MapMode's enough capabilities to operate.
49 *
50 * MapView hold meta-data about the data set currently displayed, as scale level,
51 * center point viewed, what scrolling mode or editing mode is selected or with
52 * what projection the map is viewed etc..
53 *
54 * MapView is able to administrate several layers.
55 *
56 * @author imi
57 */
58public class MapView extends NavigatableComponent {
59
60 /**
61 * Interface to notify listeners of the change of the active layer.
62 * @author imi
63 * @deprecated Use Layer.LayerChangeListener instead
64 */
65 @Deprecated public interface LayerChangeListener {
66 void activeLayerChange(Layer oldLayer, Layer newLayer);
67 void layerAdded(Layer newLayer);
68 void layerRemoved(Layer oldLayer);
69 }
70
71 /**
72 * A list of all layers currently loaded.
73 */
74 private ArrayList<Layer> layers = new ArrayList<Layer>();
75 /**
76 * The play head marker: there is only one of these so it isn't in any specific layer
77 */
78 public PlayHeadMarker playHeadMarker = null;
79 /**
80 * Direct link to the edit layer (if any) in the layers list.
81 */
82 public OsmDataLayer editLayer;
83 /**
84 * The layer from the layers list that is currently active.
85 */
86 private Layer activeLayer;
87
88 /**
89 * The last event performed by mouse.
90 */
91 public MouseEvent lastMEvent;
92
93 private LinkedList<MapViewPaintable> temporaryLayers = new LinkedList<MapViewPaintable>();
94
95 private BufferedImage offscreenBuffer;
96
97 /**
98 * The listener of the active layer changes.
99 * @deprecated Use Layer.listener instead.
100 */
101 @Deprecated private Collection<LayerChangeListener> listeners = new LinkedList<LayerChangeListener>();
102
103 public MapView() {
104 addComponentListener(new ComponentAdapter(){
105 @Override public void componentResized(ComponentEvent e) {
106 removeComponentListener(this);
107
108 new AutoScaleAction("data").actionPerformed(null);
109
110 new MapMover(MapView.this, Main.contentPane);
111 Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, java.awt.event.InputEvent.SHIFT_MASK), "UP");
112 Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, java.awt.event.InputEvent.SHIFT_MASK), "DOWN");
113 Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, java.awt.event.InputEvent.SHIFT_MASK), "LEFT");
114 Main.contentPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, java.awt.event.InputEvent.SHIFT_MASK), "RIGHT");
115
116 Main.contentPane.getActionMap().put("UP", new MoveAction(MoveAction.Direction.UP));
117 Main.contentPane.getActionMap().put("DOWN", new MoveAction(MoveAction.Direction.DOWN));
118 Main.contentPane.getActionMap().put("LEFT", new MoveAction(MoveAction.Direction.LEFT));
119 Main.contentPane.getActionMap().put("RIGHT", new MoveAction(MoveAction.Direction.RIGHT));
120
121
122 MapSlider zoomSlider = new MapSlider(MapView.this);
123 add(zoomSlider);
124 zoomSlider.setBounds(3, 0, 114, 30);
125
126 MapScaler scaler = new MapScaler(MapView.this, Main.proj);
127 add(scaler);
128 scaler.setLocation(10,30);
129 }
130 });
131
132 // listend to selection changes to redraw the map
133 DataSet.selListeners.add(new SelectionChangedListener(){
134 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
135 repaint();
136 }
137 });
138
139 //store the last mouse action
140 this.addMouseMotionListener(new MouseMotionListener() {
141 public void mouseDragged(MouseEvent e) {
142 mouseMoved(e);
143 }
144 public void mouseMoved(MouseEvent e) {
145 lastMEvent = e;
146 }
147 });
148 }
149
150 /**
151 * Add a layer to the current MapView. The layer will be added at topmost
152 * position.
153 */
154 public void addLayer(Layer layer) {
155 if (layer instanceof OsmDataLayer) {
156 editLayer = (OsmDataLayer)layer;
157 Main.ds = editLayer.data;
158 editLayer.listenerModified.add(new ModifiedChangedListener(){
159 public void modifiedChanged(boolean value, OsmDataLayer source) {
160 JOptionPane.getFrameForComponent(Main.parent).setTitle((value?"*":"")+tr("Java OpenStreetMap - Editor"));
161 }
162 });
163 }
164 if (layer instanceof MarkerLayer && playHeadMarker == null)
165 playHeadMarker = PlayHeadMarker.create();
166
167 layers.add(layers.size(), layer);
168
169 // TODO: Deprecated
170 for (LayerChangeListener l : listeners)
171 l.layerAdded(layer);
172 for (Layer.LayerChangeListener l : Layer.listeners)
173 l.layerAdded(layer);
174 if (layer instanceof OsmDataLayer) {
175 // autoselect the new layer
176 Layer old = activeLayer;
177 setActiveLayer(layer);
178 for (Layer.LayerChangeListener l : Layer.listeners)
179 l.activeLayerChange(old, layer);
180 }
181 repaint();
182 }
183
184 @Override
185 protected DataSet getData()
186 {
187 if(activeLayer != null && activeLayer instanceof OsmDataLayer)
188 return ((OsmDataLayer)activeLayer).data;
189 return new DataSet();
190 }
191
192 public Boolean isDrawableLayer()
193 {
194 return activeLayer != null && activeLayer instanceof OsmDataLayer;
195 }
196
197 /**
198 * Remove the layer from the mapview. If the layer was in the list before,
199 * an LayerChange event is fired.
200 */
201 public void removeLayer(Layer layer) {
202 if (layers.remove(layer)) {
203 // TODO: Deprecated
204 for (LayerChangeListener l : listeners)
205 l.layerRemoved(layer);
206 for (Layer.LayerChangeListener l : Layer.listeners)
207 l.layerRemoved(layer);
208 }
209 if (layer == editLayer) {
210 editLayer = null;
211 Main.ds.setSelected();
212 }
213 layer.destroy();
214 }
215
216 private Boolean virtualnodes = false;
217 public void enableVirtualNodes(Boolean state)
218 {
219 if(virtualnodes != state)
220 {
221 virtualnodes = state;
222 repaint();
223 }
224 }
225 public Boolean useVirtualNodes()
226 {
227 return virtualnodes;
228 }
229
230 /**
231 * Moves the layer to the given new position. No event is fired.
232 * @param layer The layer to move
233 * @param pos The new position of the layer
234 */
235 public void moveLayer(Layer layer, int pos) {
236 int curLayerPos = layers.indexOf(layer);
237 if (curLayerPos == -1)
238 throw new IllegalArgumentException(tr("layer not in list."));
239 if (pos == curLayerPos)
240 return; // already in place.
241 layers.remove(curLayerPos);
242 if (pos >= layers.size())
243 layers.add(layer);
244 else
245 layers.add(pos, layer);
246 }
247
248 /**
249 * Draw the component.
250 */
251 @Override public void paint(Graphics g) {
252 if (center == null)
253 return; // no data loaded yet.
254
255 // re-create offscreen-buffer if we've been resized, otherwise
256 // just re-use it.
257 if (null == offscreenBuffer || offscreenBuffer.getWidth() != getWidth()
258 || offscreenBuffer.getHeight() != getHeight())
259 offscreenBuffer = new BufferedImage(getWidth(), getHeight(),
260 BufferedImage.TYPE_INT_ARGB);
261
262 Graphics2D tempG = offscreenBuffer.createGraphics();
263 tempG.setColor(Main.pref.getColor("background", Color.BLACK));
264 tempG.fillRect(0, 0, getWidth(), getHeight());
265
266 for (int i = layers.size()-1; i >= 0; --i) {
267 Layer l = layers.get(i);
268 if (l.visible/* && l != getActiveLayer()*/)
269 l.paint(tempG, this);
270 }
271
272 /*if (getActiveLayer() != null && getActiveLayer().visible)
273 getActiveLayer().paint(tempG, this);*/
274
275 for (MapViewPaintable mvp : temporaryLayers) {
276 mvp.paint(tempG, this);
277 }
278
279 // draw world borders
280 tempG.setColor(Color.WHITE);
281 Bounds b = new Bounds();
282 Point min = getPoint(getProjection().latlon2eastNorth(b.min));
283 Point max = getPoint(getProjection().latlon2eastNorth(b.max));
284 int x1 = Math.min(min.x, max.x);
285 int y1 = Math.min(min.y, max.y);
286 int x2 = Math.max(min.x, max.x);
287 int y2 = Math.max(min.y, max.y);
288 if (x1 > 0 || y1 > 0 || x2 < getWidth() || y2 < getHeight())
289 tempG.drawRect(x1, y1, x2-x1+1, y2-y1+1);
290
291 if (playHeadMarker != null)
292 playHeadMarker.paint(tempG, this);
293
294 g.drawImage(offscreenBuffer, 0, 0, null);
295 super.paint(g);
296 }
297
298 /**
299 * Set the new dimension to the projection class. Also adjust the components
300 * scale, if in autoScale mode.
301 */
302 public void recalculateCenterScale(BoundingXYVisitor box) {
303 // -20 to leave some border
304 int w = getWidth()-20;
305 if (w < 20)
306 w = 20;
307 int h = getHeight()-20;
308 if (h < 20)
309 h = 20;
310
311 EastNorth oldCenter = center;
312 double oldScale = this.scale;
313
314 if (box == null || box.min == null || box.max == null || box.min.equals(box.max)) {
315 // no bounds means whole world
316 center = getProjection().latlon2eastNorth(new LatLon(0,0));
317 EastNorth world = getProjection().latlon2eastNorth(new LatLon(Projection.MAX_LAT,Projection.MAX_LON));
318 double scaleX = world.east()*2/w;
319 double scaleY = world.north()*2/h;
320 scale = Math.max(scaleX, scaleY); // minimum scale to see all of the screen
321 } else {
322 center = new EastNorth(box.min.east()/2+box.max.east()/2, box.min.north()/2+box.max.north()/2);
323 double scaleX = (box.max.east()-box.min.east())/w;
324 double scaleY = (box.max.north()-box.min.north())/h;
325 scale = Math.max(scaleX, scaleY); // minimum scale to see all of the screen
326 }
327
328 if (!center.equals(oldCenter))
329 firePropertyChange("center", oldCenter, center);
330 if (oldScale != scale)
331 firePropertyChange("scale", oldScale, scale);
332 repaint();
333 }
334
335 /**
336 * Add a listener for changes of active layer.
337 * @param listener The listener that get added.
338 * @deprecated Use Layer.listener.add instead.
339 */
340 @Deprecated public void addLayerChangeListener(LayerChangeListener listener) {
341 if (listener != null)
342 listeners.add(listener);
343 }
344
345 /**
346 * Remove the listener.
347 * @param listener The listener that get removed from the list.
348 * @deprecated Use Layer.listener.remove instead
349 */
350 @Deprecated public void removeLayerChangeListener(LayerChangeListener listener) {
351 listeners.remove(listener);
352 }
353
354 /**
355 * @return An unmodificable list of all layers
356 */
357 public Collection<Layer> getAllLayers() {
358 return Collections.unmodifiableCollection(layers);
359 }
360
361 /**
362 * Set the active selection to the given value and raise an layerchange event.
363 */
364 public void setActiveLayer(Layer layer) {
365 if (!layers.contains(layer))
366 throw new IllegalArgumentException("Layer must be in layerlist");
367 if (layer instanceof OsmDataLayer) {
368 editLayer = (OsmDataLayer)layer;
369 Main.ds = editLayer.data;
370 }
371 else
372 Main.ds.setSelected();
373 DataSet.fireSelectionChanged(Main.ds.getSelected());
374 Layer old = activeLayer;
375 activeLayer = layer;
376 if (old != layer) {
377 // TODO: Deprecated
378 for (LayerChangeListener l : listeners)
379 l.activeLayerChange(old, layer);
380 for (Layer.LayerChangeListener l : Layer.listeners)
381 l.activeLayerChange(old, layer);
382 }
383 repaint();
384 }
385
386 /**
387 * @return The current active layer
388 */
389 public Layer getActiveLayer() {
390 return activeLayer;
391 }
392
393 /**
394 * In addition to the base class funcitonality, this keep trak of the autoscale
395 * feature.
396 */
397 @Override public void zoomTo(EastNorth newCenter, double scale) {
398 EastNorth oldCenter = center;
399 double oldScale = this.scale;
400 super.zoomTo(newCenter, scale);
401 if ((oldCenter == null && center != null) || !oldCenter.equals(center))
402 firePropertyChange("center", oldCenter, center);
403 if (oldScale != scale)
404 firePropertyChange("scale", oldScale, scale);
405 }
406
407 public boolean addTemporaryLayer(MapViewPaintable mvp) {
408 if (temporaryLayers.contains(mvp)) return false;
409 return temporaryLayers.add(mvp);
410 }
411
412 public boolean removeTemporaryLayer(MapViewPaintable mvp) {
413 return temporaryLayers.remove(mvp);
414 }
415}
Note: See TracBrowser for help on using the repository browser.