/*
CanZE
Take a closer look at your ZE car
Copyright (C) 2015 - The CanZE Team
http://canze.fisch.lu
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package lu.fisch.canze.widgets;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.Point;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.Display;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
import java.lang.reflect.Constructor;
import lu.fisch.awt.Color;
import lu.fisch.awt.Graphics;
import lu.fisch.canze.activities.CanzeActivity;
import lu.fisch.canze.actors.Field;
import lu.fisch.canze.classes.ColorRanges;
import lu.fisch.canze.classes.Intervals;
import lu.fisch.canze.classes.Options;
import lu.fisch.canze.interfaces.DrawSurfaceInterface;
import lu.fisch.canze.activities.MainActivity;
import lu.fisch.canze.R;
import lu.fisch.canze.activities.WidgetActivity;
public class WidgetView extends SurfaceView implements DrawSurfaceInterface, SurfaceHolder.Callback, View.OnTouchListener {
// a reference to the drawing thread
private DrawThread drawThread = null;
// your application certainly needs some data model
private Drawable drawable = null;
private String fieldSID = "";
private boolean clickable = true;
protected boolean landscape = true;
private CanzeActivity canzeActivity = null;
// for data sharing
public static Drawable selectedDrawable = null;
public void setDrawable(Drawable drawable)
{
this.drawable=drawable;
//if(drawable.getDrawSurface()==null)
drawable.setDrawSurface(this);
repaint();
}
public Drawable getDrawable()
{
return drawable;
}
public WidgetView(Context context) {
super(context);
init(context, null);
setOnTouchListener(this);
}
public WidgetView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
setOnTouchListener(this);
}
public WidgetView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
setOnTouchListener(this);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
landscape = (right-left)>(bottom-top);
if(changed) drawable.onLayout(landscape);
}
public void reset()
{
drawable.reset();
repaint();
}
public String extractCarValue(String[] values)
{
// the first value is the default one
String carValue = values[0];
for(int i=1; i<values.length; i++)
if(values[i].startsWith(String.valueOf(MainActivity.car)+":"))
carValue=values[i].split(":")[1];
return carValue;
}
public void init(final Context context, AttributeSet attrs)
{
// register our interest in hearing about changes to our surface
SurfaceHolder holder = getHolder();
holder.addCallback(this);
// make sure we get key events
setFocusable(true);
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
// read attributes
if(attrs!=null)
{
try
{
// create configured widget
String[] widgets = {"Tacho","Kompass", "Bar","BatteryBar","Plotter","Label","Timeplot","BarGraph"};
TypedArray attributes = context.getTheme().obtainStyledAttributes(attrs, R.styleable.WidgetView, 0, 0);
int widgetIndex = attributes.getInt(R.styleable.WidgetView_widget, 0);
if(widgetIndex<widgets.length)
{
String widget = widgets[widgetIndex];
//MainActivity.debug("WidgetView: I am a "+widget);
Class clazz = Class.forName("lu.fisch.canze.widgets." + widget);
//Constructor<?> constructor = clazz.getConstructor(null);
Constructor<?> constructor = clazz.getConstructor();
drawable = (Drawable) constructor.newInstance();
drawable.setDrawSurface(WidgetView.this);
// apply attributes
drawable.setMin(Integer.valueOf(extractCarValue(attributes.getString(R.styleable.WidgetView_min).split(","))));
drawable.setMax(Integer.valueOf(extractCarValue(attributes.getString(R.styleable.WidgetView_max).split(","))));
//drawable.setMin(attributes.getInt(R.styleable.WidgetView_min, 0));
//drawable.setMax(attributes.getInt(R.styleable.WidgetView_max, 0));
drawable.setMajorTicks(Integer.valueOf(extractCarValue(attributes.getString(R.styleable.WidgetView_majorTicks).split(","))));
drawable.setMinorTicks(Integer.valueOf(extractCarValue(attributes.getString(R.styleable.WidgetView_minorTicks).split(","))));
//drawable.setMajorTicks(attributes.getInt(R.styleable.WidgetView_majorTicks, 0));
//drawable.setMinorTicks(attributes.getInt(R.styleable.WidgetView_minorTicks, 0));
drawable.setTitle(attributes.getString(R.styleable.WidgetView_text));
drawable.setShowLabels(attributes.getBoolean(R.styleable.WidgetView_showLabels, true));
drawable.setShowValue(attributes.getBoolean(R.styleable.WidgetView_showValue, true));
drawable.setInverted(attributes.getBoolean(R.styleable.WidgetView_isInverted, false));
String colorRangesJson =attributes.getString(R.styleable.WidgetView_colorRanges);
if(colorRangesJson!=null && !colorRangesJson.trim().isEmpty())
drawable.setColorRanges(new ColorRanges(colorRangesJson.replace("'", "\"")));
String foreground =attributes.getString(R.styleable.WidgetView_foregroundColor);
if(foreground!=null && !foreground.isEmpty())
drawable.setForeground(Color.decode(foreground));
String background =attributes.getString(R.styleable.WidgetView_backgroundColor);
if(background!=null && !background.isEmpty())
drawable.setBackground(Color.decode(background));
String intermediate =attributes.getString(R.styleable.WidgetView_intermediateColor);
if(intermediate!=null && !intermediate.isEmpty())
drawable.setIntermediate(Color.decode(intermediate));
String titleColor =attributes.getString(R.styleable.WidgetView_titleColor);
if(titleColor!=null && !titleColor.isEmpty())
drawable.setTitleColor(Color.decode(titleColor));
String intervalJson =attributes.getString(R.styleable.WidgetView_intervals);
if(intervalJson!=null && !intervalJson.trim().isEmpty())
drawable.setIntervals(new Intervals(intervalJson.replace("'", "\"")));
String optionsJson =attributes.getString(R.styleable.WidgetView_options);
if(optionsJson!=null && !optionsJson.trim().isEmpty())
drawable.setOptions(new Options(optionsJson.replace("'", "\"")));
//drawable.setMinAlt(attributes.getInt(R.styleable.WidgetView_minAlt, -1));
//drawable.setMaxAlt(attributes.getInt(R.styleable.WidgetView_maxAlt, -1));
String minAlt = attributes.getString(R.styleable.WidgetView_minAlt);
if(minAlt!=null && !minAlt.trim().isEmpty())
drawable.setMinAlt(Integer.valueOf(extractCarValue(minAlt.split(","))));
String maxAlt = attributes.getString(R.styleable.WidgetView_maxAlt);
if(maxAlt!=null && !maxAlt.trim().isEmpty())
drawable.setMaxAlt(Integer.valueOf(extractCarValue(maxAlt.split(","))));
drawable.setTimeScale(attributes.getInt(R.styleable.WidgetView_timeScale,1));
fieldSID = attributes.getString(R.styleable.WidgetView_fieldSID);
if(fieldSID!=null) {
String[] sids = fieldSID.split(",");
for (int s = 0; s < sids.length; s++) {
Field field = MainActivity.fields.getBySID(sids[s]);
if (field == null) {
MainActivity.debug("WidgetView: init: Field with SID <" + sids[s] + "> (index <" + s + "> in <" + R.styleable.WidgetView_text + "> not found!");
} else {
// add field to list of registered sids for this widget
drawable.addField(field.getSID());
// add listener
field.addListener(drawable);
// add filter to reader
int interval = drawable.getIntervals().getInterval(field.getSID());
if (interval == -1)
MainActivity.device.addActivityField(field);
else
MainActivity.device.addActivityField(field, interval);
}
}
}
//MainActivity.debug("WidgetView: My SID is "+fieldSID);
if(MainActivity.milesMode) drawable.setTitle(drawable.getTitle().replace("km","mi"));
}
else
{
MainActivity.debug("WidgetView: init: WidgetIndex " + widgetIndex + " is wrong!? Not registered in <WidgetView>?");
}
}
catch(Exception e)
{
e.printStackTrace();
}
}
// in case your application needs one or more timers,
// you have to put them here
/*Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
repaint();
}
}, 100, 100);
*/
}
private boolean motionDown = false;
private boolean motionMove = false;
private float downX, downY;
@Override
public boolean onTouch(View v, MotionEvent event)
{
// react on touch events
// get pointer index from the event object
int pointerIndex = event.getActionIndex();
// get pointer ID
int pointerId = event.getPointerId(pointerIndex);
// get masked (not specific to a pointer) action
int maskedAction = event.getActionMasked();
MainActivity.debug("WidgetView: maskedAction = " + maskedAction);
switch (maskedAction) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN:{
motionDown=true;
downX=event.getX();
downY=event.getY();
break;
}
case MotionEvent.ACTION_MOVE: {
if (Math.abs(downX - event.getX()) + Math.abs(downY - event.getY()) > 20) {
motionMove=true;
}
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_POINTER_UP:
{
if(!motionMove && clickable && MainActivity.isSafe()) {
canzeActivity.setWidgetClicked(true);
Intent intent = new Intent(this.getContext(), WidgetActivity.class);
selectedDrawable = this.getDrawable();
this.getContext().startActivity(intent);
}
motionDown=false;
motionMove=false;
break;
}
case MotionEvent.ACTION_CANCEL: {
break;
}
}
invalidate();
return true;
}
@Override
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
}
@Override
public void surfaceCreated(SurfaceHolder arg0)
{
// load data from the database
(new Thread(new Runnable() {
@Override
public void run() {
drawable.loadValuesFromDatabase();
repaint();
}
})).start();
}
// DIRECT repaint method
public void repaint2() {
Canvas c = null;
try {
c = getHolder().lockCanvas();
if (c != null) {
// enable anti-aliasing
c.setDrawFilter(new PaintFlagsDrawFilter(1, Paint.ANTI_ALIAS_FLAG));
// clean background
Paint paint = new Paint();
paint.setColor(drawable.getBackground().getAndroidColor());
c.drawRect(0, 0, c.getWidth(), c.getHeight(), paint);
// set dimensions
drawable.setWidth(getWidth());
drawable.setHeight(getHeight());
// do the drawing
drawable.draw(new Graphics(c));
}
}
catch(Exception e)
{
// ignore
}
finally
{
if (c != null) {
getHolder().unlockCanvasAndPost(c);
}
}
}
// INDIRECT repaint method (using a separate thread
public void repaint()
{
if(drawThread==null || !drawThread.isRunning())
{
// gargabe collect
System.gc();
// post a task to the UI thread
this.post(new Runnable() {
@Override
public void run() {
// create a new drawThread
drawThread = new DrawThread(getHolder(), getContext(), new Handler() {
@Override
public void handleMessage(Message m) {
}
});
// call the setter for the pointer to the model
if (drawable != null) {
drawable.setWidth(getWidth());
drawable.setHeight(getHeight());
// draw the widget
drawThread.setDrawable(drawable);
}
// start the thread
drawThread.start();
}
});
}
}
@Override
public void surfaceDestroyed(SurfaceHolder arg0)
{
// stop the drawThread properly
boolean retry = true;
while (retry)
{
try
{
// wait for it to finish
if(drawThread!=null && drawThread.isRunning())
drawThread.join();
retry = false;
}
catch (InterruptedException e)
{
// ignore any error
e.printStackTrace();
}
}
// set it to null, so that a new one can be created in case of a resume
drawThread=null;
// set parent
//if(canzeActivity!=null)
// canzeActivity.setWidgetClicked(false);
}
/* *************************************
* Getter & Setter
* *************************************/
public String getFieldSID()
{
return fieldSID;
}
@Override
public boolean isClickable() {
return clickable;
}
@Override
public void setClickable(boolean clickable) {
this.clickable = clickable;
}
public void setFieldSID(String fieldSID) {
this.fieldSID = fieldSID;
}
public void setCanzeActivity(CanzeActivity canzeActivity) {
this.canzeActivity = canzeActivity;
}
}