// License: GPL. See LICENSE file for details.
package org.openstreetmap.josm.gui;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedList;
import javax.swing.AbstractButton;
import javax.swing.JComponent;
import javax.swing.JOptionPane;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.actions.AutoScaleAction;
import org.openstreetmap.josm.actions.JosmAction;
import org.openstreetmap.josm.actions.MoveAction;
import org.openstreetmap.josm.actions.mapmode.MapMode;
import org.openstreetmap.josm.data.Bounds;
import org.openstreetmap.josm.data.SelectionChangedListener;
import org.openstreetmap.josm.data.coor.EastNorth;
import org.openstreetmap.josm.data.coor.LatLon;
import org.openstreetmap.josm.data.osm.DataSet;
import org.openstreetmap.josm.data.osm.DataSource;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.data.osm.visitor.BoundingXYVisitor;
import org.openstreetmap.josm.data.projection.Projection;
import org.openstreetmap.josm.gui.layer.Layer;
import org.openstreetmap.josm.gui.layer.MapViewPaintable;
import org.openstreetmap.josm.gui.layer.OsmDataLayer;
import org.openstreetmap.josm.gui.layer.OsmDataLayer.ModifiedChangedListener;
import org.openstreetmap.josm.gui.layer.markerlayer.MarkerLayer;
import org.openstreetmap.josm.gui.layer.markerlayer.PlayHeadMarker;
import es.emergya.ui.gis.IMapView;
/**
* This is a component used in the MapFrame for browsing the map. It use is to
* provide the MapMode's enough capabilities to operate.
*
* MapView hold meta-data about the data set currently displayed, as scale
* level, center point viewed, what scrolling mode or editing mode is selected
* or with what projection the map is viewed etc..
*
* MapView is able to administrate several layers.
*
* @author imi
*/
public class MapView extends NavigatableComponent implements IMapView {
/**
* A list of all layers currently loaded.
*/
protected ArrayList<Layer> layers = new ArrayList<Layer>();
/**
* The play head marker: there is only one of these so it isn't in any
* specific layer
*/
public PlayHeadMarker playHeadMarker = null;
/**
* Direct link to the edit layer (if any) in the layers list.
*/
public OsmDataLayer editLayer;
/**
* The layer from the layers list that is currently active.
*/
protected Layer activeLayer;
/**
* The last event performed by mouse.
*/
public MouseEvent lastMEvent;
private LinkedList<MapViewPaintable> temporaryLayers = new LinkedList<MapViewPaintable>();
private BufferedImage offscreenBuffer;
public MapView() {
addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
removeComponentListener(this);
MapSlider zoomSlider = new MapSlider(MapView.this);
add(zoomSlider);
zoomSlider.setBounds(3, 0, 114, 30);
MapScaler scaler = new MapScaler(MapView.this, Main.proj);
add(scaler);
scaler.setLocation(10, 30);
if (!zoomToEditLayerBoundingBox())
new AutoScaleAction("data").actionPerformed(null);
new MapMover(MapView.this, Main.contentPane);
JosmAction mv;
mv = new MoveAction(MoveAction.Direction.UP);
if (mv.getShortcut() != null) {
Main.contentPane.getInputMap(
JComponent.WHEN_IN_FOCUSED_WINDOW).put(
mv.getShortcut().getKeyStroke(), "UP");
Main.contentPane.getActionMap().put("UP", mv);
}
mv = new MoveAction(MoveAction.Direction.DOWN);
if (mv.getShortcut() != null) {
Main.contentPane.getInputMap(
JComponent.WHEN_IN_FOCUSED_WINDOW).put(
mv.getShortcut().getKeyStroke(), "DOWN");
Main.contentPane.getActionMap().put("DOWN", mv);
}
mv = new MoveAction(MoveAction.Direction.LEFT);
if (mv.getShortcut() != null) {
Main.contentPane.getInputMap(
JComponent.WHEN_IN_FOCUSED_WINDOW).put(
mv.getShortcut().getKeyStroke(), "LEFT");
Main.contentPane.getActionMap().put("LEFT", mv);
}
mv = new MoveAction(MoveAction.Direction.RIGHT);
if (mv.getShortcut() != null) {
Main.contentPane.getInputMap(
JComponent.WHEN_IN_FOCUSED_WINDOW).put(
mv.getShortcut().getKeyStroke(), "RIGHT");
Main.contentPane.getActionMap().put("RIGHT", mv);
}
}
});
// listend to selection changes to redraw the map
DataSet.selListeners.add(new SelectionChangedListener() {
public void selectionChanged(
Collection<? extends OsmPrimitive> newSelection) {
repaint();
}
});
// store the last mouse action
this.addMouseMotionListener(new MouseMotionListener() {
public void mouseDragged(MouseEvent e) {
mouseMoved(e);
}
public void mouseMoved(MouseEvent e) {
lastMEvent = e;
}
});
}
/*
* (non-Javadoc)
*
* @see
* org.openstreetmap.josm.gui.IMapView#addLayer(org.openstreetmap.josm.gui
* .layer.Layer)
*/
@Override
public void addLayer(Layer layer) {
addLayer(layer, 0);
}
/*
* (non-Javadoc)
*
* @see
* org.openstreetmap.josm.gui.IMapView#addLayer(org.openstreetmap.josm.gui
* .layer.Layer, int)
*/
@Override
public void addLayer(Layer layer, int pos) {
if (layer instanceof OsmDataLayer) {
editLayer = (OsmDataLayer) layer;
Main.ds = editLayer.data;
editLayer.listenerModified.add(new ModifiedChangedListener() {
public void modifiedChanged(boolean value, OsmDataLayer source) {
JOptionPane.getFrameForComponent(Main.parent).setTitle(
(value ? "*" : "")
+ tr("Java OpenStreetMap Editor"));
}
});
}
if (layer instanceof MarkerLayer && playHeadMarker == null)
playHeadMarker = PlayHeadMarker.create();
// int pos = 0;//layers.size();
// while(pos > 0 && layers.get(pos-1).background)
// --pos;
layers.add(pos, layer);
for (Layer.LayerChangeListener l : Layer.listeners)
l.layerAdded(layer);
if (layer instanceof OsmDataLayer || activeLayer == null) {
// autoselect the new layer
Layer old = activeLayer;
setActiveLayer(layer);
for (Layer.LayerChangeListener l : Layer.listeners)
l.activeLayerChange(old, layer);
}
repaint();
}
@Override
protected DataSet getData() {
if (activeLayer != null && activeLayer instanceof OsmDataLayer)
return ((OsmDataLayer) activeLayer).data;
return new DataSet();
}
/*
* (non-Javadoc)
*
* @see org.openstreetmap.josm.gui.IMapView#isDrawableLayer()
*/
@Override
public Boolean isDrawableLayer() {
return activeLayer != null && activeLayer instanceof OsmDataLayer;
}
/*
* (non-Javadoc)
*
* @see org.openstreetmap.josm.gui.IMapView#isVisibleDrawableLayer()
*/
@Override
public Boolean isVisibleDrawableLayer() {
return isDrawableLayer() && activeLayer.visible;
}
/*
* (non-Javadoc)
*
* @see
* org.openstreetmap.josm.gui.IMapView#removeLayer(org.openstreetmap.josm
* .gui.layer.Layer)
*/
@Override
public void removeLayer(Layer layer) {
if (layers.remove(layer)) {
for (Layer.LayerChangeListener l : Layer.listeners)
l.layerRemoved(layer);
}
if (layer == editLayer) {
editLayer = null;
Main.ds.setSelected();
}
layer.destroy();
}
private Boolean virtualnodes = false;
/*
* (non-Javadoc)
*
* @see
* org.openstreetmap.josm.gui.IMapView#enableVirtualNodes(java.lang.Boolean)
*/
@Override
public void enableVirtualNodes(Boolean state) {
if (virtualnodes != state) {
virtualnodes = state;
repaint();
}
}
/*
* (non-Javadoc)
*
* @see org.openstreetmap.josm.gui.IMapView#useVirtualNodes()
*/
@Override
public Boolean useVirtualNodes() {
return virtualnodes;
}
/*
* (non-Javadoc)
*
* @see
* org.openstreetmap.josm.gui.IMapView#moveLayer(org.openstreetmap.josm.
* gui.layer.Layer, int)
*/
@Override
public void moveLayer(Layer layer, int pos) {
int curLayerPos = layers.indexOf(layer);
if (curLayerPos == -1)
throw new IllegalArgumentException(tr("layer not in list."));
if (pos == curLayerPos)
return; // already in place.
layers.remove(curLayerPos);
if (pos >= layers.size())
layers.add(layer);
else
layers.add(pos, layer);
}
/*
* (non-Javadoc)
*
* @see
* org.openstreetmap.josm.gui.IMapView#getLayerPos(org.openstreetmap.josm
* .gui.layer.Layer)
*/
@Override
public int getLayerPos(Layer layer) {
int curLayerPos = layers.indexOf(layer);
if (curLayerPos == -1)
throw new IllegalArgumentException(tr("layer not in list."));
return curLayerPos;
}
/*
* (non-Javadoc)
*
* @see org.openstreetmap.josm.gui.IMapView#paint(java.awt.Graphics)
*/
@Override
public void paint(Graphics g) {
if (center == null)
return; // no data loaded yet.
// re-create offscreen-buffer if we've been resized, otherwise
// just re-use it.
if (null == offscreenBuffer || offscreenBuffer.getWidth() != getWidth()
|| offscreenBuffer.getHeight() != getHeight())
offscreenBuffer = new BufferedImage(getWidth(), getHeight(),
BufferedImage.TYPE_INT_ARGB);
Graphics2D tempG = offscreenBuffer.createGraphics();
tempG.setColor(Main.pref.getColor("background", Color.BLACK));
tempG.fillRect(0, 0, getWidth(), getHeight());
for (int i = layers.size() - 1; i >= 0; --i) {
Layer l = layers.get(i);
if (l.visible/* && l != getActiveLayer() */)
l.paint(tempG, this);
}
/*
* if (getActiveLayer() != null && getActiveLayer().visible)
* getActiveLayer().paint(tempG, this);
*/
for (MapViewPaintable mvp : temporaryLayers) {
mvp.paint(tempG, this);
}
// draw world borders
tempG.setColor(Color.WHITE);
Bounds b = new Bounds();
Point min = getPoint(getProjection().latlon2eastNorth(b.min));
Point max = getPoint(getProjection().latlon2eastNorth(b.max));
int x1 = Math.min(min.x, max.x);
int y1 = Math.min(min.y, max.y);
int x2 = Math.max(min.x, max.x);
int y2 = Math.max(min.y, max.y);
if (x1 > 0 || y1 > 0 || x2 < getWidth() || y2 < getHeight())
tempG.drawRect(x1, y1, x2 - x1 + 1, y2 - y1 + 1);
if (playHeadMarker != null)
playHeadMarker.paint(tempG, this);
g.drawImage(offscreenBuffer, 0, 0, null);
super.paint(g);
}
/*
* (non-Javadoc)
*
* @see
* org.openstreetmap.josm.gui.IMapView#recalculateCenterScale(org.openstreetmap
* .josm.data.osm.visitor.BoundingXYVisitor)
*/
@Override
public void recalculateCenterScale(BoundingXYVisitor box) {
// -20 to leave some border
int w = getWidth() - 20;
if (w < 20)
w = 20;
int h = getHeight() - 20;
if (h < 20)
h = 20;
EastNorth oldCenter = center;
double oldScale = this.scale;
if (box == null || box.min == null || box.max == null) {
// no bounds means whole world
center = getProjection().latlon2eastNorth(new LatLon(0, 0));
EastNorth world = getProjection().latlon2eastNorth(
new LatLon(Projection.MAX_LAT, Projection.MAX_LON));
double scaleX = world.east() * 2 / w;
double scaleY = world.north() * 2 / h;
scale = Math.max(scaleX, scaleY); // minimum scale to see all of the
// screen
} else {
if (box.min.equals(box.max))
box.enlargeBoundingBox();
center = new EastNorth(box.min.east() / 2 + box.max.east() / 2,
box.min.north() / 2 + box.max.north() / 2);
double scaleX = (box.max.east() - box.min.east()) / w;
double scaleY = (box.max.north() - box.min.north()) / h;
scale = Math.max(scaleX, scaleY); // minimum scale to see all of the
// screen
}
if (!center.equals(oldCenter))
firePropertyChange("center", oldCenter, center);
if (oldScale != scale)
firePropertyChange("scale", oldScale, scale);
repaint();
}
/*
* (non-Javadoc)
*
* @see org.openstreetmap.josm.gui.IMapView#getAllLayers()
*/
@Override
public Collection<Layer> getAllLayers() {
return Collections.unmodifiableCollection(layers);
}
/*
* (non-Javadoc)
*
* @see
* org.openstreetmap.josm.gui.IMapView#setActiveLayer(org.openstreetmap.
* josm.gui.layer.Layer)
*/
@Override
public void setActiveLayer(Layer layer) {
if (!layers.contains(layer))
throw new IllegalArgumentException("Layer must be in layerlist");
if (layer instanceof OsmDataLayer) {
editLayer = (OsmDataLayer) layer;
Main.ds = editLayer.data;
} else
Main.ds.setSelected();
DataSet.fireSelectionChanged(Main.ds.getSelected());
Layer old = activeLayer;
activeLayer = layer;
if (old != layer) {
for (Layer.LayerChangeListener l : Layer.listeners)
l.activeLayerChange(old, layer);
}
/*
* This only makes the buttons look disabled. Disabling the actions as
* well requires the user to re-select the tool after i.e. moving a
* layer. While testing I found that I switch layers and actions at the
* same time and it was annoying to mind the order. This way it works as
* visual clue for new users
*/
for (Enumeration<AbstractButton> e = Main.map.toolGroup.getElements(); e
.hasMoreElements();) {
AbstractButton x = e.nextElement();
x.setEnabled(((MapMode) x.getAction()).layerIsSupported(layer));
}
repaint();
}
/*
* (non-Javadoc)
*
* @see org.openstreetmap.josm.gui.IMapView#getActiveLayer()
*/
@Override
public Layer getActiveLayer() {
return activeLayer;
}
/*
* (non-Javadoc)
*
* @see
* org.openstreetmap.josm.gui.IMapView#zoomTo(org.openstreetmap.josm.data
* .coor.EastNorth, double)
*/
@Override
public void zoomTo(EastNorth newCenter, double scale) {
EastNorth oldCenter = center;
double oldScale = this.scale;
super.zoomTo(newCenter, scale);
if ((oldCenter == null && center != null) || !oldCenter.equals(center))
firePropertyChange("center", oldCenter, center);
if (oldScale != scale)
firePropertyChange("scale", oldScale, scale);
}
/*
* (non-Javadoc)
*
* @see org.openstreetmap.josm.gui.IMapView#zoomToEditLayerBoundingBox()
*/
@Override
public boolean zoomToEditLayerBoundingBox() {
// workaround for #1461 (zoom to download bounding box instead of all
// data)
// In case we already have an existing data layer ...
Collection<DataSource> dataSources = Main.main.editLayer().data.dataSources;
// ... with bounding box[es] of data loaded from OSM or a file...
BoundingXYVisitor bbox = new BoundingXYVisitor();
for (DataSource ds : dataSources) {
if (ds.bounds != null) {
bbox.visit(Main.proj.latlon2eastNorth(ds.bounds.max));
bbox.visit(Main.proj.latlon2eastNorth(ds.bounds.min));
}
if (bbox.max != null && bbox.min != null
&& !bbox.max.equals(bbox.min)) {
// ... we zoom to it's bounding box
recalculateCenterScale(bbox);
return true;
}
}
return false;
}
/*
* (non-Javadoc)
*
* @see
* org.openstreetmap.josm.gui.IMapView#addTemporaryLayer(org.openstreetmap
* .josm.gui.layer.MapViewPaintable)
*/
@Override
public boolean addTemporaryLayer(MapViewPaintable mvp) {
if (temporaryLayers.contains(mvp))
return false;
return temporaryLayers.add(mvp);
}
/*
* (non-Javadoc)
*
* @see
* org.openstreetmap.josm.gui.IMapView#removeTemporaryLayer(org.openstreetmap
* .josm.gui.layer.MapViewPaintable)
*/
@Override
public boolean removeTemporaryLayer(MapViewPaintable mvp) {
return temporaryLayers.remove(mvp);
}
}