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

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

Added virtual nodes in select mode. Closes #595.

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