blob: b9e3458ed25478b9215d0a03b51dd85c7bf25402 [file] [log] [blame]
package com.xtremelabs.robolectric.shadows;
import android.content.Context;
import android.graphics.Point;
import android.view.MotionEvent;
import android.widget.ZoomButtonsController;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;
import com.google.android.maps.Projection;
import com.xtremelabs.robolectric.Robolectric;
import com.xtremelabs.robolectric.internal.Implementation;
import com.xtremelabs.robolectric.internal.Implements;
import java.util.ArrayList;
import java.util.List;
import static com.xtremelabs.robolectric.RobolectricForMaps.shadowOf;
/**
* Shadow of {@code MapView} that simulates the internal state of a {@code MapView}. Supports {@code Projection}s,
* {@code Overlay}s, and {@code TouchEvent}s
*/
@SuppressWarnings({"UnusedDeclaration"})
@Implements(MapView.class)
public class ShadowMapView extends ShadowViewGroup {
private boolean satelliteOn;
private MapController mapController;
private List<Overlay> overlays = new ArrayList<Overlay>();
GeoPoint mapCenter = new GeoPoint(10, 10);
int longitudeSpan = 20;
int latitudeSpan = 30;
int zoomLevel = 1;
private ShadowMapController shadowMapController;
private ZoomButtonsController zoomButtonsController;
private MapView realMapView;
private Projection projection;
private boolean useBuiltInZoomMapControls;
private boolean mouseDownOnMe = false;
private Point lastTouchEventPoint;
private GeoPoint mouseDownCenter;
private boolean preLoadWasCalled;
private boolean canCoverCenter = true;
public ShadowMapView(MapView mapView) {
realMapView = mapView;
zoomButtonsController = new ZoomButtonsController(mapView);
}
public void __constructor__(Context context, String title) {
super.__constructor__(context);
}
public static int toE6(double d) {
return (int) (d * 1e6);
}
public static double fromE6(int i) {
return i / 1e6;
}
@Implementation
public void setSatellite(boolean satelliteOn) {
this.satelliteOn = satelliteOn;
}
@Implementation
public boolean isSatellite() {
return satelliteOn;
}
@Implementation
public boolean canCoverCenter() {
return canCoverCenter;
}
@Implementation
public MapController getController() {
if (mapController == null) {
try {
mapController = Robolectric.newInstanceOf(MapController.class);
shadowMapController = shadowOf(mapController);
shadowMapController.setShadowMapView(this);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return mapController;
}
@Implementation
public ZoomButtonsController getZoomButtonsController() {
return zoomButtonsController;
}
@Implementation
public void setBuiltInZoomControls(boolean useBuiltInZoomMapControls) {
this.useBuiltInZoomMapControls = useBuiltInZoomMapControls;
}
@Implementation
public com.google.android.maps.Projection getProjection() {
if (projection == null) {
projection = new Projection() {
@Override public Point toPixels(GeoPoint geoPoint, Point point) {
if (point == null) {
point = new Point();
}
point.y = scaleDegree(geoPoint.getLatitudeE6(), bottom, top, mapCenter.getLatitudeE6(), latitudeSpan);
point.x = scaleDegree(geoPoint.getLongitudeE6(), left, right, mapCenter.getLongitudeE6(), longitudeSpan);
return point;
}
@Override public GeoPoint fromPixels(int x, int y) {
int lat = scalePixel(y, bottom, -realMapView.getHeight(), mapCenter.getLatitudeE6(), latitudeSpan);
int lng = scalePixel(x, left, realMapView.getWidth(), mapCenter.getLongitudeE6(), longitudeSpan);
return new GeoPoint(lat, lng);
}
@Override public float metersToEquatorPixels(float v) {
return 0;
}
};
}
return projection;
}
private int scalePixel(int pixel, int minPixel, int maxPixel, int centerDegree, int spanDegrees) {
int offsetPixels = pixel - minPixel;
double ratio = offsetPixels / ((double) maxPixel);
int minDegrees = centerDegree - spanDegrees / 2;
return (int) (minDegrees + spanDegrees * ratio);
}
private int scaleDegree(int degree, int minPixel, int maxPixel, int centerDegree, int spanDegrees) {
int minDegree = centerDegree - spanDegrees / 2;
int offsetDegrees = degree - minDegree;
double ratio = offsetDegrees / ((double) spanDegrees);
int spanPixels = maxPixel - minPixel;
return (int) (minPixel + spanPixels * ratio);
}
@Implementation
public List<Overlay> getOverlays() {
return overlays;
}
@Implementation
public GeoPoint getMapCenter() {
return mapCenter;
}
@Implementation
public int getLatitudeSpan() {
return latitudeSpan;
}
@Implementation
public int getLongitudeSpan() {
return longitudeSpan;
}
@Implementation
public int getZoomLevel() {
return zoomLevel;
}
@Implementation
@Override public boolean dispatchTouchEvent(MotionEvent event) {
for (Overlay overlay : overlays) {
if (overlay.onTouchEvent(event, realMapView)) {
return true;
}
}
GeoPoint mouseGeoPoint = getProjection().fromPixels((int) event.getX(), (int) event.getY());
int diffX = 0;
int diffY = 0;
if (mouseDownOnMe) {
diffX = (int) event.getX() - lastTouchEventPoint.x;
diffY = (int) event.getY() - lastTouchEventPoint.y;
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mouseDownOnMe = true;
mouseDownCenter = getMapCenter();
break;
case MotionEvent.ACTION_MOVE:
if (mouseDownOnMe) {
moveByPixels(-diffX, -diffY);
}
break;
case MotionEvent.ACTION_UP:
if (mouseDownOnMe) {
moveByPixels(-diffX, -diffY);
mouseDownOnMe = false;
}
break;
case MotionEvent.ACTION_CANCEL:
getController().setCenter(mouseDownCenter);
mouseDownOnMe = false;
break;
}
lastTouchEventPoint = new Point((int) event.getX(), (int) event.getY());
return super.dispatchTouchEvent(event);
}
@Implementation
public void preLoad() {
preLoadWasCalled = true;
}
private void moveByPixels(int x, int y) {
Point center = getProjection().toPixels(mapCenter, null);
center.offset(x, y);
mapCenter = getProjection().fromPixels(center.x, center.y);
}
/**
* Non-Android accessor.
*
* @return whether to use built in zoom map controls
*/
public boolean getUseBuiltInZoomMapControls() {
return useBuiltInZoomMapControls;
}
/**
* Non-Android accessor.
*
* @return whether {@link #preLoad()} has been called on this {@code MapView}
*/
public boolean preLoadWasCalled() {
return preLoadWasCalled;
}
/**
* Non-Android accessor to set the latitude span (the absolute value of the difference between the Northernmost and
* Southernmost latitudes visible on the map) of this {@code MapView}
*
* @param latitudeSpan the new latitude span for this {@code MapView}
*/
public void setLatitudeSpan(int latitudeSpan) {
this.latitudeSpan = latitudeSpan;
}
/**
* Non-Android accessor to set the longitude span (the absolute value of the difference between the Easternmost and
* Westernmost longitude visible on the map) of this {@code MapView}
*
* @param longitudeSpan the new latitude span for this {@code MapView}
*/
public void setLongitudeSpan(int longitudeSpan) {
this.longitudeSpan = longitudeSpan;
}
/**
* Non-Android accessor that controls the value to be returned by {@link #canCoverCenter()}
*
* @param canCoverCenter the value to be returned by {@link #canCoverCenter()}
*/
public void setCanCoverCenter(boolean canCoverCenter) {
this.canCoverCenter = canCoverCenter;
}
}