/*******************************************************************************
* Gaggle is Copyright 2010 by Geeksville Industries LLC, a California limited liability corporation.
*
* Gaggle is distributed under a dual license. We've chosen this approach because within Gaggle we've used a number
* of components that Geeksville Industries LLC might reuse for commercial products. Gaggle can be distributed under
* either of the two licenses listed below.
*
* 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 General Public License for more details.
*
* Commercial Distribution License
* If you would like to distribute Gaggle (or portions thereof) under a license other than
* the "GNU General Public License, version 2", contact Geeksville Industries. Geeksville Industries reserves
* the right to release Gaggle source code under a commercial license of its choice.
*
* GNU Public License, version 2
* All other distribution of Gaggle must conform to the terms of the GNU Public License, version 2. The full
* text of this license is included in the Gaggle source, see assets/manual/gpl-2.0.txt.
******************************************************************************/
package com.geeksville.gaggle;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import com.geeksville.location.LocationList;
import com.geeksville.util.MathUtil;
import com.geeksville.view.ViewUtil;
/**
* Draw an altitude plot
*
* @author kevinh
*
* FIXME: On tap, change horizontal scale
*/
public class AltitudeView extends View {
protected LocationList locs = new LocationList();
private Paint trackPaint = new Paint(), backgroundPaint = new Paint();
protected Paint labelPaint = new Paint();
private int maxY, minY;
public AltitudeView(Context context) {
super(context);
init();
}
public AltitudeView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public AltitudeView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
trackPaint.setColor(Color.GREEN);
trackPaint.setStyle(Style.STROKE);
trackPaint.setStrokeWidth(2);
labelPaint.setColor(Color.WHITE);
labelPaint.setTextAlign(Align.CENTER);
labelPaint.setTextSize(32.0f);
backgroundPaint = new Paint();
backgroundPaint.setARGB(128, 64, 64, 128); // lt gray translucent
backgroundPaint.setAntiAlias(true);
generatePoints();
}
/**
* Reset our min/max scale
*/
private void resetScaling() {
maxY = MathUtil.max(locs.altitudeMM.toUnsafeArray(), locs.altitudeMM.length());
minY = MathUtil.min(locs.altitudeMM.toUnsafeArray(), locs.altitudeMM.length());
}
/**
* Low level line segments for fast redrawing
*/
private float[] points = new float[4];
private int numLinesPoints;
private int oldNumPoints;
protected static final int border = 5;
/**
* Get the lowest time we are willing to plot
*
* @return
*/
protected int getMinX() {
// int[] times = locs.timeMsec.toUnsafeArray();
// int numpoints = locs.numPoints();
if (ViewUtil.isDesignTime(getContext()))
return 0;
return locs.timeMsec.get(0); // Plot everything
}
private void generatePoints() {
int numpoints = locs.numPoints();
int[] alts = locs.altitudeMM.toUnsafeArray();
int[] times = locs.timeMsec.toUnsafeArray();
if (ViewUtil.isDesignTime(getContext())) {
alts = new int[] { 1, 10, 30, 52, 25, 7 };
times = new int[] { 0, 20, 30, 40, 50, 60 };
numpoints = alts.length;
resetScaling();
}
// Could anything have possibly changed?
if (numpoints == oldNumPoints)
return;
// Make sure we have enough space for all our segments
int needed = (numpoints - 1) * 4;
if (points.length < needed)
points = new float[2 * needed];
int maxX = times[numpoints - 1];
int minX = getMinX();
int height = getHeight();
int width = getWidth() - 1;
int contentY = height - (2 * border);
int contentX = width - (2 * border);
int prevX = -1, prevY = 0;
// Generate a completely new set of line segments (because if we add
// even one point everything gets shifted over)
numLinesPoints = 0;
oldNumPoints = 0;
for (int i = oldNumPoints; i < numpoints; i++) {
int time = times[i];
// We only consider points newer than our min start time
if (time >= minX) {
int alt = alts[i];
minY = Math.min(alt, minY);
maxY = Math.max(alt, maxY);
// Until we have a change in Y no point drawing altitude (and
// prevent div by zero)
if (maxY != minY) {
// Flip Y so that higher values are towards the top
int ypos = border + contentY - contentY * (alt - minY) / (maxY - minY);
int xpos = (int) (border + contentX * ((long) time - minX) / (maxX - minX));
if (prevX != -1) {
points[numLinesPoints++] = prevX;
points[numLinesPoints++] = prevY;
points[numLinesPoints++] = xpos;
points[numLinesPoints++] = ypos;
}
prevX = xpos;
prevY = ypos;
}
}
}
oldNumPoints = numpoints;
}
protected void handleTap() {
// Do nothing
}
protected void drawLabels(Canvas canvas) {
}
@Override
protected void onDraw(Canvas canvas) {
drawBackground(canvas);
drawLabels(canvas);
generatePoints(); // Perhaps regenerate line segments
if (numLinesPoints == 0)
return;
canvas.drawLines(points, 0, numLinesPoints, trackPaint);
}
private void drawBackground(Canvas canvas) {
// Draw our grey background
RectF drawRect = new RectF();
drawRect.set(0, 0, getMeasuredWidth(), getMeasuredHeight());
canvas.drawRoundRect(drawRect, 5, 5, backgroundPaint);
}
/**
* @param locs
* the locs to set
*/
public void setLocs(LocationList locs) {
this.locs = locs;
resetScaling();
}
/**
* When tapped we cycle between time scales
*
* @see android.view.View#onTouchEvent(android.view.MotionEvent)
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
// The user just tapped on us
handleTap();
}
return super.onTouchEvent(event);
}
}