package uk.org.smithfamily.mslogger.dialog;
import java.util.ArrayList;
import java.util.List;
import uk.org.smithfamily.mslogger.ApplicationSettings;
import uk.org.smithfamily.mslogger.R;
import uk.org.smithfamily.mslogger.chart.ChartFactory;
import uk.org.smithfamily.mslogger.chart.GraphicalView;
import uk.org.smithfamily.mslogger.chart.chart.PointStyle;
import uk.org.smithfamily.mslogger.chart.model.TimeSeries;
import uk.org.smithfamily.mslogger.chart.model.XYMultipleSeriesDataset;
import uk.org.smithfamily.mslogger.chart.renderer.XYMultipleSeriesRenderer;
import uk.org.smithfamily.mslogger.chart.renderer.XYSeriesRenderer;
import uk.org.smithfamily.mslogger.ecuDef.Constant;
import uk.org.smithfamily.mslogger.ecuDef.CurveEditor;
import uk.org.smithfamily.mslogger.ecuDef.MSUtils;
import uk.org.smithfamily.mslogger.ecuDef.Megasquirt;
import uk.org.smithfamily.mslogger.log.DebugLogManager;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Typeface;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.text.method.DigitsKeyListener;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnFocusChangeListener;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TableLayout;
import android.widget.TableRow;
import android.widget.TableRow.LayoutParams;
import android.widget.TextView;
/**
*
*/
public class CurveHelper
{
private GraphicalView mChartView;
private CurveEditor curve;
private Context context;
private LinearLayout containerLayout;
private LinearLayout curveLayout;
private TableLayout tableLayout;
private int tableNbX = 0;
private int tableNbY = 0;
// The curve dataset
private XYMultipleSeriesDataset dataset;
private boolean isCurveDialog;
/**
*
* @param context
* @param curve
*/
public CurveHelper(Context context, CurveEditor curve, boolean isCurveDialog)
{
this.curve = curve;
this.context = context;
this.dataset = new XYMultipleSeriesDataset();
this.isCurveDialog = isCurveDialog;
buildLayout();
createTable();
drawCurve();
}
/**
* Get the curve editor associated with the curve
*
* @return
*/
public CurveEditor getCurveEditor()
{
return this.curve;
}
/**
* Build the layout for the curve
*/
private void buildLayout()
{
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
LinearLayout containerLayout = (LinearLayout) inflater.inflate(R.layout.curve, null);
this.containerLayout = containerLayout;
this.curveLayout = (LinearLayout) containerLayout.findViewById(R.id.curve);
this.tableLayout = (TableLayout) containerLayout.findViewById(R.id.table);
LinearLayout.LayoutParams lpCurve;
if (isCurveDialog)
{
lpCurve = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
}
else
{
Resources r = context.getResources();
int pxCurve = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 300, r.getDisplayMetrics());
lpCurve = new LinearLayout.LayoutParams(pxCurve, pxCurve);
int pxTableAndGauge = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 200, r.getDisplayMetrics());
LinearLayout.LayoutParams lpTableAndGauge = new LinearLayout.LayoutParams(pxTableAndGauge, LayoutParams.WRAP_CONTENT);
LinearLayout tableAndGauge = (LinearLayout) containerLayout.findViewById(R.id.tableAndGauge);
curveLayout.setLayoutParams(lpCurve);
tableAndGauge.setLayoutParams(lpTableAndGauge);
}
containerLayout.setWeightSum(1.0f);
containerLayout.setBaselineAligned(false);
containerLayout.setLayoutParams(lpCurve);
}
/**
* Return the main layout that contains the curve and the bins
*/
public LinearLayout getLayout()
{
return containerLayout;
}
/**
* Create the table of data that will be added beside the graph
*/
private void createTable()
{
LayoutParams lp = new LayoutParams(TableLayout.LayoutParams.FILL_PARENT, TableLayout.LayoutParams.WRAP_CONTENT);
tableNbX = 2;
tableNbY = curve.getyBins().length;
// Column headers
TableRow tableRow = new TableRow(context);
tableRow.setLayoutParams(lp);
Megasquirt ecu = ApplicationSettings.INSTANCE.getEcuDefinition();
final Constant xBinsConstant = ecu.getConstantByName(curve.getxBinsName());
final Constant yBinsConstant = ecu.getConstantByName(curve.getyBinsName());
for (int x = 1; x <= tableNbX; x++)
{
TextView columnHeader = new TextView(context);
String label = "";
if (x == 1)
{
label = curve.getxLabel();
// Add units, if they exists
if (xBinsConstant != null && !xBinsConstant.getUnits().equals(""))
{
label += " (" + xBinsConstant.getUnits() + ")";
}
}
else
{
label = curve.getyLabel();
// Add units, if they exists
if (!yBinsConstant.getUnits().equals(""))
{
label += " (" + yBinsConstant.getUnits() + ")";
}
}
columnHeader.setText(label);
columnHeader.setLayoutParams(lp);
columnHeader.setTypeface(null, Typeface.BOLD);
tableRow.addView(columnHeader);
}
// Add first row with the X and Y labels
tableLayout.addView(tableRow,lp);
// Add all the X and Y values in two columns
for (int y = 1; y <= tableNbY; y++)
{
tableRow = new TableRow(context);
tableRow.setLayoutParams(lp);
for (int x = 1; x <= tableNbX; x++)
{
EditText cell = new EditText(context);
double value;
// x = 1 is the X column
// x = 2 is the Y column
if (x == 1)
{
value = MSUtils.INSTANCE.roundDouble((curve.getxBins()[y - 1] + xBinsConstant.getTranslate()) * xBinsConstant.getScale(), xBinsConstant.getDigits());
}
else
{
value = MSUtils.INSTANCE.roundDouble((curve.getyBins()[y - 1] + yBinsConstant.getTranslate()) * yBinsConstant.getScale(), yBinsConstant.getDigits());
}
cell.setText(String.valueOf(value));
cell.setId(getCellId(x,y));
cell.setLayoutParams(lp);
cell.setInputType(InputType.TYPE_NUMBER_FLAG_DECIMAL);
cell.setSingleLine(true);
cell.setPadding(10, 10, 10, 10);
cell.setTag(new int[] {x - 1, y - 1});
// X is a constant we build in MSLogger, used for MS1 for Warmup Wizard, see DialogHelper.getStdDialog
// Make the cell read only
if (x == 1 && curve.getxBinsName().equals("MSLogger_temp"))
{
cell.setEnabled(false);
}
else
{
cell.setKeyListener(DigitsKeyListener.getInstance("0123456789."));
cell.setOnFocusChangeListener(new OnFocusChangeListener()
{
public void onFocusChange(View v, boolean hasFocus)
{
if (!hasFocus)
{
int[] xy = (int[]) ((EditText) v).getTag();
Constant constant;
if (xy[0] == 0)
{
constant = xBinsConstant;
}
else
{
constant = yBinsConstant;
}
DialogHelper.verifyOutOfBoundValue(context, constant, (EditText) v);
}
}
});
cell.addTextChangedListener(new CustomTextWatcher(cell));
}
// Add EditText to row
tableRow.addView(cell);
}
tableLayout.addView(tableRow,lp);
}
}
/**
* Private class that implements TextWatcher. This makes which EditText firing the event way less painful
*/
private class CustomTextWatcher implements TextWatcher
{
private EditText mEditText;
public CustomTextWatcher(EditText e)
{
mEditText = e;
}
public void afterTextChanged(Editable s)
{
Megasquirt ecu = ApplicationSettings.INSTANCE.getEcuDefinition();
final Constant xBinsConstant = ecu.getConstantByName(curve.getxBinsName());
final Constant yBinsConstant = ecu.getConstantByName(curve.getyBinsName());
int[] xy = (int[]) mEditText.getTag();
// X-bins
if (xy[0] == 0)
{
xBinsConstant.setModified(true);
int[] xBins = curve.getxBins();
try
{
xBins[xy[1]] = (int) Math.round(Double.parseDouble(mEditText.getText().toString()) / xBinsConstant.getScale() - xBinsConstant.getTranslate());
ecu.setVector(xBinsConstant.getName(), xBins);
}
catch (NumberFormatException e) {}
}
// Y-bins
else
{
yBinsConstant.setModified(true);
int[] yBins = curve.getyBins();
try
{
yBins[xy[1]] = (int) Math.round(Double.parseDouble(mEditText.getText().toString()) / yBinsConstant.getScale() - yBinsConstant.getTranslate());
ecu.setVector(yBinsConstant.getName(), yBins);
}
catch (NumberFormatException e) {}
}
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
public void onTextChanged(CharSequence s, int start, int before, int count) {}
}
/**
* Generate on the fly ID based on the cell position
*
* @param x
* @param y
* @return
*/
private int getCellId(int x, int y)
{
return x + (y * 1000);
}
/**
* Draw the actual curve
*/
private void drawCurve()
{
// Curve color
int colors[] = {Color.rgb(255, 0, 0)};
double[] xAxis = curve.getxAxis();
double[] yAxis = curve.getyAxis();
double minXaxis = xAxis[0];
double maxXaxis = xAxis[1];
double minYaxis = yAxis[0];
double maxYaxis = yAxis[1];
XYMultipleSeriesRenderer renderer = buildRenderer(1, colors);
setChartSettings(renderer, "", "", "", minXaxis, maxXaxis, minYaxis, maxYaxis, Color.GRAY, Color.LTGRAY);
renderer.setXTitle(curve.getxLabel());
renderer.setYTitle(curve.getyLabel(), 0);
renderer.setPanLimits(new double[] { minXaxis, maxXaxis, minYaxis, maxYaxis });
renderer.setShowLabels(true);
renderer.setClickEnabled(false);
renderer.setShowGrid(true);
renderer.setZoomEnabled(true);
renderer.setShowLegend(false);
// Make sure the curve points are filled
int length = renderer.getSeriesRendererCount();
for (int i = 0; i < length; i++)
{
((XYSeriesRenderer) renderer.getSeriesRendererAt(i)).setFillPoints(true);
}
if (mChartView == null)
{
updateDataset();
mChartView = ChartFactory.getLineChartView(context, dataset, renderer);
curveLayout.addView(mChartView, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
}
else
{
mChartView.repaint();
}
}
/**
* Function called to update the chart dataset with values that are in the table
*/
private void updateDataset()
{
boolean isUpdate = false;
// If it's not the first time we're building the dataset, remove the old one first
if (dataset.getSeries().length > 0)
{
dataset.removeSeries(0);
isUpdate = true;
}
List<double[]> x = new ArrayList<double[]>();
List<double[]> values = new ArrayList<double[]>();
double[] xBins = new double[curve.getxBins().length];
double[] yBins = new double[curve.getyBins().length];
// Build the arrays of values from the EditTexts
for (int i = 0; i < curve.getxBins().length; i++)
{
EditText edit = (EditText) tableLayout.findViewById(getCellId(1,i + 1));
xBins[i] = Double.valueOf(edit.getText().toString());
edit = (EditText) tableLayout.findViewById(getCellId(2,i + 1));
yBins[i] = Double.valueOf(edit.getText().toString());
}
x.add(xBins);
values.add(yBins);
// Create the new dataset
buildDateDataset(new String[]{""}, x, values);
// If it's not the first time we're doing this, trigger a refresh of the curve
if (isUpdate)
{
mChartView.refreshDrawableState();
mChartView.repaint();
}
}
/**
* Builds an XY multiple time dataset using the provided values.
*
* @param titles the series titles
* @param xValues the values for the X axis
* @param yValues the values for the Y axis
* @return the XY multiple time dataset
*/
private void buildDateDataset(String[] titles, List<double[]> xValues, List<double[]> yValues)
{
TimeSeries series = new TimeSeries(titles[0]);
double[] xV = xValues.get(0);
double[] yV = yValues.get(0);
int seriesLength = xV.length;
for (int k = 0; k < seriesLength; k++)
{
series.add(xV[k], yV[k]);
}
dataset.addSeries(series);
}
/**
* Builds an XY multiple series renderer.
*
* @param nbLines Number of lines
* @param colors Array of colors for each point
* @return The XY multiple series renderers
*/
private XYMultipleSeriesRenderer buildRenderer(int nbLines, int[] colors)
{
XYMultipleSeriesRenderer renderer = new XYMultipleSeriesRenderer();
setRenderer(renderer, nbLines, colors);
return renderer;
}
/**
* Set the renderer parameters for the line chart
*
* @param renderer
* @param nbLines
* @param colors
*/
private void setRenderer(XYMultipleSeriesRenderer renderer, int nbLines, int[] colors)
{
renderer.setAxisTitleTextSize(16);
renderer.setChartTitleTextSize(20);
renderer.setLabelsTextSize(15);
renderer.setLegendTextSize(15);
renderer.setPointSize(5f);
renderer.setMargins(new int[] { 20, 30, 15, 20 });
for (int i = 0; i < nbLines; i++)
{
XYSeriesRenderer r = new XYSeriesRenderer();
r.setColor(colors[i]);
r.setPointStyle(PointStyle.CIRCLE);
renderer.addSeriesRenderer(r);
}
}
/**
* Sets a few of the series renderer settings.
*
* @param renderer the renderer to set the properties to
* @param title the chart title
* @param xTitle the title for the X axis
* @param yTitle the title for the Y axis
* @param xMin the minimum value on the X axis
* @param xMax the maximum value on the X axis
* @param yMin the minimum value on the Y axis
* @param yMax the maximum value on the Y axis
* @param axesColor the axes color
* @param labelsColor the labels color
*/
private void setChartSettings(XYMultipleSeriesRenderer renderer, String title, String xTitle, String yTitle, double xMin, double xMax, double yMin, double yMax, int axesColor, int labelsColor)
{
renderer.setChartTitle(title);
renderer.setXTitle(xTitle);
renderer.setYTitle(yTitle);
renderer.setXAxisMin(xMin);
renderer.setXAxisMax(xMax);
renderer.setYAxisMin(yMin);
renderer.setYAxisMax(yMax);
renderer.setAxesColor(axesColor);
renderer.setLabelsColor(labelsColor);
}
/**
* Set all cells of curve table to enabled or disabled
*
* @param isPanelEnabled
*/
public void refreshFieldsVisibility(boolean isPanelEnabled)
{
for (int i = 0; i < getCellsCount(); i++)
{
EditText cell = getCellAt(i);
cell.setEnabled(isPanelEnabled);
}
}
/**
* @param index Index of the cell
* @return The cell (EditText) associated with the index
*/
private EditText getCellAt(int index)
{
int x = index % tableNbX + 1;
int y = index / tableNbX + 1;
return (EditText) tableLayout.findViewById(getCellId(x,y));
}
/**
* @return The number of cells for the table
*/
private int getCellsCount()
{
return tableNbX * tableNbY;
}
/**
* Write the constants of the curve to the ECU
*/
public void writeChangesToEcu()
{
Megasquirt ecu = ApplicationSettings.INSTANCE.getEcuDefinition();
Constant xBinsConstant = ecu.getConstantByName(curve.getxBinsName());
Constant yBinsConstant = ecu.getConstantByName(curve.getyBinsName());
if (xBinsConstant.isModified())
{
DebugLogManager.INSTANCE.log("Constant \"" + xBinsConstant.getName() + "\" was modified, need to write change to ECU", Log.DEBUG);
xBinsConstant.setModified(false);
ecu.writeConstant(xBinsConstant);
}
if (yBinsConstant.isModified())
{
DebugLogManager.INSTANCE.log("Constant \"" + yBinsConstant.getName() + "\" was modified, need to write change to ECU", Log.DEBUG);
yBinsConstant.setModified(false);
ecu.writeConstant(yBinsConstant);
}
}
}