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

Last change on this file since 811 was 811, checked in by david, 16 years ago

paste/duplicate at mouse position: patch on behalf of Petr Dlouhý, petr.dlouhyat at email dot cz

  • Property svn:eol-style set to native
File size: 12.4 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 // autoselect the new layer
175 Layer old = activeLayer;
176 setActiveLayer(layer);
177 for (Layer.LayerChangeListener l : Layer.listeners)
178 l.activeLayerChange(old, layer);
179 repaint();
180 }
181
182 /**
183 * Remove the layer from the mapview. If the layer was in the list before,
184 * an LayerChange event is fired.
185 */
186 public void removeLayer(Layer layer) {
187 if (layers.remove(layer)) {
188 // TODO: Deprecated
189 for (LayerChangeListener l : listeners)
190 l.layerRemoved(layer);
191 for (Layer.LayerChangeListener l : Layer.listeners)
192 l.layerRemoved(layer);
193 }
194 if (layer == editLayer) {
195 editLayer = null;
196 Main.ds.setSelected();
197 }
198 layer.destroy();
199 }
200
201 private Boolean virtualnodes = false;
202 public void enableVirtualNodes(Boolean state)
203 {
204 if(virtualnodes != state)
205 {
206 virtualnodes = state;
207 repaint();
208 }
209 }
210 public Boolean useVirtualNodes()
211 {
212 return virtualnodes;
213 }
214
215 /**
216 * Moves the layer to the given new position. No event is fired.
217 * @param layer The layer to move
218 * @param pos The new position of the layer
219 */
220 public void moveLayer(Layer layer, int pos) {
221 int curLayerPos = layers.indexOf(layer);
222 if (curLayerPos == -1)
223 throw new IllegalArgumentException(tr("layer not in list."));
224 if (pos == curLayerPos)
225 return; // already in place.
226 layers.remove(curLayerPos);
227 if (pos >= layers.size())
228 layers.add(layer);
229 else
230 layers.add(pos, layer);
231 }
232
233 /**
234 * Draw the component.
235 */
236 @Override public void paint(Graphics g) {
237 if (center == null)
238 return; // no data loaded yet.
239
240 // re-create offscreen-buffer if we've been resized, otherwise
241 // just re-use it.
242 if (null == offscreenBuffer || offscreenBuffer.getWidth() != getWidth()
243 || offscreenBuffer.getHeight() != getHeight())
244 offscreenBuffer = new BufferedImage(getWidth(), getHeight(),
245 BufferedImage.TYPE_INT_ARGB);
246
247 Graphics2D tempG = offscreenBuffer.createGraphics();
248 tempG.setColor(Main.pref.getColor("background", Color.BLACK));
249 tempG.fillRect(0, 0, getWidth(), getHeight());
250
251 for (int i = layers.size()-1; i >= 0; --i) {
252 Layer l = layers.get(i);
253 if (l.visible && l != getActiveLayer())
254 l.paint(tempG, this);
255 }
256
257 if (getActiveLayer() != null && getActiveLayer().visible)
258 getActiveLayer().paint(tempG, this);
259
260 for (MapViewPaintable mvp : temporaryLayers) {
261 mvp.paint(tempG, this);
262 }
263
264 // draw world borders
265 tempG.setColor(Color.WHITE);
266 Bounds b = new Bounds();
267 Point min = getPoint(getProjection().latlon2eastNorth(b.min));
268 Point max = getPoint(getProjection().latlon2eastNorth(b.max));
269 int x1 = Math.min(min.x, max.x);
270 int y1 = Math.min(min.y, max.y);
271 int x2 = Math.max(min.x, max.x);
272 int y2 = Math.max(min.y, max.y);
273 if (x1 > 0 || y1 > 0 || x2 < getWidth() || y2 < getHeight())
274 tempG.drawRect(x1, y1, x2-x1+1, y2-y1+1);
275
276 if (playHeadMarker != null)
277 playHeadMarker.paint(tempG, this);
278
279 g.drawImage(offscreenBuffer, 0, 0, null);
280 super.paint(g);
281 }
282
283 /**
284 * Set the new dimension to the projection class. Also adjust the components
285 * scale, if in autoScale mode.
286 */
287 public void recalculateCenterScale(BoundingXYVisitor box) {
288 // -20 to leave some border
289 int w = getWidth()-20;
290 if (w < 20)
291 w = 20;
292 int h = getHeight()-20;
293 if (h < 20)
294 h = 20;
295
296 EastNorth oldCenter = center;
297 double oldScale = this.scale;
298
299 if (box == null || box.min == null || box.max == null || box.min.equals(box.max)) {
300 // no bounds means whole world
301 center = getProjection().latlon2eastNorth(new LatLon(0,0));
302 EastNorth world = getProjection().latlon2eastNorth(new LatLon(Projection.MAX_LAT,Projection.MAX_LON));
303 double scaleX = world.east()*2/w;
304 double scaleY = world.north()*2/h;
305 scale = Math.max(scaleX, scaleY); // minimum scale to see all of the screen
306 } else {
307 center = new EastNorth(box.min.east()/2+box.max.east()/2, box.min.north()/2+box.max.north()/2);
308 double scaleX = (box.max.east()-box.min.east())/w;
309 double scaleY = (box.max.north()-box.min.north())/h;
310 scale = Math.max(scaleX, scaleY); // minimum scale to see all of the screen
311 }
312
313 if (!center.equals(oldCenter))
314 firePropertyChange("center", oldCenter, center);
315 if (oldScale != scale)
316 firePropertyChange("scale", oldScale, scale);
317 repaint();
318 }
319
320 /**
321 * Add a listener for changes of active layer.
322 * @param listener The listener that get added.
323 * @deprecated Use Layer.listener.add instead.
324 */
325 @Deprecated public void addLayerChangeListener(LayerChangeListener listener) {
326 if (listener != null)
327 listeners.add(listener);
328 }
329
330 /**
331 * Remove the listener.
332 * @param listener The listener that get removed from the list.
333 * @deprecated Use Layer.listener.remove instead
334 */
335 @Deprecated public void removeLayerChangeListener(LayerChangeListener listener) {
336 listeners.remove(listener);
337 }
338
339 /**
340 * @return An unmodificable list of all layers
341 */
342 public Collection<Layer> getAllLayers() {
343 return Collections.unmodifiableCollection(layers);
344 }
345
346 /**
347 * Set the active selection to the given value and raise an layerchange event.
348 */
349 public void setActiveLayer(Layer layer) {
350 if (!layers.contains(layer))
351 throw new IllegalArgumentException("Layer must be in layerlist");
352 if (layer instanceof OsmDataLayer) {
353 editLayer = (OsmDataLayer)layer;
354 Main.ds = editLayer.data;
355 DataSet.fireSelectionChanged(Main.ds.getSelected());
356 }
357 Layer old = activeLayer;
358 activeLayer = layer;
359 if (old != layer) {
360 // TODO: Deprecated
361 for (LayerChangeListener l : listeners)
362 l.activeLayerChange(old, layer);
363 for (Layer.LayerChangeListener l : Layer.listeners)
364 l.activeLayerChange(old, layer);
365 }
366 repaint();
367 }
368
369 /**
370 * @return The current active layer
371 */
372 public Layer getActiveLayer() {
373 return activeLayer;
374 }
375
376 /**
377 * In addition to the base class funcitonality, this keep trak of the autoscale
378 * feature.
379 */
380 @Override public void zoomTo(EastNorth newCenter, double scale) {
381 EastNorth oldCenter = center;
382 double oldScale = this.scale;
383 super.zoomTo(newCenter, scale);
384 if ((oldCenter == null && center != null) || !oldCenter.equals(center))
385 firePropertyChange("center", oldCenter, center);
386 if (oldScale != scale)
387 firePropertyChange("scale", oldScale, scale);
388 }
389
390 public boolean addTemporaryLayer(MapViewPaintable mvp) {
391 if (temporaryLayers.contains(mvp)) return false;
392 return temporaryLayers.add(mvp);
393 }
394
395 public boolean removeTemporaryLayer(MapViewPaintable mvp) {
396 return temporaryLayers.remove(mvp);
397 }
398}
Note: See TracBrowser for help on using the repository browser.