1079 lines
35 KiB
C++
1079 lines
35 KiB
C++
#include "mainwindow.h"
|
|
#include "ui_mainwindow.h"
|
|
#include "can_structs.h"
|
|
#include <QDateTime>
|
|
#include <QFileDialog>
|
|
#include <QtSerialPort/QSerialPortInfo>
|
|
#include "connections/canconmanager.h"
|
|
#include "connections/connectionwindow.h"
|
|
#include "helpwindow.h"
|
|
#include "utility.h"
|
|
#include "filterutility.h"
|
|
|
|
/*
|
|
Some notes on things I'd like to put into the program but haven't put on github (yet)
|
|
|
|
Allow scripts to read/write signals from DBC files
|
|
allow scripts to load DBC files in support of the script - maybe the graphing system too.
|
|
*/
|
|
|
|
QString MainWindow::loadedFileName = "";
|
|
MainWindow *MainWindow::selfRef = nullptr;
|
|
|
|
MainWindow *MainWindow::getReference()
|
|
{
|
|
return selfRef;
|
|
}
|
|
|
|
MainWindow::MainWindow(QWidget *parent) :
|
|
QMainWindow(parent),
|
|
ui(new Ui::MainWindow)
|
|
{
|
|
ui->setupUi(this);
|
|
qRegisterMetaTypeStreamOperators<QVector<QString>>();
|
|
qRegisterMetaTypeStreamOperators<QVector<int>>();
|
|
|
|
useHex = true;
|
|
|
|
selfRef = this;
|
|
|
|
this->setWindowTitle("Savvy CAN V" + QString::number(VERSION));
|
|
|
|
model = new CANFrameModel(this); // set parent to mainwindow to prevent canframemodel to change thread (might be done by setModel but just in case)
|
|
|
|
QSortFilterProxyModel* proxyModel = new QSortFilterProxyModel;
|
|
proxyModel->setSourceModel(model);
|
|
|
|
ui->canFramesView->setModel(proxyModel);
|
|
|
|
settingsDialog = new MainSettingsDialog(); //instantiate the settings dialog so it can initialize settings if this is the first run or the config file was deleted.
|
|
settingsDialog->updateSettings(); //write out all the settings. If this is the first run it'll write defaults out.
|
|
|
|
readSettings();
|
|
|
|
//QHeaderView *verticalHeader = ui->canFramesView->verticalHeader();
|
|
//verticalHeader->setSectionResizeMode(QHeaderView::Fixed);
|
|
//verticalHeader->setDefaultSectionSize(10);
|
|
QHeaderView *HorzHdr = ui->canFramesView->horizontalHeader();
|
|
HorzHdr->setStretchLastSection(true); //causes the data column to automatically fill the tableview
|
|
connect(HorzHdr, SIGNAL(sectionClicked(int)), this, SLOT(headerClicked(int)));
|
|
|
|
graphingWindow = nullptr;
|
|
frameInfoWindow = nullptr;
|
|
playbackWindow = nullptr;
|
|
flowViewWindow = nullptr;
|
|
frameSenderWindow = nullptr;
|
|
dbcMainEditor = nullptr;
|
|
comparatorWindow = nullptr;
|
|
settingsDialog = nullptr;
|
|
firmwareUploaderWindow = nullptr;
|
|
discreteStateWindow = nullptr;
|
|
connectionWindow = nullptr;
|
|
scriptingWindow = nullptr;
|
|
rangeWindow = nullptr;
|
|
dbcFileWindow = nullptr;
|
|
fuzzingWindow = nullptr;
|
|
udsScanWindow = nullptr;
|
|
motorctrlConfigWindow = nullptr;
|
|
isoWindow = nullptr;
|
|
snifferWindow = nullptr;
|
|
bisectWindow = nullptr;
|
|
signalViewerWindow = nullptr;
|
|
temporalGraphWindow = nullptr;
|
|
dbcHandler = DBCHandler::getReference();
|
|
bDirty = false;
|
|
inhibitFilterUpdate = false;
|
|
rxFrames = 0;
|
|
framesPerSec = 0;
|
|
continuousLogging = false;
|
|
continuousLogFlushCounter = 0;
|
|
|
|
connect(ui->actionSetup, SIGNAL(triggered(bool)), SLOT(showConnectionSettingsWindow()));
|
|
connect(ui->actionOpen_Log_File, &QAction::triggered, this, &MainWindow::handleLoadFile);
|
|
connect(ui->actionGraph_Dta, &QAction::triggered, this, &MainWindow::showGraphingWindow);
|
|
connect(ui->actionFrame_Data_Analysis, &QAction::triggered, this, &MainWindow::showFrameDataAnalysis);
|
|
connect(ui->btnClearFrames, &QAbstractButton::clicked, this, &MainWindow::clearFrames);
|
|
connect(ui->actionSave_Log_File, &QAction::triggered, this, &MainWindow::handleSaveFile);
|
|
connect(ui->actionSave_Filtered_Log_File, &QAction::triggered, this, &MainWindow::handleSaveFilteredFile);
|
|
connect(ui->actionLoad_Filter_Definition, &QAction::triggered, this, &MainWindow::handleLoadFilters);
|
|
connect(ui->actionSave_Filter_Definition, &QAction::triggered, this, &MainWindow::handleSaveFilters);
|
|
connect(ui->action_Playback, &QAction::triggered, this, &MainWindow::showPlaybackWindow);
|
|
connect(ui->actionFlow_View, &QAction::triggered, this, &MainWindow::showFlowViewWindow);
|
|
connect(ui->action_Custom, &QAction::triggered, this, &MainWindow::showFrameSenderWindow);
|
|
connect(ui->canFramesView, &QAbstractItemView::pressed, this, &MainWindow::gridClicked);
|
|
connect(ui->canFramesView, &QAbstractItemView::doubleClicked, this, &MainWindow::gridDoubleClicked);
|
|
connect(ui->cbInterpret, &QAbstractButton::toggled, this, &MainWindow::interpretToggled);
|
|
connect(ui->cbOverwrite, &QAbstractButton::toggled, this, &MainWindow::overwriteToggled);
|
|
connect(ui->btnCaptureToggle, &QAbstractButton::clicked, this, &MainWindow::toggleCapture);
|
|
connect(ui->actionExit_Application, &QAction::triggered, this, &MainWindow::exitApp);
|
|
connect(ui->actionFuzzy_Scope, &QAction::triggered, this, &MainWindow::showFuzzyScopeWindow);
|
|
connect(ui->actionRange_State_2, &QAction::triggered, this, &MainWindow::showRangeWindow);
|
|
connect(ui->actionSave_Decoded_Frames, &QAction::triggered, this, &MainWindow::handleSaveDecoded);
|
|
connect(ui->actionSingle_Multi_State_2, &QAction::triggered, this, &MainWindow::showSingleMultiWindow);
|
|
connect(ui->actionFile_Comparison, &QAction::triggered, this, &MainWindow::showComparisonWindow);
|
|
connect(ui->actionScripting_INterface, &QAction::triggered, this, &MainWindow::showScriptingWindow);
|
|
connect(ui->btnNormalize, &QAbstractButton::clicked, this, &MainWindow::normalizeTiming);
|
|
connect(ui->actionPreferences, &QAction::triggered, this, &MainWindow::showSettingsDialog);
|
|
connect(model, &CANFrameModel::updatedFiltersList, this, &MainWindow::updateFilterList);
|
|
connect(ui->listFilters, &QListWidget::itemChanged, this, &MainWindow::filterListItemChanged);
|
|
connect(ui->btnFilterAll, &QAbstractButton::clicked, this, &MainWindow::filterSetAll);
|
|
connect(ui->btnFilterNone, &QAbstractButton::clicked, this, &MainWindow::filterClearAll);
|
|
connect(ui->actionFirmware_Update, &QAction::triggered, this, &MainWindow::showFirmwareUploaderWindow);
|
|
connect(ui->actionDBC_File_Manager, &QAction::triggered, this, &MainWindow::showDBCFileWindow);
|
|
connect(ui->actionFuzzing, &QAction::triggered, this, &MainWindow::showFuzzingWindow);
|
|
connect(ui->actionUDS_Scanner, &QAction::triggered, this, &MainWindow::showUDSScanWindow);
|
|
connect(ui->actionISO_TP_Decoder, &QAction::triggered, this, &MainWindow::showISOInterpreterWindow);
|
|
connect(ui->actionSniffer, &QAction::triggered, this, &MainWindow::showSnifferWindow);
|
|
connect(ui->actionMotorControlConfig, &QAction::triggered, this, &MainWindow::showMCConfigWindow);
|
|
connect(ui->actionCapture_Bisector, &QAction::triggered, this, &MainWindow::showBisectWindow);
|
|
connect(ui->actionSignal_Viewer, &QAction::triggered, this, &MainWindow::showSignalViewer);
|
|
connect(ui->actionSave_Continuous_Logfile, &QAction::triggered, this, &MainWindow::handleContinousLogging);
|
|
connect(ui->actionTemporal_Graph, &QAction::triggered, this, &MainWindow::showTemporalGraphWindow);
|
|
|
|
connect(CANConManager::getInstance(), &CANConManager::framesReceived, model, &CANFrameModel::addFrames);
|
|
|
|
lbStatusConnected.setText(tr("Connected to 0 buses"));
|
|
lbHelp.setText(tr("Press F1 on any screen for help"));
|
|
lbHelp.setAlignment(Qt::AlignCenter);
|
|
QFont boldFont;
|
|
boldFont.setBold(true);
|
|
lbHelp.setFont(boldFont);
|
|
updateFileStatus();
|
|
//lbStatusDatabase.setText(tr("No DBC database loaded"));
|
|
ui->statusBar->insertWidget(0, &lbStatusConnected, 1);
|
|
ui->statusBar->insertWidget(1, &lbStatusFilename, 1);
|
|
ui->statusBar->insertWidget(2, &lbHelp, 1);
|
|
//ui->statusBar->addWidget(&lbStatusDatabase);
|
|
ui->lblRemoteConn->setVisible(false);
|
|
ui->lineRemoteKey->setVisible(false);
|
|
|
|
ui->lbFPS->setText("0");
|
|
ui->lbNumFrames->setText("0");
|
|
|
|
// Prevent annoying accidental horizontal scrolling when filter list is populated with long interpreted message names
|
|
ui->listFilters->horizontalScrollBar()->setEnabled(false);
|
|
|
|
connect(&updateTimer, &QTimer::timeout, this, &MainWindow::tickGUIUpdate);
|
|
updateTimer.setInterval(250);
|
|
updateTimer.start();
|
|
|
|
elapsedTime = new QTime;
|
|
elapsedTime->start();
|
|
|
|
isConnected = false;
|
|
allowCapture = true;
|
|
|
|
//create a temporary frame to be able to capture the correct
|
|
//default height of an item in the table. Need to do this in case
|
|
//of scaling or font differences between different computers.
|
|
CANFrame temp;
|
|
temp.bus = 0;
|
|
temp.setFrameId(0x100);
|
|
temp.isReceived = true;
|
|
temp.setTimeStamp(QCanBusFrame::TimeStamp(0, 100000000));
|
|
model->addFrame(temp, true);
|
|
qApp->processEvents();
|
|
tickGUIUpdate(); //force a GUI refresh so that the row exists to measure
|
|
normalRowHeight = ui->canFramesView->rowHeight(0);
|
|
if (normalRowHeight == 0) normalRowHeight = 30; //should not be necessary but provides a sane number if something stupid happened.
|
|
qDebug() << "normal row height = " << normalRowHeight;
|
|
model->clearFrames();
|
|
|
|
//connect(CANConManager::getInstance(), CANConManager::connectionStatusUpdated, this, MainWindow::connectionStatusUpdated);
|
|
connect(CANConManager::getInstance(), SIGNAL(connectionStatusUpdated(int)), this, SLOT(connectionStatusUpdated(int)));
|
|
|
|
//Automatically create the connection window so it can be updated even if we never opened it.
|
|
connectionWindow = new ConnectionWindow();
|
|
connect(this, SIGNAL(suspendCapturing(bool)), connectionWindow, SLOT(setSuspendAll(bool)));
|
|
|
|
//these either are unfinished/not working or are not for general use. But,they exist
|
|
//so if you want to enable them and play with them then go for it.
|
|
ui->actionFirmware_Update->setVisible(false);
|
|
ui->actionMotorControlConfig->setVisible(false);
|
|
ui->actionSingle_Multi_State_2->setVisible(false);
|
|
|
|
installEventFilter(this);
|
|
}
|
|
|
|
MainWindow::~MainWindow()
|
|
{
|
|
updateTimer.stop();
|
|
killEmAll(); //Ride the lightning
|
|
delete ui;
|
|
delete model;
|
|
delete elapsedTime;
|
|
delete dbcHandler;
|
|
}
|
|
|
|
//kill every sub window that could be open. At the moment a hard coded list
|
|
//but eventually each window should be registered and be able to be iterated.
|
|
void MainWindow::killEmAll()
|
|
{
|
|
killWindow(graphingWindow);
|
|
killWindow(frameInfoWindow);
|
|
killWindow(playbackWindow);
|
|
killWindow(flowViewWindow);
|
|
killWindow(frameSenderWindow);
|
|
killWindow(comparatorWindow);
|
|
killWindow(dbcMainEditor);
|
|
killWindow(settingsDialog);
|
|
killWindow(discreteStateWindow);
|
|
killWindow(scriptingWindow);
|
|
killWindow(rangeWindow);
|
|
killWindow(dbcFileWindow);
|
|
killWindow(fuzzingWindow);
|
|
killWindow(udsScanWindow);
|
|
killWindow(isoWindow);
|
|
killWindow(snifferWindow);
|
|
killWindow(bisectWindow);
|
|
killWindow(firmwareUploaderWindow);
|
|
killWindow(motorctrlConfigWindow);
|
|
killWindow(signalViewerWindow);
|
|
killWindow(connectionWindow);
|
|
killWindow(temporalGraphWindow);
|
|
}
|
|
|
|
//forcefully close the window, kill it, and salt the earth
|
|
//note, for some stupid reason this function causes a seg fault
|
|
//it seems that when it runs just before the program closes it'll
|
|
//fault out when trying to close the connection window. I assume
|
|
//this could be because that window has long running threads open and doesn't
|
|
//close quickly or maybe cleanly. Investigate.
|
|
void MainWindow::killWindow(QDialog *win)
|
|
{
|
|
if (win)
|
|
{
|
|
win->close();
|
|
delete win;
|
|
win = nullptr;
|
|
}
|
|
}
|
|
|
|
void MainWindow::exitApp()
|
|
{
|
|
this->close();
|
|
QApplication::quit(); //forces the whole application to terminate when the main window is closed
|
|
}
|
|
|
|
void MainWindow::closeEvent(QCloseEvent *event)
|
|
{
|
|
Q_UNUSED(event);
|
|
removeEventFilter(this);
|
|
writeSettings();
|
|
exitApp();
|
|
}
|
|
|
|
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
|
|
{
|
|
if (event->type() == QEvent::KeyRelease) {
|
|
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
|
|
switch (keyEvent->key())
|
|
{
|
|
case Qt::Key_F1:
|
|
HelpWindow::getRef()->showHelp("mainscreen.html");
|
|
return true;
|
|
break;
|
|
}
|
|
return false;
|
|
} else {
|
|
// standard event processing
|
|
return QObject::eventFilter(obj, event);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
void MainWindow::updateSettings()
|
|
{
|
|
readUpdateableSettings();
|
|
emit settingsUpdated();
|
|
}
|
|
|
|
void MainWindow::readSettings()
|
|
{
|
|
QSettings settings;
|
|
if (settings.value("Main/SaveRestorePositions", false).toBool())
|
|
{
|
|
resize(settings.value("Main/WindowSize", QSize(800, 750)).toSize());
|
|
move(settings.value("Main/WindowPos", QPoint(100, 100)).toPoint());
|
|
ui->canFramesView->setColumnWidth(0, settings.value("Main/TimeColumn", 150).toUInt()); //time stamp
|
|
ui->canFramesView->setColumnWidth(1, settings.value("Main/IDColumn", 70).toUInt()); //frame ID
|
|
ui->canFramesView->setColumnWidth(2, settings.value("Main/ExtColumn", 40).toUInt()); //extended
|
|
ui->canFramesView->setColumnWidth(3, settings.value("Main/RemColumn", 40).toUInt()); //remote
|
|
ui->canFramesView->setColumnWidth(4, settings.value("Main/DirColumn", 40).toUInt()); //direction
|
|
ui->canFramesView->setColumnWidth(5, settings.value("Main/BusColumn", 40).toUInt()); //bus
|
|
ui->canFramesView->setColumnWidth(6, settings.value("Main/LengthColumn", 40).toUInt()); //length
|
|
ui->canFramesView->setColumnWidth(7, settings.value("Main/AsciiColumn", 50).toUInt()); //ascii
|
|
//ui->canFramesView->setColumnWidth(8, settings.value("Main/DataColumn", 225).toUInt()); //data
|
|
}
|
|
if (settings.value("Main/AutoScroll", false).toBool())
|
|
{
|
|
ui->cbAutoScroll->setChecked(true);
|
|
}
|
|
int fontSize = settings.value("Main/FontSize", 9).toUInt();
|
|
QFont newFont = QFont(ui->canFramesView->font());
|
|
newFont.setPointSize(fontSize);
|
|
ui->canFramesView->setFont(newFont);
|
|
|
|
readUpdateableSettings();
|
|
}
|
|
|
|
void MainWindow::readUpdateableSettings()
|
|
{
|
|
QSettings settings;
|
|
useHex = settings.value("Main/UseHex", true).toBool();
|
|
model->setHexMode(useHex);
|
|
Utility::decimalMode = !useHex;
|
|
secondsMode = settings.value("Main/TimeSeconds", false).toBool();
|
|
model->setSecondsMode(secondsMode);
|
|
useSystemClock = settings.value("Main/TimeClock", false).toBool();
|
|
model->setSysTimeMode(useSystemClock);
|
|
useFiltered = settings.value("Main/UseFiltered", false).toBool();
|
|
model->setTimeFormat(settings.value("Main/TimeFormat", "MMM-dd HH:mm:ss.zzz").toString());
|
|
|
|
if (settings.value("Main/FilterLabeling", false).toBool())
|
|
ui->listFilters->setMaximumWidth(250);
|
|
else
|
|
ui->listFilters->setMaximumWidth(175);
|
|
updateFilterList();
|
|
}
|
|
|
|
|
|
void MainWindow::writeSettings()
|
|
{
|
|
QSettings settings;
|
|
|
|
if (settings.value("Main/SaveRestorePositions", false).toBool())
|
|
{
|
|
settings.setValue("Main/WindowSize", size());
|
|
settings.setValue("Main/WindowPos", pos());
|
|
settings.setValue("Main/TimeColumn", ui->canFramesView->columnWidth(0));
|
|
settings.setValue("Main/IDColumn", ui->canFramesView->columnWidth(1));
|
|
settings.setValue("Main/ExtColumn", ui->canFramesView->columnWidth(2));
|
|
settings.setValue("Main/RemColumn", ui->canFramesView->columnWidth(3));
|
|
settings.setValue("Main/DirColumn", ui->canFramesView->columnWidth(4));
|
|
settings.setValue("Main/BusColumn", ui->canFramesView->columnWidth(5));
|
|
settings.setValue("Main/LengthColumn", ui->canFramesView->columnWidth(6));
|
|
settings.setValue("Main/AsciiColumn", ui->canFramesView->columnWidth(7));
|
|
//settings.setValue("Main/DataColumn", ui->canFramesView->columnWidth(8));
|
|
}
|
|
}
|
|
|
|
void MainWindow::updateConnectionSettings(QString connectionType, QString port, int speed0, int speed1)
|
|
{
|
|
Q_UNUSED(connectionType);
|
|
Q_UNUSED(port);
|
|
Q_UNUSED(speed0);
|
|
Q_UNUSED(speed1);
|
|
//connType = connectionType;
|
|
//portName = port;
|
|
|
|
//canSpeed0 = speed0;
|
|
//canSpeed1 = speed1;
|
|
if (isConnected)
|
|
{
|
|
//emit updateBaudRates(speed0, speed1);
|
|
}
|
|
}
|
|
|
|
void MainWindow::headerClicked(int logicalIndex)
|
|
{
|
|
//ui->canFramesView->sortByColumn(logicalIndex);
|
|
model->sortByColumn(logicalIndex);
|
|
}
|
|
|
|
void MainWindow::gridClicked(QModelIndex idx)
|
|
{
|
|
//qDebug() << "Grid Clicked";
|
|
if (ui->canFramesView->rowHeight(idx.row()) > normalRowHeight)
|
|
{
|
|
ui->canFramesView->setRowHeight(idx.row(), normalRowHeight);
|
|
}
|
|
else {
|
|
ui->canFramesView->resizeRowToContents(idx.row());
|
|
}
|
|
}
|
|
|
|
void MainWindow::gridDoubleClicked(QModelIndex idx)
|
|
{
|
|
//qDebug() << "Grid double clicked";
|
|
//grab ID and timestamp and send them away
|
|
CANFrame frame = model->getListReference()->at(idx.row());
|
|
emit sendCenterTimeID(frame.frameId(), frame.timeStamp().microSeconds() / 1000000.0);
|
|
}
|
|
|
|
void MainWindow::interpretToggled(bool state)
|
|
{
|
|
model->setInterpretMode(state);
|
|
//ui->canFramesView->resizeRowsToContents(); //a VERY costly operation!
|
|
}
|
|
|
|
void MainWindow::overwriteToggled(bool state)
|
|
{
|
|
if (state)
|
|
{
|
|
QMessageBox::StandardButton confirmDialog;
|
|
confirmDialog = QMessageBox::question(this, "Danger Will Robinson", "Enabling Overwrite mode will\ndelete your captured frames\nand replace them with one\nframe per ID.\n\nAre you ready to do that?",
|
|
QMessageBox::Yes|QMessageBox::No);
|
|
if (confirmDialog == QMessageBox::Yes)
|
|
{
|
|
model->setOverwriteMode(true);
|
|
}
|
|
else ui->cbOverwrite->setCheckState(Qt::Unchecked);
|
|
}
|
|
else
|
|
{
|
|
model->setOverwriteMode(false);
|
|
}
|
|
}
|
|
|
|
void MainWindow::updateFilterList()
|
|
{
|
|
if (model == nullptr) return;
|
|
const QMap<int, bool> *filters = model->getFiltersReference();
|
|
if (filters == nullptr) return;
|
|
|
|
qDebug() << "updateFilterList called on MainWindow";
|
|
|
|
inhibitFilterUpdate = true;
|
|
|
|
ui->listFilters->clear();
|
|
|
|
if (filters->isEmpty()) return;
|
|
|
|
QMap<int, bool>::const_iterator filterIter;
|
|
for (filterIter = filters->begin(); filterIter != filters->end(); ++filterIter)
|
|
{
|
|
/*QListWidgetItem *thisItem = */FilterUtility::createCheckableFilterItem(filterIter.key(), filterIter.value(), ui->listFilters);
|
|
}
|
|
inhibitFilterUpdate = false;
|
|
}
|
|
|
|
void MainWindow::filterListItemChanged(QListWidgetItem *item)
|
|
{
|
|
if (inhibitFilterUpdate) return;
|
|
//qDebug() << item->text();
|
|
|
|
// strip away possible filter label
|
|
int ID = FilterUtility::getIdAsInt(item);
|
|
bool isSet = false;
|
|
if (item->checkState() == Qt::Checked) isSet = true;
|
|
|
|
model->setFilterState(ID, isSet);
|
|
}
|
|
|
|
void MainWindow::filterSetAll()
|
|
{
|
|
inhibitFilterUpdate = true;
|
|
for (int i = 0; i < ui->listFilters->count(); i++)
|
|
{
|
|
ui->listFilters->item(i)->setCheckState(Qt::Checked);
|
|
}
|
|
inhibitFilterUpdate = false;
|
|
model->setAllFilters(true);
|
|
}
|
|
|
|
void MainWindow::filterClearAll()
|
|
{
|
|
inhibitFilterUpdate = true;
|
|
for (int i = 0; i < ui->listFilters->count(); i++)
|
|
{
|
|
ui->listFilters->item(i)->setCheckState(Qt::Unchecked);
|
|
}
|
|
inhibitFilterUpdate = false;
|
|
model->setAllFilters(false);
|
|
}
|
|
|
|
void MainWindow::tickGUIUpdate()
|
|
{
|
|
rxFrames = model->sendBulkRefresh();
|
|
//if(rxFrames>0)
|
|
//{
|
|
int elapsed = elapsedTime->elapsed();
|
|
if(elapsed) {
|
|
framesPerSec = (framesPerSec + (rxFrames * 1000 / elapsed)) / 2;
|
|
elapsedTime->restart();
|
|
}
|
|
else
|
|
framesPerSec = 0;
|
|
|
|
ui->lbNumFrames->setText(QString::number(model->rowCount()));
|
|
if (rxFrames > 0 && /*allowCapture && */ ui->cbAutoScroll->isChecked()) ui->canFramesView->scrollToBottom();
|
|
ui->lbFPS->setText(QString::number(framesPerSec));
|
|
if (rxFrames > 0)
|
|
{
|
|
bDirty = true;
|
|
emit framesUpdated(rxFrames); //anyone care that frames were updated?
|
|
}
|
|
|
|
if (model->needsFilterRefresh()) updateFilterList();
|
|
|
|
if (continuousLogging)
|
|
{
|
|
const QVector<CANFrame> *modelFrames = model->getListReference();
|
|
FrameFileIO::writeContinuousNative(modelFrames, modelFrames->count() - rxFrames);
|
|
|
|
continuousLogFlushCounter++;
|
|
if ((continuousLogFlushCounter % 3) == 0)
|
|
{
|
|
if (ui->lblContMsg->text().length() > 2)
|
|
{
|
|
ui->lblContMsg->setText("");
|
|
}
|
|
else
|
|
{
|
|
ui->lblContMsg->setText("CONTINUOUS LOGGING");
|
|
}
|
|
}
|
|
if (continuousLogFlushCounter > 8)
|
|
{
|
|
continuousLogFlushCounter = 0;
|
|
FrameFileIO::flushContinuousNative();
|
|
}
|
|
}
|
|
|
|
rxFrames = 0;
|
|
//}
|
|
}
|
|
|
|
void MainWindow::gotFrames(int framesSinceLastUpdate)
|
|
{
|
|
rxFrames += framesSinceLastUpdate;
|
|
emit frameUpdateRapid(framesSinceLastUpdate);
|
|
}
|
|
|
|
void MainWindow::addFrameToDisplay(CANFrame &frame, bool autoRefresh = false)
|
|
{
|
|
model->addFrame(frame, autoRefresh);
|
|
if (autoRefresh)
|
|
{
|
|
if (ui->cbAutoScroll->isChecked()) ui->canFramesView->scrollToBottom();
|
|
ui->lbNumFrames->setText(QString::number(model->rowCount()));
|
|
}
|
|
}
|
|
|
|
//A sub-window is sending us a center on timestamp and ID signal
|
|
//try to find the relevant frame in the list and focus on it.
|
|
void MainWindow::gotCenterTimeID(int32_t ID, double timestamp)
|
|
{
|
|
int idx = model->getIndexFromTimeID(ID, timestamp);
|
|
if (idx > -1)
|
|
{
|
|
ui->canFramesView->selectRow(idx);
|
|
}
|
|
}
|
|
|
|
void MainWindow::clearFrames()
|
|
{
|
|
ui->canFramesView->scrollToTop();
|
|
model->clearFrames();
|
|
CANConManager::getInstance()->resetTimeBasis();
|
|
ui->lbNumFrames->setText(QString::number(model->rowCount()));
|
|
bDirty = false;
|
|
loadedFileName = "";
|
|
updateFileStatus();
|
|
emit framesUpdated(-1);
|
|
}
|
|
|
|
void MainWindow::normalizeTiming()
|
|
{
|
|
model->normalizeTiming();
|
|
emit framesUpdated(-2); //claim an all new set of frames because every frame was updated.
|
|
}
|
|
|
|
void MainWindow::handleLoadFile()
|
|
{
|
|
QString filename;
|
|
QVector<CANFrame> tempFrames;
|
|
|
|
QMessageBox::StandardButton confirmDialog;
|
|
|
|
bool loadResult = FrameFileIO::loadFrameFile(filename, &tempFrames);
|
|
|
|
if (!loadResult)
|
|
{
|
|
confirmDialog = QMessageBox::question(this, "Error Loading", "Do you want to salvage what could be loaded?",
|
|
QMessageBox::Yes|QMessageBox::No);
|
|
if (confirmDialog == QMessageBox::Yes) {
|
|
loadResult = true;
|
|
}
|
|
}
|
|
|
|
if (loadResult)
|
|
{
|
|
ui->canFramesView->scrollToTop();
|
|
model->clearFrames();
|
|
model->insertFrames(tempFrames);
|
|
loadedFileName = filename;
|
|
model->recalcOverwrite();
|
|
ui->lbNumFrames->setText(QString::number(model->rowCount()));
|
|
if (ui->cbAutoScroll->isChecked()) ui->canFramesView->scrollToBottom();
|
|
|
|
updateFileStatus();
|
|
emit framesUpdated(-1);
|
|
}
|
|
}
|
|
|
|
void MainWindow::handleSaveFile()
|
|
{
|
|
QString filename;
|
|
|
|
if (FrameFileIO::saveFrameFile(filename, model->getListReference()))
|
|
{
|
|
loadedFileName = filename;
|
|
updateFileStatus();
|
|
}
|
|
}
|
|
|
|
void MainWindow::handleContinousLogging()
|
|
{
|
|
continuousLogging = !continuousLogging;
|
|
|
|
if (continuousLogging)
|
|
{
|
|
ui->actionSave_Continuous_Logfile->setText(tr("Cease Continuous Logging"));
|
|
FrameFileIO::openContinuousNative();
|
|
}
|
|
else
|
|
{
|
|
ui->actionSave_Continuous_Logfile->setText(tr("Start Continuous Logging"));
|
|
ui->lblContMsg->setText("");
|
|
FrameFileIO::closeContinuousNative();
|
|
}
|
|
}
|
|
|
|
void MainWindow::handleSaveFilteredFile()
|
|
{
|
|
QString filename;
|
|
|
|
if (FrameFileIO::saveFrameFile(filename, model->getFilteredListReference()))
|
|
{
|
|
loadedFileName = filename;
|
|
updateFileStatus();
|
|
}
|
|
}
|
|
|
|
void MainWindow::handleSaveFilters()
|
|
{
|
|
QString filename;
|
|
QFileDialog dialog(this);
|
|
QSettings settings;
|
|
|
|
QStringList filters;
|
|
filters.append(QString(tr("Filter list (*.ftl)")));
|
|
|
|
dialog.setDirectory(settings.value("Filters/LoadSaveDirectory", dialog.directory().path()).toString());
|
|
dialog.setFileMode(QFileDialog::AnyFile);
|
|
dialog.setNameFilters(filters);
|
|
dialog.setViewMode(QFileDialog::Detail);
|
|
dialog.setAcceptMode(QFileDialog::AcceptSave);
|
|
|
|
if (dialog.exec() == QDialog::Accepted)
|
|
{
|
|
filename = dialog.selectedFiles()[0];
|
|
if (!filename.contains('.')) filename += ".ftl";
|
|
if (dialog.selectedNameFilter() == filters[0]) model->saveFilterFile(filename);
|
|
settings.setValue("Filters/LoadSaveDirectory", dialog.directory().path());
|
|
}
|
|
}
|
|
|
|
void MainWindow::handleLoadFilters()
|
|
{
|
|
QString filename;
|
|
QFileDialog dialog(this);
|
|
QSettings settings;
|
|
|
|
QStringList filters;
|
|
filters.append(QString(tr("Filter List (*.ftl)")));
|
|
|
|
dialog.setDirectory(settings.value("Filters/LoadSaveDirectory", dialog.directory().path()).toString());
|
|
dialog.setFileMode(QFileDialog::ExistingFile);
|
|
dialog.setNameFilters(filters);
|
|
dialog.setViewMode(QFileDialog::Detail);
|
|
|
|
if (dialog.exec() == QDialog::Accepted)
|
|
{
|
|
filename = dialog.selectedFiles()[0];
|
|
//right now there is only one file type that can be loaded here so just do it.
|
|
model->loadFilterFile(filename);
|
|
settings.setValue("Filters/LoadSaveDirectory", dialog.directory().path());
|
|
}
|
|
}
|
|
|
|
void MainWindow::handleSaveDecoded()
|
|
{
|
|
QString filename;
|
|
QFileDialog dialog(this);
|
|
QSettings settings;
|
|
|
|
QStringList filters;
|
|
filters.append(QString(tr("Text File (*.txt)")));
|
|
|
|
dialog.setDirectory(settings.value("FileIO/LoadSaveDirectory", dialog.directory().path()).toString());
|
|
dialog.setFileMode(QFileDialog::AnyFile);
|
|
dialog.setNameFilters(filters);
|
|
dialog.setViewMode(QFileDialog::Detail);
|
|
dialog.setAcceptMode(QFileDialog::AcceptSave);
|
|
|
|
if (dialog.exec() == QDialog::Accepted)
|
|
{
|
|
filename = dialog.selectedFiles()[0];
|
|
if (!filename.contains('.')) filename += ".txt";
|
|
saveDecodedTextFile(filename);
|
|
settings.setValue("FileIO/LoadSaveDirectory", dialog.directory().path());
|
|
}
|
|
}
|
|
|
|
void MainWindow::saveDecodedTextFile(QString filename)
|
|
{
|
|
QFile *outFile = new QFile(filename);
|
|
const QVector<CANFrame> *frames = model->getFilteredListReference();
|
|
|
|
const unsigned char *data;
|
|
int dataLen;
|
|
const CANFrame *frame;
|
|
|
|
if (!outFile->open(QIODevice::WriteOnly | QIODevice::Text))
|
|
return;
|
|
/*
|
|
Time: 205.173000 ID: 0x20E Std Bus: 0 Len: 8
|
|
Data Bytes: 88 10 00 13 BB 00 06 00
|
|
SignalName Value
|
|
*/
|
|
for (int c = 0; c < frames->count(); c++)
|
|
{
|
|
frame = &frames->at(c);
|
|
data = reinterpret_cast<const unsigned char *>(frame->payload().constData());
|
|
dataLen = frame->payload().count();
|
|
|
|
QString builderString;
|
|
builderString += tr("Time: ") + QString::number((frame->timeStamp().microSeconds() / 1000000.0), 'f', 6);
|
|
builderString += tr(" ID: ") + Utility::formatCANID(frame->frameId(), frame->hasExtendedFrameFormat());
|
|
if (frame->hasExtendedFrameFormat()) builderString += tr(" Ext ");
|
|
else builderString += tr(" Std ");
|
|
builderString += tr("Bus: ") + QString::number(frame->bus);
|
|
builderString += " Len: " + QString::number(dataLen) + "\n";
|
|
outFile->write(builderString.toUtf8());
|
|
|
|
builderString = tr("Data Bytes: ");
|
|
for (int temp = 0; temp < dataLen; temp++)
|
|
{
|
|
builderString += Utility::formatNumber(data[temp]) + " ";
|
|
}
|
|
builderString += "\n";
|
|
outFile->write(builderString.toUtf8());
|
|
|
|
builderString = "";
|
|
if (dbcHandler != nullptr)
|
|
{
|
|
DBC_MESSAGE *msg = dbcHandler->findMessage(*frame);
|
|
if (msg != nullptr)
|
|
{
|
|
for (int j = 0; j < msg->sigHandler->getCount(); j++)
|
|
{
|
|
|
|
QString temp;
|
|
if (msg->sigHandler->findSignalByIdx(j)->processAsText(*frame, temp))
|
|
{
|
|
builderString.append("\t" + temp);
|
|
builderString.append("\n");
|
|
}
|
|
}
|
|
}
|
|
builderString.append("\n");
|
|
outFile->write(builderString.toUtf8());
|
|
}
|
|
}
|
|
outFile->close();
|
|
}
|
|
|
|
void MainWindow::toggleCapture()
|
|
{
|
|
allowCapture = !allowCapture;
|
|
if (allowCapture)
|
|
ui->btnCaptureToggle->setText("Suspend Capturing");
|
|
else
|
|
ui->btnCaptureToggle->setText("Restart Capturing");
|
|
|
|
emit suspendCapturing(!allowCapture);
|
|
}
|
|
|
|
void MainWindow::connectionStatusUpdated(int conns)
|
|
{
|
|
lbStatusConnected.setText(tr("Connected to ") + QString::number(conns) + tr(" buses"));
|
|
}
|
|
|
|
void MainWindow::updateFileStatus()
|
|
{
|
|
QString output;
|
|
if (model->rowCount() == 0)
|
|
{
|
|
output = tr("No file loaded");
|
|
}
|
|
else
|
|
{
|
|
if (loadedFileName.length() > 2)
|
|
{
|
|
output = loadedFileName + " loaded";
|
|
}
|
|
else
|
|
{
|
|
output = tr("No file loaded");
|
|
}
|
|
|
|
if (bDirty)
|
|
{
|
|
output += " (X)";
|
|
}
|
|
}
|
|
lbStatusFilename.setText(output);
|
|
}
|
|
|
|
CANFrameModel* MainWindow::getCANFrameModel()
|
|
{
|
|
return model;
|
|
}
|
|
|
|
void MainWindow::showSettingsDialog()
|
|
{
|
|
if (!settingsDialog)
|
|
{
|
|
settingsDialog = new MainSettingsDialog();
|
|
connect (settingsDialog, SIGNAL(updatedSettings()), this, SLOT(readUpdateableSettings()));
|
|
}
|
|
settingsDialog->show();
|
|
}
|
|
|
|
//always gets unfiltered list. You ask for the graphs so there is no need to send filtered frames
|
|
void MainWindow::showGraphingWindow()
|
|
{
|
|
if (!graphingWindow) {
|
|
graphingWindow = new GraphingWindow(model->getListReference());
|
|
connect(graphingWindow, SIGNAL(sendCenterTimeID(uint32_t,double)), this, SLOT(gotCenterTimeID(int32_t,double)));
|
|
connect(this, SIGNAL(sendCenterTimeID(uint32_t,double)), graphingWindow, SLOT(gotCenterTimeID(int32_t,double)));
|
|
}
|
|
|
|
if (flowViewWindow) //connect the two external windows together
|
|
{
|
|
connect(graphingWindow, SIGNAL(sendCenterTimeID(uint32_t,double)), flowViewWindow, SLOT(gotCenterTimeID(int32_t,double)));
|
|
connect(flowViewWindow, SIGNAL(sendCenterTimeID(uint32_t,double)), graphingWindow, SLOT(gotCenterTimeID(int32_t,double)));
|
|
}
|
|
graphingWindow->show();
|
|
}
|
|
|
|
void MainWindow::showTemporalGraphWindow()
|
|
{
|
|
//only create an instance of the object if we dont have one. Otherwise just display the existing one.
|
|
if (!temporalGraphWindow)
|
|
{
|
|
if (!useFiltered)
|
|
temporalGraphWindow = new TemporalGraphWindow(model->getListReference());
|
|
else
|
|
temporalGraphWindow = new TemporalGraphWindow(model->getFilteredListReference());
|
|
}
|
|
temporalGraphWindow->show();
|
|
}
|
|
|
|
void MainWindow::showFrameDataAnalysis()
|
|
{
|
|
//only create an instance of the object if we dont have one. Otherwise just display the existing one.
|
|
if (!frameInfoWindow)
|
|
{
|
|
if (!useFiltered)
|
|
frameInfoWindow = new FrameInfoWindow(model->getListReference());
|
|
else
|
|
frameInfoWindow = new FrameInfoWindow(model->getFilteredListReference());
|
|
}
|
|
frameInfoWindow->show();
|
|
}
|
|
|
|
void MainWindow::showISOInterpreterWindow()
|
|
{
|
|
if (!isoWindow)
|
|
{
|
|
if (!useFiltered)
|
|
isoWindow = new ISOTP_InterpreterWindow(model->getListReference());
|
|
else
|
|
isoWindow = new ISOTP_InterpreterWindow(model->getFilteredListReference());
|
|
}
|
|
isoWindow->show();
|
|
}
|
|
|
|
void MainWindow::showSnifferWindow()
|
|
{
|
|
if (!snifferWindow)
|
|
snifferWindow = new SnifferWindow(this);
|
|
snifferWindow->show();
|
|
}
|
|
|
|
void MainWindow::showBisectWindow()
|
|
{
|
|
if (!bisectWindow)
|
|
{
|
|
bisectWindow = new BisectWindow(model->getListReference());
|
|
}
|
|
bisectWindow->show();
|
|
}
|
|
|
|
void MainWindow::showFrameSenderWindow()
|
|
{
|
|
if (!frameSenderWindow)
|
|
{
|
|
if (!useFiltered)
|
|
frameSenderWindow = new FrameSenderWindow(model->getListReference());
|
|
else
|
|
frameSenderWindow = new FrameSenderWindow(model->getFilteredListReference());
|
|
}
|
|
frameSenderWindow->show();
|
|
}
|
|
|
|
void MainWindow::showPlaybackWindow()
|
|
{
|
|
if (!playbackWindow)
|
|
{
|
|
if (!useFiltered)
|
|
playbackWindow = new FramePlaybackWindow(model->getListReference());
|
|
else
|
|
playbackWindow = new FramePlaybackWindow(model->getFilteredListReference());
|
|
}
|
|
playbackWindow->show();
|
|
}
|
|
|
|
void MainWindow::showFirmwareUploaderWindow()
|
|
{
|
|
if (!firmwareUploaderWindow)
|
|
{
|
|
firmwareUploaderWindow = new FirmwareUploaderWindow(model->getListReference());
|
|
}
|
|
firmwareUploaderWindow->show();
|
|
}
|
|
|
|
void MainWindow::showComparisonWindow()
|
|
{
|
|
if (!comparatorWindow)
|
|
{
|
|
comparatorWindow = new FileComparatorWindow();
|
|
}
|
|
comparatorWindow->show();
|
|
}
|
|
|
|
void MainWindow::showSingleMultiWindow()
|
|
{
|
|
if (!discreteStateWindow)
|
|
{
|
|
discreteStateWindow = new DiscreteStateWindow(model->getListReference());
|
|
}
|
|
discreteStateWindow->show();
|
|
}
|
|
|
|
void MainWindow::showFuzzingWindow()
|
|
{
|
|
if (!fuzzingWindow)
|
|
{
|
|
fuzzingWindow = new FuzzingWindow(model->getListReference());
|
|
}
|
|
fuzzingWindow->show();
|
|
}
|
|
|
|
void MainWindow::showMCConfigWindow()
|
|
{
|
|
if (!motorctrlConfigWindow)
|
|
{
|
|
motorctrlConfigWindow = new MotorControllerConfigWindow(model->getListReference());
|
|
//connect(motorctrlConfigWindow, SIGNAL(sendCANFrame(const CANFrame*,int)), worker, SLOT(sendFrame(const CANFrame*,int)));
|
|
//connect(motorctrlConfigWindow, SIGNAL(sendFrameBatch(const QList<CANFrame>*)), worker, SLOT(sendFrameBatch(const QList<CANFrame>*)));
|
|
}
|
|
motorctrlConfigWindow->show();
|
|
}
|
|
|
|
void MainWindow::showUDSScanWindow()
|
|
{
|
|
if (!udsScanWindow)
|
|
{
|
|
udsScanWindow = new UDSScanWindow(model->getListReference());
|
|
}
|
|
udsScanWindow->show();
|
|
}
|
|
|
|
void MainWindow::showScriptingWindow()
|
|
{
|
|
if (!scriptingWindow)
|
|
{
|
|
scriptingWindow = new ScriptingWindow(model->getListReference());
|
|
}
|
|
scriptingWindow->show();
|
|
}
|
|
|
|
void MainWindow::showRangeWindow()
|
|
{
|
|
if (!rangeWindow)
|
|
{
|
|
rangeWindow = new RangeStateWindow(model->getListReference());
|
|
}
|
|
rangeWindow->show();
|
|
}
|
|
|
|
void MainWindow::showFuzzyScopeWindow()
|
|
{
|
|
//not done yet
|
|
}
|
|
|
|
void MainWindow::showFlowViewWindow()
|
|
{
|
|
if (!flowViewWindow)
|
|
{
|
|
if (!useFiltered)
|
|
flowViewWindow = new FlowViewWindow(model->getListReference());
|
|
else
|
|
flowViewWindow = new FlowViewWindow(model->getFilteredListReference());
|
|
connect(flowViewWindow, SIGNAL(sendCenterTimeID(uint32_t,double)), this, SLOT(gotCenterTimeID(int32_t,double)));
|
|
connect(this, SIGNAL(sendCenterTimeID(uint32_t,double)), flowViewWindow, SLOT(gotCenterTimeID(int32_t,double)));
|
|
}
|
|
|
|
if (graphingWindow)
|
|
{
|
|
connect(graphingWindow, SIGNAL(sendCenterTimeID(uint32_t,double)), flowViewWindow, SLOT(gotCenterTimeID(int32_t,double)));
|
|
connect(flowViewWindow, SIGNAL(sendCenterTimeID(uint32_t,double)), graphingWindow, SLOT(gotCenterTimeID(int32_t,double)));
|
|
}
|
|
|
|
flowViewWindow->show();
|
|
}
|
|
|
|
|
|
void MainWindow::DBCSettingsUpdated()
|
|
{
|
|
updateFilterList();
|
|
model->sendRefresh();
|
|
}
|
|
|
|
void MainWindow::showDBCFileWindow()
|
|
{
|
|
if (!dbcFileWindow)
|
|
{
|
|
dbcFileWindow = new DBCLoadSaveWindow(model->getListReference());
|
|
connect(dbcFileWindow, &DBCLoadSaveWindow::updatedDBCSettings, this, &MainWindow::DBCSettingsUpdated);
|
|
}
|
|
dbcFileWindow->show();
|
|
}
|
|
|
|
void MainWindow::showSignalViewer()
|
|
{
|
|
if (!signalViewerWindow)
|
|
{
|
|
if (!useFiltered)
|
|
signalViewerWindow = new SignalViewerWindow(model->getListReference());
|
|
else
|
|
signalViewerWindow = new SignalViewerWindow(model->getFilteredListReference());
|
|
}
|
|
signalViewerWindow->show();
|
|
}
|
|
|
|
void MainWindow::showConnectionSettingsWindow()
|
|
{
|
|
if (!connectionWindow)
|
|
{
|
|
connectionWindow = new ConnectionWindow();
|
|
}
|
|
connectionWindow->show();
|
|
}
|