/*
* Copyright 2010, 2011, 2012 mapsforge.org
*
* This program is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation, either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mapsforge.android.maps;
import org.mapsforge.android.maps.mapgenerator.MapGenerator;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.widget.LinearLayout.LayoutParams;
import android.widget.ZoomControls;
/**
* A MapZoomControls instance displays buttons for zooming in and out in a map.
*/
public class MapZoomControls {
private static class ZoomControlsHideHandler extends Handler {
private final ZoomControls zoomControls;
ZoomControlsHideHandler(ZoomControls zoomControls) {
super();
this.zoomControls = zoomControls;
}
@Override
public void handleMessage(Message message) {
this.zoomControls.hide();
}
}
private static class ZoomInClickListener implements View.OnClickListener {
private final MapView mapView;
ZoomInClickListener(MapView mapView) {
this.mapView = mapView;
}
@Override
public void onClick(View view) {
this.mapView.zoom((byte) 1, 1);
}
}
private static class ZoomOutClickListener implements View.OnClickListener {
private final MapView mapView;
ZoomOutClickListener(MapView mapView) {
this.mapView = mapView;
}
@Override
public void onClick(View view) {
this.mapView.zoom((byte) -1, 1);
}
}
/**
* Default {@link Gravity} of the zoom controls.
*/
private static final int DEFAULT_ZOOM_CONTROLS_GRAVITY = Gravity.BOTTOM | Gravity.RIGHT;
/**
* Default maximum zoom level.
*/
private static final byte DEFAULT_ZOOM_LEVEL_MAX = 22;
/**
* Default minimum zoom level.
*/
private static final byte DEFAULT_ZOOM_LEVEL_MIN = 0;
/**
* Message code for the handler to hide the zoom controls.
*/
private static final int MSG_ZOOM_CONTROLS_HIDE = 0;
/**
* Horizontal padding for the zoom controls.
*/
private static final int ZOOM_CONTROLS_HORIZONTAL_PADDING = 5;
/**
* Delay in milliseconds after which the zoom controls disappear.
*/
private static final long ZOOM_CONTROLS_TIMEOUT = ViewConfiguration.getZoomControlsTimeout();
private boolean gravityChanged;
private boolean showMapZoomControls;
private final ZoomControls zoomControls;
private int zoomControlsGravity;
private final Handler zoomControlsHideHandler;
private byte zoomLevelMax;
private byte zoomLevelMin;
MapZoomControls(Context context, final MapView mapView) {
this.zoomControls = new ZoomControls(context);
this.showMapZoomControls = true;
this.zoomLevelMax = DEFAULT_ZOOM_LEVEL_MAX;
this.zoomLevelMin = DEFAULT_ZOOM_LEVEL_MIN;
this.zoomControls.setVisibility(View.GONE);
this.zoomControlsGravity = DEFAULT_ZOOM_CONTROLS_GRAVITY;
this.zoomControls.setOnZoomInClickListener(new ZoomInClickListener(mapView));
this.zoomControls.setOnZoomOutClickListener(new ZoomOutClickListener(mapView));
this.zoomControlsHideHandler = new ZoomControlsHideHandler(this.zoomControls);
int wrapContent = android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
LayoutParams layoutParams = new LayoutParams(wrapContent, wrapContent);
mapView.addView(this.zoomControls, layoutParams);
}
/**
* @return the current gravity for the placing of the zoom controls.
* @see Gravity
*/
public int getZoomControlsGravity() {
return this.zoomControlsGravity;
}
/**
* @return the maximum zoom level of the map.
*/
public byte getZoomLevelMax() {
return this.zoomLevelMax;
}
/**
* @return the minimum zoom level of the map.
*/
public byte getZoomLevelMin() {
return this.zoomLevelMin;
}
/**
* @return true if the zoom controls are visible, false otherwise.
*/
public boolean isShowMapZoomControls() {
return this.showMapZoomControls;
}
/**
* @param showMapZoomControls
* true if the zoom controls should be visible, false otherwise.
*/
public void setShowMapZoomControls(boolean showMapZoomControls) {
this.showMapZoomControls = showMapZoomControls;
}
/**
* Sets the gravity for the placing of the zoom controls. Supported values are {@link Gravity#TOP},
* {@link Gravity#CENTER_VERTICAL}, {@link Gravity#BOTTOM}, {@link Gravity#LEFT}, {@link Gravity#CENTER_HORIZONTAL}
* and {@link Gravity#RIGHT}.
*
* @param zoomControlsGravity
* a combination of {@link Gravity} constants describing the desired placement.
*/
public void setZoomControlsGravity(int zoomControlsGravity) {
if (this.zoomControlsGravity != zoomControlsGravity) {
this.zoomControlsGravity = zoomControlsGravity;
this.gravityChanged = true;
}
}
/**
* Sets the maximum zoom level of the map.
* <p>
* The maximum possible zoom level of the MapView depends also on the current {@link MapGenerator}. For example,
* downloading map tiles may only be possible up to a certain zoom level. Setting a higher maximum zoom level has no
* effect in this case.
*
* @param zoomLevelMax
* the maximum zoom level.
* @throws IllegalArgumentException
* if the maximum zoom level is smaller than the current minimum zoom level.
*/
public void setZoomLevelMax(byte zoomLevelMax) {
if (zoomLevelMax < this.zoomLevelMin) {
throw new IllegalArgumentException();
}
this.zoomLevelMax = zoomLevelMax;
}
/**
* Sets the minimum zoom level of the map.
*
* @param zoomLevelMin
* the minimum zoom level.
* @throws IllegalArgumentException
* if the minimum zoom level is larger than the current maximum zoom level.
*/
public void setZoomLevelMin(byte zoomLevelMin) {
if (zoomLevelMin > this.zoomLevelMax) {
throw new IllegalArgumentException();
}
this.zoomLevelMin = zoomLevelMin;
}
private int calculatePositionLeft(int left, int right, int zoomControlsWidth) {
int gravity = this.zoomControlsGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
switch (gravity) {
case Gravity.LEFT:
return ZOOM_CONTROLS_HORIZONTAL_PADDING;
case Gravity.CENTER_HORIZONTAL:
return (right - left - zoomControlsWidth) / 2;
case Gravity.RIGHT:
return right - left - zoomControlsWidth - ZOOM_CONTROLS_HORIZONTAL_PADDING;
}
throw new IllegalArgumentException("unknown horizontal gravity: " + gravity);
}
private int calculatePositionTop(int top, int bottom, int zoomControlsHeight) {
int gravity = this.zoomControlsGravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (gravity) {
case Gravity.TOP:
return 0;
case Gravity.CENTER_VERTICAL:
return (bottom - top - zoomControlsHeight) / 2;
case Gravity.BOTTOM:
return bottom - top - zoomControlsHeight;
}
throw new IllegalArgumentException("unknown vertical gravity: " + gravity);
}
private void showZoomControls() {
this.zoomControlsHideHandler.removeMessages(MSG_ZOOM_CONTROLS_HIDE);
if (this.zoomControls.getVisibility() != View.VISIBLE) {
this.zoomControls.show();
}
}
private void showZoomControlsWithTimeout() {
showZoomControls();
this.zoomControlsHideHandler.sendEmptyMessageDelayed(MSG_ZOOM_CONTROLS_HIDE, ZOOM_CONTROLS_TIMEOUT);
}
int getMeasuredHeight() {
return this.zoomControls.getMeasuredHeight();
}
int getMeasuredWidth() {
return this.zoomControls.getMeasuredWidth();
}
void measure(int widthMeasureSpec, int heightMeasureSpec) {
this.zoomControls.measure(widthMeasureSpec, heightMeasureSpec);
}
void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (!changed && !this.gravityChanged) {
return;
}
int zoomControlsWidth = this.zoomControls.getMeasuredWidth();
int zoomControlsHeight = this.zoomControls.getMeasuredHeight();
int positionLeft = calculatePositionLeft(left, right, zoomControlsWidth);
int positionTop = calculatePositionTop(top, bottom, zoomControlsHeight);
int positionRight = positionLeft + zoomControlsWidth;
int positionBottom = positionTop + zoomControlsHeight;
this.zoomControls.layout(positionLeft, positionTop, positionRight, positionBottom);
this.gravityChanged = false;
}
void onMapViewTouchEvent(int action) {
if (this.showMapZoomControls) {
switch (action) {
case MotionEvent.ACTION_DOWN:
showZoomControls();
break;
case MotionEvent.ACTION_CANCEL:
showZoomControlsWithTimeout();
break;
case MotionEvent.ACTION_UP:
showZoomControlsWithTimeout();
break;
}
}
}
void onZoomLevelChange(int zoomLevel) {
boolean zoomInEnabled = zoomLevel < this.zoomLevelMax;
boolean zoomOutEnabled = zoomLevel > this.zoomLevelMin;
this.zoomControls.setIsZoomInEnabled(zoomInEnabled);
this.zoomControls.setIsZoomOutEnabled(zoomOutEnabled);
}
}