//-----------------------------------------------------------------------
//
// Copyright © 2012 Nils Hammar. All rights reserved.
//
//-----------------------------------------------------------------------
/*
* Software to access vehicle information via the OBD-II connector.
*
* Copyright © 2012 Nils Hammar
*
* 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
* (at your option) 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 .
*
* Alternative licensing is possible, see the licensing document.
*
* The above text may not be removed or modified.
*/
namespace UserInterface.GUI.OBD
{
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;
using global::DataSource.FileAccess;
using global::Protocol.OBD;
using global::SharedObjects;
using global::SharedObjects.DataMgmt;
using global::SharedObjects.GUI;
using global::SharedObjects.GUI.OBD;
using global::SharedObjects.Misc;
using global::SharedObjects.Misc.Objects;
using global::SharedObjects.Protocol;
using global::SharedObjects.Protocol.OBD;
using global::UserInterface.GUI.Objects;
///
/// Panel for presentation of snapshot data items.
///
public partial class CurrentDataPanel : AbstractUserControl, IDataPresentation, IProgressFeedback
{
///
/// Gets OBD data panel instance.
///
public ObdDataPanel obdDataPanel
{
get
{
return this.obdDataPanel1;
}
}
///
/// Gets poll interval when plotting.
///
private int pollInterval;
///
/// Messaging instance.
///
private IMessaging iMessaging;
///
/// Data source instance.
///
private IDataSource iDataSource;
///
/// Data requester instance.
///
private DataRequester dataRequester;
///
/// Indicate if plotting is checked.
///
private bool plotting = false;
///
/// Indicate if logging is checked.
///
private bool logging = false;
///
/// Start time point for logging/plotting to get relative time.
///
/// Value is in milliseconds.
///
///
private long t0 = 0;
///
/// Dictionary of plot panels.
///
private SortedDictionary plotPanels = new SortedDictionary();
///
/// Preferences data.
///
private IPreferences iPreferences;
#if DEBUG1
///
/// To only get one shot of data.
///
private bool oneShot = false;
#endif
///
/// Mode parser instance.
///
private ModeParser modeParser;
///
/// List of requested items.
///
private IList requestedItems = null;
///
/// Tool tip handling for disabled controls.
///
private Control currentToolTipControl = null;
///
/// Tool tip handling.
///
private ToolTip toolTip = new ToolTip();
///
/// Gets data file directory.
///
protected string dataFileDir { get; private set; }
///
/// Initializes a new instance of the class.
///
/// Logging instance.
/// Preferences data.
/// Application tree instance.
/// Messaging instance.
/// Data source interface instance.
/// Current vehicle instance.
/// Data requester instance.
/// Mode parser instance.
/// Data file directory path.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "CanApp.CustomToolTip", Justification = "Reviewed.")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Reviewed.")]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "1", Justification = "Reviewed.")]
public CurrentDataPanel(
ILogging iLogging,
IPreferences iPreferences,
IApplicationTree applicationTree,
IMessaging iMessaging,
IDataSource iDataSource,
XmlClass.vehicle vehicle,
DataRequester dataRequester,
ModeParser modeParser,
string dataFileDir)
: base(iLogging, vehicle, applicationTree)
{
this.iPreferences = iPreferences;
this.iMessaging = iMessaging;
this.iDataSource = iDataSource;
this.dataRequester = dataRequester;
this.modeParser = modeParser;
this.dataFileDir = dataFileDir;
this.InitializeComponent();
this.pollInterval = this.iPreferences.defaultTimeout;
this.pollUpDown.Value = this.pollInterval;
this.goButton.Enabled = this.logDataCheckBox.Checked | this.plotDataCheckBox.Checked;
this.obdDataPanel1.init(
iLogging,
iMessaging,
applicationTree,
0x01,
iDataSource,
vehicle,
this.dataFileDir);
this.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.logDataCheckBox.Enabled = this.iLogging.getValueDataLogWriter() != null;
if (!this.logDataCheckBox.Enabled)
{
this.logDataCheckBox.Checked = false;
}
this.folderBrowserDialog1.SelectedPath = this.iPreferences.getPreference(PreferencesFileAccess.LAST_DATA_DIRECTORY);
this.toolTip.SetToolTip(this.saveButton, "Save the current selection to file.");
this.toolTip.SetToolTip(this.loadButton, "Load a saved selection setting from file.");
this.toolTip.SetToolTip(this.getSnapshotDataButton, "Do a one shot read of data according to the selection list.");
this.toolTip.SetToolTip(this.refreshButton, "Refresh the list of selectable items and clear all current selections.");
this.toolTip.SetToolTip(this.plotDataCheckBox, "Data shall be plotted to charts in sub-panels.");
this.toolTip.SetToolTip(this.logDataCheckBox, "Data shall be logged to file in CSV format.");
this.toolTip.SetToolTip(this.pollUpDown, "Base interval between polls.\r\nNotice that parameters can be defined to a lower frequency.");
this.toolTip.SetToolTip(this.goButton, "Start logging/plotting of data selected in table below.");
this.toolTip.SetToolTip(this.stopButton, "Stop logging/plotting of data.");
this.toolTip.SetToolTip(this.clearButton, "Clear all sub-panels with data and charts.");
this.toolTip.SetToolTip(this.saveDataButton, "Save the data in the plot charts to CSV file or files.");
this.toolTip.SetToolTip(this.saveChartsButton, "Save plotted charts as images.");
this.toolTip.SetToolTip(this.progressBar1, "This is an animation to indicate that the data request thread is active.");
}
///
/// Close the panel.
///
public void close()
{
if (this.dataRequester != null)
{
this.dataRequester.stopPlotting();
}
this.owningNode.Nodes.Clear();
this.plotPanels.Clear();
}
///
/// Initialize and refresh data.
///
public void init()
{
this.timer1.Start();
}
///
/// Trig a get data event.
///
public override void getDataEvent()
{
this.prepareRequestData();
this.dataRequester.requestSelectedPids();
}
///
/// Dispatch the data to the suitable receiver.
///
/// Presentation data wrapper.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Reviewed, intentional.")]
public void simpleDataAdd(IPresentationData presentationData)
{
if ((presentationData.mode & 0xbf) != 0x02)
{
if (!this.plotting && !this.logging)
{
EcuDataPanel ecuDataPanel = getDataPanelByAddress(presentationData.sourceAddress, -1);
ecuDataPanel.simpleDataAdd(presentationData);
}
if (this.logging && presentationData.pidItem != null && presentationData.sensor != null)
{
this.logData(presentationData);
}
if (this.plotting)
{
this.plotData(presentationData);
}
}
}
///
/// Add an item of the type Control.
///
/// ECU source address for data.
/// Which parameter mode that is selected.
/// Item to add.
public void add(uint sourceAddress, byte mode, Control control)
{
if ((mode & 0xbf) != 0x02)
{
if (!this.plotting && !this.logging)
{
EcuDataPanel ecuDataPanel = getDataPanelByAddress(sourceAddress, -1);
ecuDataPanel.add(sourceAddress, mode, control);
}
}
}
///
/// Update progress bar to indicate that we do something.
///
/// Counter value.
public void tickProgress(uint count)
{
if (this.progressBar1.InvokeRequired)
{
try
{
this.Invoke(new tickProgressFunc(this.tickProgress), new object[] { count });
}
catch (System.Reflection.TargetParameterCountException ex)
{
MessageBox.Show(
"Exception: " + ex.Message + "\r\n",
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Stop);
}
catch (System.ObjectDisposedException)
{
// Ignore.
}
}
else
{
this.progressBar1.Value = (int)(count % 6);
}
}
///
/// Write the file heading row.
///
/// All labels array.
/// Stream to write to.
/// CSV field separator.
private static void writeHeading(string[] allLabels, StreamWriter streamWriter, string csvSep)
{
streamWriter.Write("t (s)");
foreach (string str in allLabels)
{
streamWriter.Write(csvSep);
streamWriter.Write("\"");
streamWriter.Write(str);
streamWriter.Write("\"");
}
streamWriter.WriteLine();
}
///
/// Write the values to the file.
///
/// All mapped data dictionary.
/// Stream to write to.
/// CSV field separator.
private static void writeData(SortedDictionary allMappedData, StreamWriter streamWriter, string csvSep)
{
DataPoint[] previousDpa = null;
foreach (KeyValuePair kvp in allMappedData)
{
DataPoint[] dpa = kvp.Value;
if (dpa[0] != null)
{
if (previousDpa != null)
{
for (int i = 0; i < dpa.Length && i < previousDpa.Length; i++)
{
if (dpa[i] == null && previousDpa[i] != null)
{
dpa[i] = previousDpa[i];
}
}
}
streamWriter.Write(dpa[0].XValue.ToString(CultureInfo.CurrentCulture));
foreach (DataPoint dp in dpa)
{
streamWriter.Write(csvSep);
if (dp != null)
{
streamWriter.Write(dp.YValues[0].ToString(CultureInfo.CurrentCulture));
}
}
previousDpa = dpa;
streamWriter.WriteLine();
}
}
}
///
/// Handle an exception and present to the user.
///
/// Name of file related.
/// Exception data.
private static void handleException(string fileName, Exception ex)
{
#if DEBUG
MessageBox.Show(
"Could not open file '" + fileName + "'\r\n\r\n" + ex.Message + "\r\n\r\n" + ex.StackTrace,
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Hand);
#else
MessageBox.Show(
"Could not open file '" + fileName + "'\r\n\r\n" + ex.Message,
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Hand);
#endif
}
///
/// Close writer and stream for the file.
///
/// Stream to close.
/// Writer to close.
private static void closeFile(Stream fileStream, StreamWriter streamWriter)
{
if (streamWriter != null)
{
try
{
streamWriter.Close();
}
catch
{
}
}
if (fileStream != null)
{
try
{
fileStream.Close();
}
catch
{
}
}
}
///
/// Log data to file.
///
/// Data to log.
private void logData(IPresentationData presentationData)
{
uint k = 0;
foreach (XmlClass.pidgroup.pidlist.sensordata sensor in presentationData.pidItem.sensors)
{
if (sensor.offset == presentationData.sensor.offset && sensor.unit == presentationData.sensor.unit)
{
XmlClass.unititem thisunit = null;
foreach (XmlClass.unititem unit in this.iDataSource.units)
{
if (sensor.unit.Equals(unit.name))
{
thisunit = unit;
break;
}
}
if (this.iPreferences.selectedUnitCategory == 0 || (thisunit != null
&& (thisunit.category == 0
|| (thisunit.category > 0 && thisunit.category == this.iPreferences.selectedUnitCategory))))
{
uint? destinationAddress = DataRequester.getDestinationAddress(this.iMessaging.protocolHandler, presentationData.sourceAddress);
uint da = (destinationAddress != null) ? (uint)destinationAddress : 0;
uint[] keys = { da, (uint)(presentationData.mode & 0xbf), presentationData.pid, k };
if (sensor.unit == "bit")
{
this.dataRequester.iDataLog.logData(keys, presentationData.value < 0.5 ? 0 : 1);
}
else
{
this.dataRequester.iDataLog.logData(keys, presentationData.value);
}
}
}
k++;
}
}
///
/// Plot the data on a chart.
///
/// Data to plot.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Reviewed, intentional.")]
private void plotData(IPresentationData presentationData)
{
uint key = (uint)(((uint)(presentationData.mode & 0xff) << 24) | (uint)(presentationData.pid & 0xffffff));
bool grouped = false;
if (this.requestedItems != null)
{
foreach (PidItemWrapper item in this.requestedItems)
{
if (item != null)
{
if ((item.pidMode & 0xbf) == (presentationData.mode & 0xbf)
&& item.pidItem.pid_int == presentationData.pid
&& item.plotGroup != 0)
{
key = (uint)item.plotGroup;
grouped = true;
break;
}
}
}
}
PanelTreeNode panelTreeNode;
if (this.plotPanels.ContainsKey(key))
{
panelTreeNode = this.plotPanels[key];
}
else
{
XmlClass.pidgroup.pidlist pidItem = this.getPidItem(presentationData);
string title = "N/A";
if (grouped)
{
title = "Plot Group " + key;
}
else
{
if (pidItem != null)
{
title = pidItem.name;
}
}
PlotDataPanel plotDataPanel = new PlotDataPanel(
this.iLogging,
this.iPreferences,
this.applicationTree,
title,
this.t0,
this.pollInterval,
grouped,
(byte)(presentationData.mode & 0xbf),
presentationData.pid);
panelTreeNode = new PanelTreeNode(title, "chart_line.png", "chart_line.png", plotDataPanel);
this.plotPanels.Add(key, panelTreeNode);
this.addNode(panelTreeNode);
}
// Precaution in something odd happens.
if (panelTreeNode.userControl is PlotDataPanel)
{
PlotDataPanel plotDataPanel1 = (PlotDataPanel)panelTreeNode.userControl;
plotDataPanel1.simpleDataAdd(presentationData);
}
}
///
/// Manually refresh the data.
///
/// Sending object.
/// Event data.
private void refreshButton_Click(object sender, EventArgs e)
{
this.obdDataPanel1.refreshModeData(this.vehicle);
this.init();
}
///
/// Poll interval value was changed by the user.
///
/// Sending object.
/// Event data.
private void pollUpDown_ValueChanged(object sender, EventArgs e)
{
this.pollInterval = (int)this.pollUpDown.Value;
if (this.dataRequester != null)
{
this.dataRequester.pollInterval = this.pollInterval;
}
}
///
/// Present snapshot data.
///
/// Sending object.
/// Event data.
private void getSnapshotDataButton_Click(object sender, EventArgs e)
{
this.t0 = DateTime.Now.Ticks / Utils.TICKS_PER_MILLISECOND;
// Refresh the dictionary with latest data.
this.modeParser.configureModePidDictionary();
this.getDataEvent();
}
///
/// Prepare the request data.
///
private void prepareRequestData()
{
this.requestedItems = this.obdDataPanel1.getSelectedItems();
this.dataRequester.prepareRequestData();
foreach (PidItemWrapper item in this.requestedItems)
{
this.dataRequester.handlePid(
item.addrList,
(byte)(item.actualMode & 0xff),
0,
item.pidMode,
item.pidItem,
item.pidItem.pid_int);
}
this.dataRequester.finishPrepareDataRequest();
}
///
/// Add node in the navigation tree.
///
/// Node to add.
private delegate void addNodeFunc(PanelTreeNode panelTreeNode);
///
/// Add node in the navigation tree.
///
/// Node to add.
private void addNode(PanelTreeNode panelTreeNode)
{
if (this.applicationTree.InvokeRequired)
{
try
{
this.Invoke(new addNodeFunc(this.addNode), new object[] { panelTreeNode });
}
catch (System.Reflection.TargetParameterCountException ex)
{
MessageBox.Show(
"Exception: " + ex.Message + "\r\n",
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Stop);
}
catch (System.ObjectDisposedException)
{
// Ignore.
}
}
else
{
this.owningNode.Nodes.Add(panelTreeNode);
this.owningNode.Expand();
}
}
///
/// Get PID item matching the data.
///
/// Data to get PID item for.
/// PID item or 'null' if not found.
private XmlClass.pidgroup.pidlist getPidItem(IPresentationData presentationData)
{
XmlClass.pidgroup.pidlist pidItem = null;
foreach (XmlClass.pidgroup pidgroup in this.iDataSource.pidgroups)
{
if (pidgroup.mode_int == (presentationData.mode & 0xbf))
{
foreach (XmlClass.pidgroup.pidlist pid in pidgroup.pids)
{
if (pid.pid_int == presentationData.pid)
{
pidItem = pid;
break;
}
}
if (pidItem != null)
{
break;
}
}
}
return pidItem;
}
///
/// Start logging/plotting.
///
/// Sending object.
/// Event data.
private void goButton_Click(object sender, EventArgs e)
{
DialogResult res = DialogResult.Yes;
if (this.plotPanels.Count > 0)
{
res = MessageBox.Show(
"Select 'Yes' to continue on the current series of data"
+ " and 'No' to start a new series on the same chart starting from zero.",
"Question",
MessageBoxButtons.YesNoCancel,
MessageBoxIcon.Question,
MessageBoxDefaultButton.Button1);
this.t0 = DateTime.Now.Ticks / Utils.TICKS_PER_MILLISECOND;
if (res == DialogResult.No)
{
foreach (PanelTreeNode node in this.plotPanels.Values)
{
if (node.userControl is PlotDataPanel)
{
PlotDataPanel plotDataPanel = (PlotDataPanel)node.userControl;
plotDataPanel.restart(this.t0);
}
}
}
}
else
{
this.t0 = DateTime.Now.Ticks / Utils.TICKS_PER_MILLISECOND;
}
if (res != DialogResult.Cancel)
{
// Refresh the dictionary with latest data.
this.modeParser.configureModePidDictionary();
this.setPlotActionButtonsVisible(false);
this.stopButton.Enabled = true;
this.goButton.Enabled = false;
this.plotDataCheckBox.Enabled = false;
this.logDataCheckBox.Enabled = false;
this.getSnapshotDataButton.Enabled = false;
this.refreshButton.Enabled = false;
this.clearButton.Enabled = false;
this.saveDataButton.Enabled = false;
this.saveChartsButton.Enabled = false;
this.dataRequester.preparePlotting(this.plotting, this.logging);
if (!string.IsNullOrEmpty(this.iPreferences.gpsSerialPortName))
{
this.dataRequester.startGpsThread();
}
this.prepareRequestData();
this.dataRequester.startPlotThread(this);
}
}
///
/// Update progress bar to indicate that we do something.
///
/// Counter value.
private delegate void tickProgressFunc(uint count);
///
/// Set the plot action buttons visibility in all the related plot panels.
///
/// 'true' if set to visible, 'false' to hide.
private void setPlotActionButtonsVisible(bool visible)
{
foreach (PanelTreeNode node in this.plotPanels.Values)
{
if (node.userControl is PlotDataPanel)
{
PlotDataPanel panel = (PlotDataPanel)node.userControl;
panel.setViewButtonsVisible(visible);
}
}
}
///
/// Stop logging/plotting.
///
/// Sending object.
/// Event data.
private void stopButton_Click(object sender, EventArgs e)
{
this.dataRequester.stopPlotting();
this.stopButton.Enabled = false;
this.goButton.Enabled = this.logDataCheckBox.Checked | this.plotDataCheckBox.Checked;
this.plotDataCheckBox.Enabled = true;
this.logDataCheckBox.Enabled = this.iLogging.getValueDataLogWriter() != null;
this.getSnapshotDataButton.Enabled = true;
this.refreshButton.Enabled = true;
this.clearButton.Enabled = true;
this.saveDataButton.Enabled = true;
this.saveChartsButton.Enabled = true;
this.setPlotActionButtonsVisible(true);
if (!this.logDataCheckBox.Enabled)
{
this.logDataCheckBox.Checked = false;
}
}
///
/// Select data logging.
///
/// Sending object.
/// Event data.
private void logDataCheckBox_CheckedChanged(object sender, EventArgs e)
{
this.goButton.Enabled = this.logDataCheckBox.Checked | this.plotDataCheckBox.Checked;
this.logging = this.logDataCheckBox.Checked;
}
///
/// Select data plotting.
///
/// Sending object.
/// Event data.
private void plotDataCheckBox_CheckedChanged(object sender, EventArgs e)
{
this.goButton.Enabled = this.logDataCheckBox.Checked | this.plotDataCheckBox.Checked;
this.plotting = this.plotDataCheckBox.Checked;
}
///
/// Clear the current set of sub-panels.
///
/// Sending object.
/// Event data.
private void clearButton_Click(object sender, EventArgs e)
{
if (MessageBox.Show(
"Do you want to discard the current set of sub-panels?",
"Confirm",
MessageBoxButtons.YesNo,
MessageBoxIcon.Question,
MessageBoxDefaultButton.Button1) == DialogResult.Yes)
{
this.owningNode.Nodes.Clear();
this.plotPanels.Clear();
this.ecuData.Clear();
}
}
///
/// Log data checkbox activation.
///
/// Sending object.
/// Event data.
private void CurrentDataPanel_VisibleChanged(object sender, EventArgs e)
{
this.logDataCheckBox.Enabled = this.iLogging.getValueDataLogWriter() != null;
if (!this.logDataCheckBox.Enabled)
{
this.logDataCheckBox.Checked = false;
}
}
///
/// Log data checkbox activation.
///
/// Sending object.
/// Event data.
private void CurrentDataPanel_MouseEnter(object sender, EventArgs e)
{
this.logDataCheckBox.Enabled = this.iLogging.getValueDataLogWriter() != null;
if (!this.logDataCheckBox.Enabled)
{
this.logDataCheckBox.Checked = false;
}
}
///
/// Log data checkbox activation.
///
/// Sending object.
/// Event data.
private void getSnapshotDataButton_Paint(object sender, PaintEventArgs e)
{
this.logDataCheckBox.Enabled = this.iLogging.getValueDataLogWriter() != null;
if (!this.logDataCheckBox.Enabled)
{
this.logDataCheckBox.Checked = false;
}
}
///
/// Save current selection to file.
///
/// Sending object.
/// Event data.
private void saveButton_Click(object sender, EventArgs e)
{
this.saveFileDialog1.FileName = string.Empty;
this.saveFileDialog1.DefaultExt = ".xml";
this.saveFileDialog1.AddExtension = true;
this.saveFileDialog1.ValidateNames = true;
this.saveFileDialog1.Filter = "XML Format|*.xml";
DialogResult res = this.saveFileDialog1.ShowDialog();
if (res == DialogResult.OK)
{
ModePidSelectionFileAccess modePidSelectionFileAccess = new ModePidSelectionFileAccess(this.iLogging, this.saveFileDialog1.FileName, this.dataFileDir);
IList items = this.obdDataPanel1.getSelectedItems();
foreach (IPidItemWrapper item in items)
{
XmlClass.selectionModePid oneSelection = new XmlClass.selectionModePid();
List addressList = new List();
foreach (uint addr in item.addrList)
{
XmlClass.selectionModePid.addressItem addrItem = new XmlClass.selectionModePid.addressItem();
addrItem.address = addr;
addressList.Add(addrItem);
}
oneSelection.addrList = addressList;
oneSelection.mode_int = item.pidMode;
oneSelection.pid_int = item.pidItem.pid_int;
oneSelection.plotGroup = item.plotGroup;
modePidSelectionFileAccess.selections.Add(oneSelection);
}
modePidSelectionFileAccess.save();
}
}
///
/// Load saved selection from file.
///
/// Sending object.
/// Event data.
private void loadButton_Click(object sender, EventArgs e)
{
this.openFileDialog1.FileName = string.Empty;
this.openFileDialog1.DefaultExt = ".xml";
this.openFileDialog1.AddExtension = true;
this.openFileDialog1.ValidateNames = true;
this.openFileDialog1.Filter = "XML Format|*.xml";
DialogResult res = this.openFileDialog1.ShowDialog();
if (res == DialogResult.OK)
{
ModePidSelectionFileAccess modePidSelectionFileAccess = new ModePidSelectionFileAccess(this.iLogging, this.openFileDialog1.FileName, this.dataFileDir);
modePidSelectionFileAccess.load();
this.obdDataPanel1.selectItems(modePidSelectionFileAccess.selections);
}
}
///
/// Select a directory to save data to.
///
/// Out value with the selected path.
/// Action preferred by the user.
private DialogResult selectDirectory(out string path)
{
DialogResult res = DialogResult.No;
path = null;
while (res == DialogResult.No)
{
res = this.folderBrowserDialog1.ShowDialog();
if (res == DialogResult.OK)
{
path = this.folderBrowserDialog1.SelectedPath;
this.iPreferences.setPreference(PreferencesFileAccess.LAST_DATA_DIRECTORY, path);
if (Directory.GetFiles(path).Length > 0)
{
res = MessageBox.Show(
"Directory '" + path + "' is not empty, do you want to use it anyway?",
"Warning",
MessageBoxButtons.YesNoCancel,
MessageBoxIcon.Warning);
}
}
}
return res;
}
///
/// Save chart data to CSV file.
///
/// Sending object.
/// Event data.
private void saveDataButton_Click(object sender, EventArgs e)
{
string path;
DialogResult res = this.selectDirectory(out path);
if (res != DialogResult.Cancel && path != null)
{
DialogResult res1 = MessageBox.Show(
"Do you want to save data in one single file or several individual files?\r\n"
+ "Yes = Single file, No = Multiple files.",
"Question",
MessageBoxButtons.YesNoCancel,
MessageBoxIcon.Question);
switch (res1)
{
case DialogResult.Yes:
this.saveSingleFile(path);
break;
case DialogResult.No:
this.saveMultiFile(path);
break;
default:
// Do nothing.
break;
}
}
}
///
/// Save data to multiple files.
///
/// Path to save to.
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Reviewed.")]
private void saveSingleFile(string path)
{
SortedDictionary allMappedData = new SortedDictionary();
string[] allLabels = this.joinAllData(allMappedData);
string fileName = path + "\\AllData.csv";
Stream fileStream = null;
StreamWriter streamWriter = null;
try
{
fileStream = new FileStream(fileName, FileMode.Create, FileAccess.Write);
streamWriter = new StreamWriter(fileStream, Utils.iso_8859_1);
string csvSep = ";";
if (CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator == ".")
{
csvSep = ",";
}
writeHeading(allLabels, streamWriter, csvSep);
writeData(allMappedData, streamWriter, csvSep);
}
catch (Exception ex)
{
handleException(fileName, ex);
}
finally
{
closeFile(fileStream, streamWriter);
}
}
///
/// Join data from all charts into one single set.
///
/// Set to join data into.
/// Array of headings.
private string[] joinAllData(SortedDictionary allMappedData)
{
int totalColumns = this.getTotalColumns();
string[] allLabels = new string[totalColumns];
try
{
int columnIndex = 0;
foreach (PanelTreeNode node in this.plotPanels.Values)
{
if (node.userControl is PlotDataPanel)
{
PlotDataPanel plotDataPanel = (PlotDataPanel)node.userControl;
int seriesColumns = plotDataPanel.countSeriesColumns();
SortedDictionary mappedData = new SortedDictionary();
string[] labels = plotDataPanel.getMappedData(seriesColumns, mappedData);
for (int i = 0; i < seriesColumns && i < labels.Length; i++)
{
allLabels[columnIndex + i] = labels[i];
}
foreach (KeyValuePair kvp in mappedData)
{
DataPoint[] allPoints;
if (allMappedData.ContainsKey(kvp.Key))
{
allPoints = allMappedData[kvp.Key];
}
else
{
allPoints = new DataPoint[totalColumns];
allMappedData.Add(kvp.Key, allPoints);
}
DataPoint[] mappedPoints = kvp.Value;
for (int i = 0; i < seriesColumns && i < mappedPoints.Length; i++)
{
allPoints[columnIndex + i] = mappedPoints[i];
}
}
columnIndex += seriesColumns;
}
}
}
catch (Exception ex)
{
MessageBox.Show(
ex.Message + "\r\n" + ex.StackTrace,
"Error",
MessageBoxButtons.OK,
MessageBoxIcon.Stop);
}
return allLabels;
}
///
/// Count total number of columns to write.
///
/// Total number of columns.
private int getTotalColumns()
{
int totalColumns = 0;
foreach (PanelTreeNode node in this.plotPanels.Values)
{
if (node.userControl is PlotDataPanel)
{
PlotDataPanel plotDataPanel = (PlotDataPanel)node.userControl;
int seriesColumns = plotDataPanel.countSeriesColumns();
totalColumns += seriesColumns;
}
}
return totalColumns;
}
///
/// Save data to multiple files.
///
/// Path to save to.
private void saveMultiFile(string path)
{
foreach (PanelTreeNode node in this.plotPanels.Values)
{
if (node.userControl is PlotDataPanel)
{
PlotDataPanel plotDataPanel = (PlotDataPanel)node.userControl;
plotDataPanel.saveData(path);
}
}
}
///
/// Save chart images to CSV file.
///
/// Sending object.
/// Event data.
private void saveChartsButton_Click(object sender, EventArgs e)
{
string path;
DialogResult res = this.selectDirectory(out path);
if (res != DialogResult.Cancel && path != null)
{
foreach (PanelTreeNode node in this.plotPanels.Values)
{
if (node.userControl is PlotDataPanel)
{
PlotDataPanel plotDataPanel = (PlotDataPanel)node.userControl;
plotDataPanel.saveChartImage(path);
}
}
}
}
///
/// Act on mouse moved.
///
/// Sending object.
/// Event data.
private void CurrentDataPanel_MouseMove(object sender, MouseEventArgs e)
{
Control control = GetChildAtPoint(e.Location);
if (control != null)
{
if (!control.Enabled && this.currentToolTipControl == null)
{
string toolTipString = this.toolTip.GetToolTip(control);
// trigger the tooltip with no delay and some basic positioning just to give you an idea
this.toolTip.Show(toolTipString, control, control.Width / 2, control.Height / 2);
this.currentToolTipControl = control;
}
}
else
{
if (this.currentToolTipControl != null)
{
this.toolTip.Hide(this.currentToolTipControl);
}
this.currentToolTipControl = null;
}
}
///
/// Delayed request for data.
///
/// Sending object.
/// Event data.
private void timer1_Tick(object sender, EventArgs e)
{
this.timer1.Stop();
IRequestData rd = new RequestData(null, TxMsg.MODE_CURRENT, null, new TxMsg(TxMsg.GET_SUPPORTED_PIDS));
this.iMessaging.queueMsg(rd);
this.iMessaging.sendMsg();
}
}
}