package uk.org.smithfamily.mslogger.dialog; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map.Entry; import uk.org.smithfamily.mslogger.ApplicationSettings; import uk.org.smithfamily.mslogger.R; import uk.org.smithfamily.mslogger.dialog.EditRequiredFuel.OnReqFuelResult; import uk.org.smithfamily.mslogger.ecuDef.Constant; import uk.org.smithfamily.mslogger.ecuDef.CurveEditor; import uk.org.smithfamily.mslogger.ecuDef.DialogField; import uk.org.smithfamily.mslogger.ecuDef.DialogPanel; import uk.org.smithfamily.mslogger.ecuDef.InjectedCommand; import uk.org.smithfamily.mslogger.ecuDef.MSDialog; import uk.org.smithfamily.mslogger.ecuDef.MSUtils; import uk.org.smithfamily.mslogger.ecuDef.MSUtilsShared; import uk.org.smithfamily.mslogger.ecuDef.Megasquirt; import uk.org.smithfamily.mslogger.ecuDef.TableEditor; import uk.org.smithfamily.mslogger.log.DebugLogManager; import android.app.AlertDialog; import android.app.Dialog; import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.res.Resources; import android.graphics.Color; import android.graphics.Typeface; import android.os.Bundle; 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.view.WindowManager; import android.widget.AdapterView; import android.widget.AdapterView.OnItemSelectedListener; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.SeekBar; import android.widget.SeekBar.OnSeekBarChangeListener; import android.widget.Spinner; import android.widget.TableLayout; import android.widget.TableRow; import android.widget.TableRow.LayoutParams; import android.widget.TextView; /** * */ public class EditDialog extends Dialog implements android.view.View.OnClickListener { private MSDialog dialog; private RelativeLayout content; private Megasquirt ecu; private int nbPanels = 1; // Used on label in table row with no field beside // Those are usually used as separator so add top and bottom margins private LayoutParams lpSpanWithMargins; // Same as lpSpanWithMargins but without the span private LayoutParams lpWithTopBottomMargins; // Used on label in table row with field beside label, add a margin right // so the label and field are separated private LayoutParams lpWithMargins; // Regular layout params for dialog row with label and constant private LayoutParams lp; private HashMap<String, CurveHelper> curveHelpers = new HashMap<String, CurveHelper>(); private HashMap<String, TableHelper> tableHelpers = new HashMap<String, TableHelper>(); private OnEditDialogResult mDialogResult; private String panelLabel = ""; private BroadcastReceiver yourReceiver; /** * Constructor for dialog which set the current dialog and ECU object * * @param context * @param dialog */ public EditDialog(Context context, MSDialog dialog) { super(context); this.ecu = ApplicationSettings.INSTANCE.getEcuDefinition(); this.dialog = dialog; // Initialise some layout params lpSpanWithMargins = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); lpSpanWithMargins.setMargins(0, 10, 0, 15); lpSpanWithMargins.span = 2; lpWithMargins = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); lpWithMargins.setMargins(0, 0, 8, 0); lpWithTopBottomMargins = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); lpWithTopBottomMargins.setMargins(0, 10, 8, 15); lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); } /** * * * @param savedInstanceState */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.editdialog); content = (RelativeLayout) findViewById(R.id.content); setTitle(dialog.getLabel()); Button buttonBurn = (Button) findViewById(R.id.burn); buttonBurn.setOnClickListener(this); Button buttonClose = (Button) findViewById(R.id.close); buttonClose.setOnClickListener(this); drawDialogFields(null, dialog, dialog, false, "", null); final IntentFilter injectCommandResultsFilter = new IntentFilter(); injectCommandResultsFilter.addAction(Megasquirt.INJECTED_COMMAND_RESULTS); this.yourReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (action.equals(Megasquirt.INJECTED_COMMAND_RESULTS)) { int resultId = intent.getIntExtra(Megasquirt.INJECTED_COMMAND_RESULT_ID, 0); if (resultId == Megasquirt.CONTROLLER_COMMAND) { DebugLogManager.INSTANCE.log("Refreshing fields visibility after controller command", Log.DEBUG); // Re-evaluate the expressions with the data updated ecu.setUserDefinedVisibilityFlags(); // Refresh the UI refreshFieldsVisibility(dialog); } } } }; // Registers the receiver so that your service will listen for broadcasts getContext().registerReceiver(this.yourReceiver, injectCommandResultsFilter); // Hide keyboard getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN); } /** * Unregister the broadcast receiver when the dialog is closed */ public void onStop() { getContext().unregisterReceiver(this.yourReceiver); } /** * Add the panel to the layout by looking at the orientation to insert the panel at the right place * * @param parentLayout The parent layout the panel to add will be inserted into * @param relativeLayoutToAdd The layout to be added with all the dialog fields already there * @param orientation The orientation of the new panel * @param dialogName The name of the dialog (for debugging purpose only) * @param previousPanelLayout An instance of the previous layout added since the new one will be added in relation to the previous one */ private void addPanel(RelativeLayout parentLayout, RelativeLayout relativeLayoutToAdd, String orientation, String dialogName, String dialogAxis, RelativeLayout previousPanelLayout) { RelativeLayout.LayoutParams tlp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT); DebugLogManager.INSTANCE.log("PANEL relativeLayoutToAdd id (" + dialogName + ") is " + relativeLayoutToAdd.getId(), Log.DEBUG); // This is not the first panel we add on this dialog if (previousPanelLayout != null) { if (orientation.equals("North")) { tlp.addRule(RelativeLayout.ABOVE, previousPanelLayout.getId()); relativeLayoutToAdd.setPadding(0, 0, 0, 15); DebugLogManager.INSTANCE.log("PANEL " + dialogName + " above " + previousPanelLayout.getTag() + " (" + previousPanelLayout.getId() + ")", Log.DEBUG); } else if (orientation.equals("South")) { tlp.addRule(RelativeLayout.BELOW, previousPanelLayout.getId()); relativeLayoutToAdd.setPadding(0, 15, 0, 0); DebugLogManager.INSTANCE.log("PANEL " + dialogName + " below " + previousPanelLayout.getTag() + " (" + previousPanelLayout.getId() + ")", Log.DEBUG); } else if (orientation.equals("West")) { tlp.addRule(RelativeLayout.LEFT_OF, previousPanelLayout.getId()); relativeLayoutToAdd.setPadding(0, 0, 15, 0); DebugLogManager.INSTANCE.log("PANEL " + dialogName + " at the left of " + previousPanelLayout.getTag() + " (" + previousPanelLayout.getId() + ")", Log.DEBUG); } else { // For yAxis orientation, add panel one under the other if (dialogAxis.equals("yAxis")) { tlp.addRule(RelativeLayout.BELOW, previousPanelLayout.getId()); tlp.addRule(RelativeLayout.ALIGN_LEFT, previousPanelLayout.getId()); relativeLayoutToAdd.setPadding(15, 15, 0, 0); DebugLogManager.INSTANCE.log("PANEL " + dialogName + " (Dialog axis: " + dialogAxis + ") below " + previousPanelLayout.getTag() + " (" + previousPanelLayout.getId() + ")", Log.DEBUG); } // For xAxis orientation, add panel at the right of the last one else { tlp.addRule(RelativeLayout.RIGHT_OF, previousPanelLayout.getId()); tlp.addRule(RelativeLayout.ALIGN_TOP, previousPanelLayout.getId()); relativeLayoutToAdd.setPadding(15, 0, 0, 0); DebugLogManager.INSTANCE.log("PANEL " + dialogName + " (Dialog axis: " + dialogAxis + ") at the right of " + previousPanelLayout.getTag() + " (" + previousPanelLayout.getId() + ")", Log.DEBUG); } } relativeLayoutToAdd.setLayoutParams(tlp); } else { DebugLogManager.INSTANCE.log("PANEL " + dialogName + " previousPanelLayout is null!", Log.DEBUG); } // Panel to add have a parent layout, add view to it if (parentLayout != null) { DebugLogManager.INSTANCE.log("PANEL parentLayout is " + parentLayout.getTag(), Log.DEBUG); parentLayout.addView(relativeLayoutToAdd); } // No parent layout, default to main layout else { DebugLogManager.INSTANCE.log("PANEL parentLayout is null, adding to main layout", Log.DEBUG); content.addView(relativeLayoutToAdd); } nbPanels++; } /** * Helper function to wrap a table layout into a relative layout * * @param tl The table layout that will be wrapped into a relative layout * @param dialogName The dialog name of the panel we want to wrap */ private RelativeLayout wrapTableLayoutIntoRelativeLayout(TableLayout tl, String dialogName) { RelativeLayout containerPanelLayout = new RelativeLayout(getContext()); RelativeLayout.LayoutParams tlp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT); containerPanelLayout.setLayoutParams(tlp); containerPanelLayout.setId(nbPanels); containerPanelLayout.setTag(dialogName); containerPanelLayout.addView(tl); return containerPanelLayout; } /** * Used to figure out if there is at least one complete row in a dialog/panel. * This is useful because we use span=2 for row with just a label and if all * rows of the dialog end up being span=2, TableLayout doesn't like it and the * whole layout dissapear. * * @param dialogFields The fields of the dialog * * @return true is there is at least one complete row in the dialog, false otherwise */ private boolean atLeastOneCompleteRow(List<DialogField> dialogFields) { boolean atLeastOne = false; for (DialogField df : dialogFields) { if (!((df.getLabel().equals("") && df.getName().equals("null")) || df.getName().equals("null") || df.isCommandButton())) { atLeastOne = true; break; } } return atLeastOne; } /** * Create controller command button with the properties and events * * @return An instance of Button */ private Button createControllerCommandButton(MSDialog dialog, DialogField df) { final String controllerCommandName = df.getName(); Button cmdButton = new Button(getContext()); cmdButton.setText(df.getLabel()); cmdButton.setTag(df.getName()); cmdButton.setEnabled(ecu.getUserDefinedVisibilityFlagsByName(dialog.getName() + "_" + df.getName())); cmdButton.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { clickCmdButton(controllerCommandName); } }); if (atLeastOneCompleteRow(dialog.getFieldsList())) { cmdButton.setLayoutParams(lpSpanWithMargins); } else { cmdButton.setLayoutParams(lpWithTopBottomMargins); } return cmdButton; } /** * Recursive function that will draw fields and panels of a dialog. * It's called recursively until all fields and panels were drawn. * * @param parentLayout The parent layout in which the dialog fields should be added * @param dialog The dialog object * @param parentDialog If it's a panel, this is the instance of the parent dialog * @param isPanel true if the dialog object is a panel, false otherwise * @param orientation The orientation of the panel * @param previousDialogPanelLayout An instance of the previous layout added since the new one will be added in relation to the previous one * * @return A relative layout with all fields in it */ private RelativeLayout drawDialogFields(RelativeLayout parentLayout, MSDialog dialog, MSDialog parentDialog, boolean isPanel, String orientation, RelativeLayout previousDialogPanelLayout) { TableLayout panelLayout = new TableLayout(getContext()); // If it's a panel or at least a dialog with one field or more, we can display its label if ((isPanel || dialog.getFieldsList().size() > 0) && !dialog.getLabel().equals("")) { panelLabel = dialog.getLabel(); } // For each dialog field, add a row in the table layout for (DialogField df : dialog.getFieldsList()) { Constant constant = ecu.getConstantByName(df.getName()); if (constant == null && !df.isCommandButton() && !df.getName().equals("null")) { showConstantDoesntExists(df.getName()); } else { // If we have a panel label to add, add it! if (!panelLabel.equals("")) { panelLayout.addView(showPanelLabel(panelLabel, dialog.getFieldsList(), panelLayout)); panelLabel = ""; } TableRow tableRow = new TableRow(getContext()); // Dialog field is a command button if (df.isCommandButton()) { Button cmdButton = createControllerCommandButton(dialog, df); tableRow.addView(cmdButton); } // Dialog field is not a command button else { // For empty label or empty field name, we just insert an empty text view as second column of the row if ((df.getLabel().equals("") && df.getName().equals("null")) || df.getName().equals("null")) { // Special label used to identify hard coded required fuel panel in dialog if (df.getLabel().equals("std_required_fuel")) { RelativeLayout requiredFuel = getRequiredFuelPanel(); requiredFuel.setLayoutParams(lpWithTopBottomMargins); tableRow.addView(requiredFuel); } // Special label used to identify hard coded seek bar in std_accel dialog else if (df.getLabel().equals("std_accel_seek_bar")) { RelativeLayout accelSeekBar = getAccelSeekBar(); accelSeekBar.setLayoutParams(lpWithTopBottomMargins); tableRow.addView(accelSeekBar); } else { TextView label = getLabel(df, constant); tableRow.addView(label); // No second column so label is used to separate so make it bold and merge columns label.setTypeface(null, Typeface.BOLD); // If it's not an empty label and not , add some top and bottom margins if (!df.getLabel().equals("")) { if (atLeastOneCompleteRow(dialog.getFieldsList())) { label.setLayoutParams(lpSpanWithMargins); } else { label.setLayoutParams(lpWithTopBottomMargins); } } } } // Regular row with label and constant fields else { TextView label = getLabel(df, constant); tableRow.addView(label, lp); // Multi-choice constant if (constant.getClassType().equals("bits")) { Spinner spin = buildMultiValuesConstantField(dialog.getName(), df, constant); tableRow.addView(spin); } // Single value constant else { EditText edit = buildSingleValueConstantField(dialog.getName(), df, constant); tableRow.addView(edit); } } } panelLayout.addView(tableRow); } } // Wrap panel layout into a relative layout so it can be used as parent RelativeLayout containerPanelLayout = wrapTableLayoutIntoRelativeLayout(panelLayout, dialog.getName()); addPanel(parentLayout, containerPanelLayout, orientation, dialog.getName(), parentDialog.getAxis(), previousDialogPanelLayout); RelativeLayout sameDialogPreviousLayoutPanel = null; // If we added at least one row to the previous layout (AKA there is at least one field) if (dialog.getFieldsList().size() > 0) { sameDialogPreviousLayoutPanel = containerPanelLayout; } // When we are in a panel, the parent layout is not the R.id.content layout but the parent panel layout else if (isPanel) { parentLayout = containerPanelLayout; } // For each dialog panel, add a layout to the dialog for (DialogPanel dp : dialog.getPanelsList()) { MSDialog dialogPanel = ecu.getDialogByName(dp.getName()); if (dialogPanel != null) { sameDialogPreviousLayoutPanel = drawDialogFields(parentLayout, dialogPanel, dialog, true, dp.getOrientation(), sameDialogPreviousLayoutPanel); } else { // Not a regular dialog, but maybe it's an std_* dialog dialogPanel = DialogHelper.getStdDialog(dp.getName()); if (dialogPanel != null) { sameDialogPreviousLayoutPanel = drawDialogFields(parentLayout, dialogPanel, dialog, true, dp.getOrientation(), sameDialogPreviousLayoutPanel); } else { // Maybe it's a curve panel CurveEditor curvePanel = ecu.getCurveEditorByName(dp.getName()); if (curvePanel != null) { sameDialogPreviousLayoutPanel = createCurvePanel(parentLayout, curvePanel, dp.getOrientation(), dialog.getName(), sameDialogPreviousLayoutPanel); } else { // Maybe it's a table panel TableEditor tablePanel = ecu.getTableEditorByName(dp.getName()); if (tablePanel != null) { sameDialogPreviousLayoutPanel = createTablePanel(parentLayout, tablePanel, dp.getOrientation(), dialog.getName(), sameDialogPreviousLayoutPanel); } else { RelativeLayout rLayout = DialogHelper.getCustomPanel(getContext(), dp.getName()); // Maybe it's a custom panel if (rLayout != null) { rLayout.setId(nbPanels); rLayout.setTag(dialog.getName()); addPanel(parentLayout, rLayout, orientation, dialog.getName(), parentDialog.getAxis(), sameDialogPreviousLayoutPanel); } else { DebugLogManager.INSTANCE.log("Invalid panel name " + dp.getName(), Log.DEBUG); } } } } } } return containerPanelLayout; } /** * @param controllerCommandName */ private void clickCmdButton(String controllerCommandName) { String command = ecu.getControllerCommands().get(controllerCommandName); command = MSUtilsShared.HexStringToBytes(command); byte[] byteCommand = MSUtils.INSTANCE.commandStringtoByteArray(command); InjectedCommand writeToRAM = new InjectedCommand(byteCommand, 300, true, Megasquirt.CONTROLLER_COMMAND); ecu.injectCommand(writeToRAM); } /** * Inflate the layout for the required fuel panel, do the initialization work and return the layout * * @return The relative layout containing the required fuel stuff */ private RelativeLayout getRequiredFuelPanel() { LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); RelativeLayout requiredFuelLayout = (RelativeLayout) inflater.inflate(R.layout.required_fuel_panel, null); Button requiredFuelButton = (Button) requiredFuelLayout.findViewById(R.id.bt_required_fuel); double reqFuel = 0; String reqFuelConstantName = "reqFuel"; // MS1 if (ecu.isConstantExists("reqFuel1")) { reqFuelConstantName = "reqFuel1"; } Constant reqFuelConstant = ecu.getConstantByName(reqFuelConstantName); reqFuel = (ecu.getField(reqFuelConstantName) + reqFuelConstant.getTranslate()) * reqFuelConstant.getScale(); final EditText reqFuelEdit = (EditText) requiredFuelLayout.findViewById(R.id.req_fuel); reqFuelEdit.setText(String.valueOf(reqFuel)); final EditText reqFuelDownloadedEdit = (EditText) requiredFuelLayout.findViewById(R.id.req_fuel_downloaded); final int divider = ecu.getDivider(); final int nInjectors = ecu.getInjectorsCount(); final double injectorStaging = ecu.getInjectorStating() + 1; reqFuelDownloadedEdit.setText(String.valueOf(reqFuel * (injectorStaging * divider) / nInjectors)); requiredFuelButton.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { EditRequiredFuel dialog = new EditRequiredFuel(getContext()); dialog.setDialogResult(new OnReqFuelResult() { public void finish(double reqFuel, double dReqFuel) { reqFuelEdit.setText(String.valueOf(reqFuel)); reqFuelDownloadedEdit.setText(String.valueOf(dReqFuel)); } }); dialog.show(); } }); return requiredFuelLayout; } /** * Build a seek bar used to choose between MAP/TPS based accel enrichement * * @return The relative layout containing the seek bar */ private RelativeLayout getAccelSeekBar() { RelativeLayout seekBarLayout = new RelativeLayout(getContext()); SeekBar sb = new SeekBar(getContext()); sb.setMax(100); sb.setProgress(50); sb.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar v, int progress, boolean fromUser) { if (fromUser) { } } @Override public void onStartTrackingTouch(SeekBar seekBar) {} @Override public void onStopTrackingTouch(SeekBar seekBar) {} }); seekBarLayout.addView(sb); return seekBarLayout; } /** * Create a curve panel to insert in a dialog * * @param parentLayout The parent layout the panel to add will be inserted into * @param curvePanel The curve panel itself * @param orientation The orientation of the panel * @param parentDialogName The name of the dialog parent to the panel * @param previousPanelLayout An instance of the previous layout added since the new one will be added in relation to the previous one * * @return The relative layout with the curve panel in it */ private RelativeLayout createCurvePanel(RelativeLayout parentLayout, CurveEditor curvePanel, String orientation, String parentDialogName, RelativeLayout previousPanelLayout) { CurveHelper curveHelper = new CurveHelper(getContext(), curvePanel, false); // Wrap panel layout into a relative layout so it can be used as parent RelativeLayout containerPanelLayout = new RelativeLayout(getContext()); // Convert to density independent pixels so it hopefully looks the same on every screen size Resources r = getContext().getResources(); int px = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 700, r.getDisplayMetrics()); RelativeLayout.LayoutParams tlp = new RelativeLayout.LayoutParams(px, px); containerPanelLayout.setLayoutParams(tlp); containerPanelLayout.setId(nbPanels); containerPanelLayout.setTag(curvePanel.getName()); if (nbPanels > 0) { addPanel(parentLayout, containerPanelLayout, orientation, curvePanel.getName(), "", previousPanelLayout); } LinearLayout curveLayout = curveHelper.getLayout(); curveLayout.setLayoutParams(lpWithMargins); containerPanelLayout.addView(curveLayout); boolean isPanelEnabled = ecu.getUserDefinedVisibilityFlagsByName(parentDialogName + "_" + curvePanel.getName()); // Table panel is disabled, make it look like it is if (!isPanelEnabled) { curveHelper.refreshFieldsVisibility(false); } curveHelpers.put(curvePanel.getName(), curveHelper); return containerPanelLayout; } /** * Create a table panel to insert in a dialog * * @param parentLayout The parent layout the panel to add will be inserted into * @param tablePanel The table panel itself * @param orientation The orientation of the panel * @param parentDialogName The name of the dialog parent to the panel * @param previousPanelLayout An instance of the previous layout added since the new one will be added in relation to the previous one * * @return The relative layout with the table panel in it */ private RelativeLayout createTablePanel(RelativeLayout parentLayout, TableEditor tablePanel, String orientation, String parentDialogName, RelativeLayout previousPanelLayout) { TableHelper tableHelper = new TableHelper(getContext(), tablePanel, false); RelativeLayout panelLayout = new RelativeLayout(getContext()); panelLayout.setId(nbPanels); panelLayout.setTag(tablePanel.getName()); if (nbPanels > 0) { addPanel(parentLayout, panelLayout, orientation, tablePanel.getName(), "", previousPanelLayout); } LinearLayout tableLayout = tableHelper.getLayout(); tableLayout.setLayoutParams(lpWithMargins); panelLayout.addView(tableLayout); boolean isPanelEnabled = ecu.getUserDefinedVisibilityFlagsByName(parentDialogName + "_" + tablePanel.getName()); // Table panel is disabled, make it look like it is if (!isPanelEnabled) { tableHelper.refreshFieldsVisibility(false); } tableHelpers.put(tablePanel.getName(), tableHelper); return panelLayout; } /** * Take information from a dialog field and constant and build the label for the field * * @param df The DialogField to build the label for * @param constant The Constant to build the label for * * @return The TextView object */ private TextView getLabel(DialogField df, Constant constant) { String labelText = df.getLabel(); // Add units to label if (constant != null && !constant.getUnits().equals("")) { labelText += " (" + constant.getUnits() + ")"; } TextView label = new TextView(getContext()); label.setText(labelText); // If the first character of the label is a #, we need to highlight that label // It means it is used as a section separator if (labelText.length() > 0 && labelText.substring(0,1).equals("#")) { label.setText(" " + label.getText().toString().substring(1)); // Replace the # by a space label.setBackgroundColor(Color.rgb(110, 110, 110)); label.setPadding(2, 8, 2, 8); } label.setLayoutParams(lpWithMargins); return label; } /** * Build an EditText for displaying single value constant * * @param dialogName Name of the dialog the field is included in * @param df The dialog field to build the display for * @param constant The constant associated with the dialog field * * @return The EditText that can be displayed */ private EditText buildSingleValueConstantField(String dialogName, DialogField df, Constant constant) { double constantValue = MSUtils.INSTANCE.roundDouble((ecu.getField(df.getName()) + constant.getTranslate()) * constant.getScale(), constant.getDigits()); String displayedValue = ""; if (constant.getDigits() == 0) { displayedValue = String.valueOf((int) constantValue); } else { displayedValue = String.valueOf(constantValue); } EditText edit = new EditText(getContext()); edit.setText(displayedValue); edit.setInputType(InputType.TYPE_NUMBER_FLAG_DECIMAL); edit.setSingleLine(true); edit.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18); edit.setPadding(8, 5, 8, 5); edit.setTag(df.getName()); edit.setKeyListener(DigitsKeyListener.getInstance("0123456789.")); edit.setOnFocusChangeListener(new OnFocusChangeListener() { public void onFocusChange(View v, boolean hasFocus) { if (!hasFocus) { Constant constant = ecu.getConstantByName(((EditText) v).getTag().toString()); DialogHelper.verifyOutOfBoundValue(getContext(), constant, (EditText) v); } } }); edit.addTextChangedListener(new TextWatcher() { /** * Set the constant to modified when value is changed * * @param s */ @Override public void afterTextChanged(Editable s) { EditText edit = (EditText) getCurrentFocus(); String constantName = edit.getTag().toString(); // Constant has been modified and will need to be burn to ECU Constant constant = ecu.getConstantByName(constantName); constant.setModified(true); int value = 0; try { value = (int) Math.round(Double.parseDouble(edit.getText().toString()) / constant.getScale() - constant.getTranslate()); } catch (NumberFormatException e){} // Update ecu field with new value ecu.setField(constantName, value); } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after){} @Override public void onTextChanged(CharSequence s, int start, int before, int count){} }); // Field is ready only or disabled if (df.isDisplayOnly() || !ecu.getUserDefinedVisibilityFlagsByName(dialogName + "_" + df.getName())) { edit.setEnabled(false); } return edit; } /** * Helper class for multi values spinner which specify an id and text for each row of the spinner */ class MultiValuesSpinnerData { private int id = 0; private String text = ""; public MultiValuesSpinnerData(int id, String text) { this.id = id; this.text = text; } public int getId() { return id; } public String getText() { return text; } public String toString() { return text; } } /** * Build a Spinner for displaying multi values constant * * @param dialogName Name of the dialog the field is included in * @param df The dialog field to build the display for * @param constant The constant associated with the dialog field * @return The Spinner that can be displayed */ private Spinner buildMultiValuesConstantField(String dialogName, DialogField df, Constant constant) { Spinner spin = new Spinner(getContext()); // Field is ready only or disabled if (df.isDisplayOnly() || !ecu.getUserDefinedVisibilityFlagsByName(dialogName + "_" + df.getName())) { spin.setEnabled(false); } final List<MultiValuesSpinnerData> spinnerData = new ArrayList<MultiValuesSpinnerData>(); int selectedValue = 0; // Special case for custom constant build at runtime if (df.getName().equals("MSLogger_nSquirts")) { selectedValue = (int) ecu.getCylindersCount() / ecu.getDivider() - 1; } else { selectedValue = (int) ecu.getField(df.getName()); } int selectedIndex = 0; int invalidCount = 0; // Remove INVALID from values for (int i = 0; i < constant.getValues().length; i++) { String value = constant.getValues()[i]; if (value.equals("INVALID")) { invalidCount++; } else { spinnerData.add(new MultiValuesSpinnerData(i, value)); } /* * When we reach the currently selected valid, we need to keep track of how many * invalid values there was before that, because those won't be displayed in the * spinner and we need to know which index to select */ if (selectedValue == i) { selectedIndex = i - invalidCount; } } ArrayAdapter<MultiValuesSpinnerData> spinAdapter = new ArrayAdapter<MultiValuesSpinnerData>(getContext(), android.R.layout.simple_spinner_item, spinnerData); // Specify the layout to use when the list of choices appears spinAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); spin.setAdapter(spinAdapter); spin.setSelection(selectedIndex); spin.setTag(df.getName()); final MSDialog msDialog = this.dialog; spin.setOnItemSelectedListener(new OnItemSelectedListener() { boolean ignoreEvent = true; @Override public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) { // First onItemSelected event of the spinner come from populating it, ignore it! if (ignoreEvent) { ignoreEvent = false; } else { String constantName = parentView.getTag().toString(); int value = spinnerData.get(position).getId(); // Special case for this constant which should do some extra validation if (constantName.equals("MSLogger_nSquirts")) { int nCylinders = ecu.getCylindersCount(); // nCylinders should divide by value without remainder if (nCylinders % value > 0) { showInvalidNumberOfSquirts(nCylinders + " cylinders is not valid with " + value + " squirts (Number of cylinders / number of squirts should divide without remainder)"); } // If injector staging is alternating else if (ecu.getInjectorStating() == 1 && !(nCylinders / value < nCylinders && nCylinders / (value * 2) == 0)) { showInvalidNumberOfSquirts("Cannot alternate this Squirts per engine cycle with this number of cylinders."); } } else { // Value changed, update field in ECU class if (ecu.getField(constantName) != value) { // Constant has been modified and will need to be burn to ECU Constant constant = ecu.getConstantByName(constantName); constant.setModified(true); // Update ecu field with new value ecu.setField(constantName, value); // Re-evaluate the expressions with the data updated ecu.setUserDefinedVisibilityFlags(); // Refresh the UI refreshFieldsVisibility(msDialog); } } } } @Override public void onNothingSelected(AdapterView<?> parentView){} }); return spin; } /** * Add a label at the top of a panel * * @param title The label of the panel * @param fields The fields list of the panel * @param tl Table layout to add the table row to * * @return true if a panel label will be shown, false otherwise */ private TableRow showPanelLabel(String title, List<DialogField> fields, TableLayout tl) { TableRow tableRow = new TableRow(getContext()); if (!title.equals("")) { LayoutParams lpSpan = new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT); if (atLeastOneCompleteRow(fields)) { lpSpan.span = 2; } TextView label = new TextView(getContext()); label.setText(title); label.setTextAppearance(getContext(), android.R.style.TextAppearance_Medium); label.setPadding(0, 0, 0, 10); label.setLayoutParams(lpSpan); tableRow.addView(label); } return tableRow; } /** * Display an alert dialog whenever the number of squirts is invalid * * @param message The error message to display */ private void showInvalidNumberOfSquirts(String message) { AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); builder.setMessage(message) .setIcon(android.R.drawable.ic_dialog_info) .setTitle("Invalid number of squirts per engine cycle") .setCancelable(true) .setPositiveButton("OK", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id){} }); AlertDialog alert = builder.create(); alert.show(); } /** * Show the constant doesn't exists alert dialog * * @param constantName The name of the constant that doesn't exists */ private void showConstantDoesntExists(String constantName) { AlertDialog.Builder builder = new AlertDialog.Builder(getContext()); builder.setMessage("Uh oh, It looks like constant \"" + constantName + "\" is missing!") .setIcon(android.R.drawable.ic_dialog_info) .setTitle("Missing constant") .setCancelable(true) .setPositiveButton("OK", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id){} }); AlertDialog alert = builder.create(); alert.show(); } /** * When value are changed, it's possible dialog fields change state * so we need to refresh fields visibility and re-apply them recursivly * on all the panels * * @param dialog The dialog to refresh fields visibility for */ private void refreshFieldsVisibility(MSDialog dialog) { for (DialogField df : dialog.getFieldsList()) { // Field is not ready only and not disabled boolean isFieldEnabled = !df.isDisplayOnly() && ecu.getUserDefinedVisibilityFlagsByName(dialog.getName() + "_" + df.getName()); if (df.isCommandButton()) { Button button = (Button) content.findViewWithTag(df.getName()); button.setEnabled(isFieldEnabled); } else { Constant constant = ecu.getConstantByName(df.getName()); if (constant != null) { if (constant.getClassType().equals("bits")) { Spinner spin = (Spinner) content.findViewWithTag(df.getName()); spin.setEnabled(isFieldEnabled); } else { EditText edit = (EditText) content.findViewWithTag(df.getName()); edit.setEnabled(isFieldEnabled); } } } } for (DialogPanel dp : dialog.getPanelsList()) { MSDialog dialogPanel = DialogHelper.getStdDialog(dp.getName()); // It's an std_* panel if (dialogPanel != null) { refreshFieldsVisibility(dialogPanel); } // It's a table panel else if (tableHelpers.containsKey(dp.getName())) { TableHelper tableHelper = tableHelpers.get(dp.getName()); boolean isPanelEnabled = ecu.getUserDefinedVisibilityFlagsByName(dialog.getName() + "_" + dp.getName()); tableHelper.refreshFieldsVisibility(isPanelEnabled); } // It's a curve panel else if (curveHelpers.containsKey(dp.getName())) { CurveHelper curveHelper = curveHelpers.get(dp.getName()); boolean isPanelEnabled = ecu.getUserDefinedVisibilityFlagsByName(dialog.getName() + "_" + dp.getName()); curveHelper.refreshFieldsVisibility(isPanelEnabled); } else { // Check regular panel last as a table panel or curve panel will // have a regular dialog too but we want to do specific processing // for them dialogPanel = ecu.getDialogByName(dp.getName()); if (dialogPanel != null) { refreshFieldsVisibility(dialogPanel); } } } } /** * Burn the change to the ECU */ private void burnToECU() { boolean requirePowerCycle = false; List<String> constantsThatRequirePowerCycle = ecu.getRequiresPowerCycle(); // Burn all constant for (String constantName : ecu.getAllConstantsNamesForDialog(dialog)) { Constant constant = ecu.getConstantByName(constantName); if (constant.isModified()) { DebugLogManager.INSTANCE.log("Constant \"" + constantName + "\" was modified, need to write change to ECU", Log.DEBUG); constant.setModified(false); ecu.writeConstant(constant); } // Special case for custom constant MSLogger_nSquirts // The value of it should be saved into divider constant if (constantName.equals("MSLogger_nSquirts")) { constant = ecu.isConstantExists("divider") ? ecu.getConstantByName("divider") : ecu.getConstantByName("divider1"); } // If constant require power cycle, set flag to true if (constantsThatRequirePowerCycle.contains(constantName)) { requirePowerCycle = true; } } // Burn all curves for (Entry<String, CurveHelper> entry : curveHelpers.entrySet()) { CurveHelper curveHelper = entry.getValue(); curveHelper.writeChangesToEcu(); } // Burn all tables for (Entry<String, TableHelper> entry : tableHelpers.entrySet()) { TableHelper tableHelper = entry.getValue(); tableHelper.writeChangesToEcu(); } mDialogResult.finish(requirePowerCycle); dismiss(); } /** * Triggered when one of the two bottoms button are clicked ("Burn" and "Cancel") * * @param v The view that was clicked on */ @Override public void onClick(View v) { int which = v.getId(); if (which == R.id.burn) { burnToECU(); } else if (which == R.id.close) { for (DialogField df : dialog.getFieldsList()) { if (df.isCommandButton()) { boolean enabled = ecu.getUserDefinedVisibilityFlagsByName(dialog.getName() + "_" + df.getName()); if (df.getCommandOnClose().equals("clickOnClose") || (enabled && df.getCommandOnClose().equals("clickOnCloseIfEnabled")) || (!enabled && df.getCommandOnClose().equals("clickOnCloseIfDisabled"))) { clickCmdButton(df.getName()); } } } cancel(); } } /** * Used by the parent to set a new OnEditDialogResult to the dialog * * @param dialogResult */ public void setDialogResult(OnEditDialogResult dialogResult) { mDialogResult = dialogResult; } /** * Interface used to send the data back to the dialog's parent */ public interface OnEditDialogResult { void finish(boolean isPowerCycleRequired); } }