package uk.org.smithfamily.mslogger.dialog;
import uk.org.smithfamily.mslogger.ApplicationSettings;
import uk.org.smithfamily.mslogger.R;
import uk.org.smithfamily.mslogger.ecuDef.Constant;
import uk.org.smithfamily.mslogger.ecuDef.MSUtils;
import uk.org.smithfamily.mslogger.ecuDef.Megasquirt;
import uk.org.smithfamily.mslogger.ecuDef.OutputChannel;
import uk.org.smithfamily.mslogger.ecuDef.TableEditor;
import uk.org.smithfamily.mslogger.log.DebugLogManager;
import android.content.Context;
import android.graphics.Color;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.text.method.DigitsKeyListener;
import android.util.FloatMath;
import android.util.Log;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
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 TableHelper
{
private TableEditor table;
private Context context;
private int tableNbX = 0;
private int tableNbY = 0;
// An array of 5 colors: (blue, cyan, green, yellow, red) using {R,G,B} for each
private static final float COLORS[][] = {{0, 0, 255}, {0, 255, 255}, {0, 255, 0}, {255, 255, 0}, {255, 0, 0}};
private static final int NUM_COLORS = 5;
private float currentTableMin = 0;
private float currentTableMax = 0;
private LinearLayout containerLayout;
private TextView xBinsLabel;
private TextView yBinsLabel;
private TextView tableLabel;
private TableLayout tableLayout;
private boolean isTableDialog;
/**
* Construtor that trigger the build of the table
*
* @param context
* @param table
* @param isTableDialog
*/
public TableHelper(Context context, TableEditor table, boolean isTableDialog)
{
this.table = table;
this.context = context;
this.isTableDialog = isTableDialog;
buildLayout();
drawTable();
}
/**
* Get the table editor associated with the table
*
* @return
*/
public TableEditor getTableEditor()
{
return this.table;
}
/**
* Build the layout for the table
*/
private void buildLayout()
{
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
LinearLayout containerLayout = (LinearLayout) inflater.inflate(R.layout.table, null);
LinearLayout.LayoutParams containerLayoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.FILL_PARENT);
containerLayout.setBaselineAligned(false);
containerLayout.setOrientation(LinearLayout.VERTICAL);
containerLayout.setWeightSum(1.0f);
containerLayout.setLayoutParams(containerLayoutParams);
this.containerLayout = containerLayout;
this.xBinsLabel = (TextView) containerLayout.findViewById(R.id.xBinsLabel);
this.yBinsLabel = (TextView) containerLayout.findViewById(R.id.yBinsLabel);
this.tableLabel = (TextView) containerLayout.findViewById(R.id.tableLabel);
this.tableLayout = (TableLayout) containerLayout.findViewById(R.id.tableLayout);
// If the generated table is a table dialog
if (isTableDialog)
{
tableLabel.setVisibility(View.GONE);
}
}
/**
* Return the container layout of the 2D table
*/
public LinearLayout getLayout()
{
return containerLayout;
}
/**
* Helper function that take a string as input and add a \n after each letter to use in text view so the text is vertical
*
* @param text String to convert
* @return Converted string
*/
private String convertToVerticalTextView(String text)
{
StringBuffer output = new StringBuffer();
for (int i = 0; i < text.length(); i++)
{
output.append(text.substring(i, i + 1) + "\n");
}
return output.toString();
}
/**
* Create all the headers and cells to generate the 2D table into the main table layout
*/
private void drawTable()
{
Megasquirt ecu = ApplicationSettings.INSTANCE.getEcuDefinition();
int[][] zBins = ecu.getArray(table.getzBins());
// Get table constant
Constant tableConstant = ecu.getConstantByName(table.getzBins());
this.tableNbX = zBins.length;
this.tableNbY = zBins[0].length;
// X and Y axis labels
OutputChannel xOutputChannel = ecu.getOutputChannelByName(table.getxOutputChannel());
OutputChannel yOutputChannel = ecu.getOutputChannelByName(table.getyOutputChannel());
String labelText = table.getxOutputChannel();
if (xOutputChannel != null)
{
labelText += " (" + xOutputChannel.getUnits() + ")";
}
xBinsLabel.setText(labelText);
labelText = table.getyOutputChannel();
if (yOutputChannel != null)
{
labelText += " " + yOutputChannel.getUnits();
}
yBinsLabel.setLineSpacing(0, 0.9f);
yBinsLabel.setText(convertToVerticalTextView(labelText));
LayoutParams lp = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
LayoutParams rowHeaderLayout = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
rowHeaderLayout.setMargins(0, 0, 10, 0);
// Add label on top of table if this table won't be used as a dialog with a single table in it,
// otherwise it looks weird.
if (!isTableDialog)
{
tableLabel.setText(table.getLabel());
}
TableRow tableRow;
int xBins[] = table.getxBins();
int yBins[] = table.getyBins();
int xDigits = 0;
if (xOutputChannel != null)
{
xDigits = getDigitsFromScale(xOutputChannel.getScale());
}
int yDigits = 0;
if (yOutputChannel != null)
{
yDigits = getDigitsFromScale(yOutputChannel.getScale());
}
for (int y = 1; y <= tableNbY; y++)
{
tableRow = new TableRow(context);
tableRow.setLayoutParams(lp);
// Row header
TextView rowHeader = new TextView(context);
String headerLabel = "";
if (yDigits == 0)
{
headerLabel = String.valueOf((int) yBins[tableNbY - y]);
}
else
{
headerLabel = String.valueOf(MSUtils.INSTANCE.roundDouble(yBins[tableNbY - y], yDigits));
}
rowHeader.setText(headerLabel);
rowHeader.setLayoutParams(rowHeaderLayout);
tableRow.addView(rowHeader);
for (int x = 1; x <= tableNbX; x++)
{
EditText cell = new EditText(context);
cell.setText(Double.toString(MSUtils.INSTANCE.roundDouble((zBins[x - 1][tableNbY - y] + tableConstant.getTranslate()) * tableConstant.getScale(), tableConstant.getDigits())));
cell.setId(getCellId(x,y));
cell.setLayoutParams(lp);
cell.setInputType(InputType.TYPE_NUMBER_FLAG_DECIMAL);
cell.setSingleLine(true);
cell.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18);
cell.setPadding(7, 4, 7, 4);
cell.setTag(new int[] {x - 1, tableNbY - y});
cell.setKeyListener(DigitsKeyListener.getInstance("0123456789."));
// Refresh background color when one edit text lost focus
cell.addTextChangedListener(new CustomTextWatcher(cell));
// Add EditText to row
tableRow.addView(cell);
}
tableLayout.addView(tableRow,lp);
}
// Column headers
tableRow = new TableRow(context);
tableRow.setLayoutParams(lp);
for (int x = 1; x <= tableNbX + 1; x++)
{
TextView columnHeader = new TextView(context);
if (x > 1)
{
String headerLabel = "";
if (xDigits == 0)
{
headerLabel = String.valueOf((int) xBins[x - 2]);
}
else
{
headerLabel = String.valueOf(MSUtils.INSTANCE.roundDouble(xBins[x - 2], xDigits));
}
columnHeader.setText(headerLabel);
columnHeader.setGravity(Gravity.CENTER);
}
columnHeader.setLayoutParams(lp);
tableRow.addView(columnHeader);
}
tableLayout.addView(tableRow,lp);
refreshCellsBackgroundColor(null);
}
/**
* The INI define digits as scale in the following annoying format: 1.0, 0.1, etc. We need to extract how many digits that represents
*
* @param scale
* @return
*/
private int getDigitsFromScale(double scale)
{
String sScale = Double.toString(scale);
int digits = 0, separatorPosition = sScale.indexOf(".");
if (separatorPosition > -1)
{
int i = 0;
for (i = separatorPosition + 1; i < sScale.length(); i++)
{
if (sScale.substring(i, i + 1).equals("0"))
{
break;
}
}
digits = i - 1 - separatorPosition;
}
return digits;
}
/**
* Private class that implements TextWatcher. This class makes allow us to find which EditText fired 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();
// Get table constant
Constant tableConstant = ecu.getConstantByName(table.getzBins());
tableConstant.setModified(true);
// Get the x, y coordinates for that cell
int[] xy = (int[]) mEditText.getTag();
DebugLogManager.INSTANCE.log("Modyfiying table " + table.getzBins() + " at " + xy[0] + "," + xy[1] + " with " + mEditText.getText().toString(), Log.DEBUG);
int[][] zBins = ecu.getArray(table.getzBins());
try
{
// Modify local array
zBins[xy[0]][xy[1]] = (int) Math.round(Double.parseDouble(mEditText.getText().toString()) / tableConstant.getScale() - tableConstant.getTranslate());
// Apply array to ECU class
ecu.setArray(table.getzBins(), zBins);
}
catch (NumberFormatException e) {}
refreshCellsBackgroundColor(mEditText);
}
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);
}
/**
* This method goes over all the table cells and apply the right background color
* so all the cells together looks like a heat map
*
* @param cell The cell that triggered the refresh, if any
*/
private void refreshCellsBackgroundColor(EditText cell)
{
EditText firstCell = (EditText) tableLayout.findViewById(getCellId(1,1));
float min = Float.parseFloat(firstCell.getText().toString());
float max = min;
// Find min/max
for (int y = 1; y <= tableNbY; y++)
{
for (int x = 1; x <= tableNbX; x++)
{
EditText currentCell = (EditText) tableLayout.findViewById(getCellId(x,y));
float currentValue = 0;
try
{
currentValue = Float.parseFloat(currentCell.getText().toString());
}
catch (NumberFormatException e) {}
if (min > currentValue)
{
min = currentValue;
}
if (max < currentValue)
{
max = currentValue;
}
}
}
// Min and/or maximum value of the table changed, we need redraw all background colors
if (cell == null || currentTableMin != min || currentTableMax != max)
{
// Change background colors
for (int y = 1; y <= tableNbY; y++)
{
for (int x = 1; x <= tableNbX; x++)
{
EditText currentCell = (EditText) tableLayout.findViewById(getCellId(x,y));
float currentValue = 0;
try
{
currentValue = Float.parseFloat(currentCell.getText().toString());
}
catch (NumberFormatException e) {}
float currentPercent = (currentValue - min) / (max - min);
currentCell.setBackgroundColor(getHeatMapColor(currentPercent));
}
}
}
// Just redraw the specific cell that the value changed
else
{
float currentValue = 0;
try
{
currentValue = Float.parseFloat(cell.getText().toString());
}
catch (NumberFormatException e) {}
float currentPercent = (currentValue - min) / (max - min);
cell.setBackgroundColor(getHeatMapColor(currentPercent));
}
}
/**
* Used in table to generate background color based on the cell value
*
* @param value Between 0 and 1 representing the percent (based on the min/max) of the color to return
* @return An int of the color
*/
private int getHeatMapColor(float value)
{
int idx1, idx2;
// Fraction between "idx1" and "idx2" where our value is
float fractBetween = 0;
if (value <= 0)
{
idx1 = idx2 = 0;
}
else if (value >= 1)
{
idx1 = idx2 = NUM_COLORS - 1;
}
else
{
value = value * (NUM_COLORS - 1);
idx1 = (int) FloatMath.floor(value);
idx2 = idx1 + 1;
// Distance between the two indexes (0-1)
fractBetween = value - (float) idx1;
}
int red = (int) ((COLORS[idx2][0] - COLORS[idx1][0]) * fractBetween + COLORS[idx1][0]);
int green = (int) ((COLORS[idx2][1] - COLORS[idx1][1]) * fractBetween + COLORS[idx1][1]);
int blue = (int) ((COLORS[idx2][2] - COLORS[idx1][2]) * fractBetween + COLORS[idx1][2]);
return Color.rgb(red, green, blue);
}
/**
* Set all cells of 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 constant of the table to the ECU
*/
public void writeChangesToEcu()
{
Megasquirt ecu = ApplicationSettings.INSTANCE.getEcuDefinition();
TableEditor tableEditor = getTableEditor();
// Get table constant
Constant tableConstant = ecu.getConstantByName(tableEditor.getzBins());
// Make sure table has been modified
if (tableConstant.isModified())
{
DebugLogManager.INSTANCE.log("Table " + tableEditor.getName() + " was modified, need to write change to ECU", Log.DEBUG);
ecu.writeConstant(tableConstant);
}
}
}